How to Use the Go Assembler and Object Files

Go's assembler is a low-level tool for writing performance-critical code or interfacing with hardware, but you rarely use it directly for standard application logic.

Go's assembler is a low-level tool for writing performance-critical code or interfacing with hardware, but you rarely use it directly for standard application logic. You write assembly in .s files using Go's specific syntax (which differs from AT&T or Intel syntax) and compile them alongside your Go code, or you inspect object files (.o) using go tool nm and go tool objdump to analyze symbols and binary structure.

For writing custom assembly, you must adhere to the Go assembler syntax, which uses a specific register naming convention and requires a TEXT directive to define a function. Here is a minimal example of a Go function calling a custom assembly routine that adds two integers:

add.s

// +build ignore

// This file must be compiled with: go tool compile -S add.s
// Or included in a package via go build if the build tags match.

TEXT ยทAdd(SB), NOSPLIT, $0-16
    MOVQ x+0(FP), AX
    MOVQ y+8(FP), BX
    ADDQ BX, AX
    MOVQ AX, ret+16(FP)
    RET

main.go

package main

import "fmt"

//go:linkname Add add.Add
func Add(x, y int64) int64

func main() {
    fmt.Println(Add(10, 20)) // Output: 30
}

Note that go:linkname is required to expose the assembly function to Go code, and the function signature in the assembly file ($0-16) must match the stack layout of the Go arguments.

To inspect object files or understand how your Go code translates to machine instructions, use the go tool suite. First, compile your package to an object file without linking:

go tool compile -S main.go > main.s

This outputs the assembly listing. To inspect the symbols and sections of a compiled object file (.o), use nm:

go tool compile main.go
go tool nm main.o

This lists all global and local symbols. For a deeper look at the actual machine code and disassembly, use objdump:

go tool objdump -d main.o

This is essential for debugging performance bottlenecks or verifying that inlining and optimizations occurred as expected. Remember that the Go assembler is tightly coupled with the Go compiler; you cannot easily mix it with external C assembly without using cgo or writing a C wrapper, as the calling conventions and stack management are specific to the Go runtime.