50

I'm interested in examining the x86 assembly output of the standard Go compiler to see if my code is really being converted into reasonably efficient assembly code; hopefully, by profiling and examining the assembly output, I could get a clue as to where/how I should rewrite my Go code for maximum performance. But when I examine the code using the -S flag, Go spits out a mess! I'd like two things:

  1. Is there a way to make the Go compiler dump the assembly output into a file, not just print it out on Terminal?

  2. Also, is there a way to make the Go compiler separate out the assembly code into separate functions, with labels? I know some functions may be inlined and hence not appear in the assembly code. What I'm seeing know is just a homogenous blob of assembly which is almost impossible to understand.

phuclv
  • 37,963
  • 15
  • 156
  • 475
Gautam
  • 1,732
  • 4
  • 18
  • 26
  • 6
    1) What about using `>` or whatever your command shell supports for redirecting the output of a command to a file? – Michael May 21 '14 at 17:39
  • 1
    Also, your approach to profiling by looking at the assembly code may not be the most productive one. Have you looked at the profiling tools available for Go code? (e.g. http://blog.golang.org/profiling-go-programs) – Michael May 21 '14 at 17:43
  • 1
    I agree with you, assembly output needs some improvement. The problem is that the Go compiler does not generate actual assembly. It generates something that looks a lot like assembly but contains a lot of pseudo-instructions that will be expanded by the linker. Only after linking is done, actual assembly is produced (and immediately assembled). – fuz May 21 '14 at 18:27
  • Also, have a look at [how the Plan 9 assembler works](http://plan9.bell-labs.com/sys/doc/asm.html). The reference implementation of Go uses this assembler. – fuz May 21 '14 at 18:29
  • PS: I'm looking for something similar to the GCC command: -fverbose-asm – Gautam May 21 '14 at 20:20

7 Answers7

47
  1. You can redirect the output to a file like this:

     go tool compile -S file.go > file.s
    
  2. You can disable the optimization with -N:

     go tool compile -S -N file.go
    

Alternatively, you can use gccgo:

gccgo -S -O0 -masm=intel test.go

which will generate test.s. You can play with the -O0/1/2/3 to see the different optimizations.

fuz
  • 88,405
  • 25
  • 200
  • 352
creack
  • 116,210
  • 12
  • 97
  • 73
  • 2
    `go build -gcflags -S test.go > test.s` should work, too (according to https://golang.org/doc/asm) – rkusa Dec 22 '15 at 17:04
  • 18
    `go tool comile -S file.go > file.S` since `go1.5` – Ivan Black Feb 10 '16 at 09:03
  • 1
    @IvanBlack Your comment deserves a top-level response. – dolmen Feb 21 '17 at 15:45
  • tried to add the original to add what @IvanBlack said (only since go.15 for "go tool compile"), but the edit queue is full. Perhaps author may want to. Not a big deal though, these versions get old so quick... – alex gimenez Aug 20 '21 at 10:21
24

I don't recommend using the output of -S as the Go linker can change what gets written to the object code quite a lot. It does give you some idea as to what is going on.

The go assembler output is rather non-standard too.

When I want to do this I always use objdump which will give you a nice standard assembler output.

Eg for x86 / amd64

objdump -d executable > disassembly

And for ARM (to get the register names to be the same as Go uses)

objdump -M reg-names-raw -d executable > disassembly
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
16

Run go tool objdump on the resulting executable file.

To restrict the output to interesting functions, use its -s option.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
9

To dump the output to file:

go tool objdump EXECUTABLE_FILE > ASSEMBLY_FILE

If you want to include the source Go code (assuming you have a working golang setup, and you built the executable yourself):

go tool objdump -S EXECUTABLE_FILE

To make the output even easier to look at I use a small hacky wrapper that produces the following (in a nutshell, it colorizes instructions that alter the control flow -blue for jumps, green for call/return, red for traps, violet for padding- and adds new lines after unconditional control flow jumps):

example output of go-objdump wrapper

If you use the wrapper above you will likely want to use the -R switch when piping to less (or by adding it to the environment, e.g. in .bashrc: export LESS="$LESS -R"):

go-objdump EXECUTABLE_FILE | less -R

Alternatively, there is godbolt.org that has probably the most readable output and allows you to switch between compilers (gc, gccgo) and versions very easily.

CAFxX
  • 28,060
  • 6
  • 41
  • 66
7

A recent alternative would be loov/lensm, which can view assembly and source.
(From Egon Elbre)

To run the program, provide a regular expression filter for the symbol you want to inspect.
-watch allows to automatically reload the executable and information when it changes.

lensm -watch -filter Fibonacci lensm

Note: The program requires a binary that is built on your computer, otherwise the source code for the functions cannot be loaded.

Result:

https://github.com/loov/lensm/blob/main/screenshot.png?raw=true


That could be a nice addition to godbolt.org

godbolt.org

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
3

I had problems with the other answers as the assembly produced provided much more information than I wanted and still not enough details. Let me explain: it provided the assembly for all libraries imported by go internally and did not provide the lines of where my code was (my code was all at the bottom of the file)

Here is what I found from the official docs:

$ GOOS=linux GOARCH=amd64 go tool compile -S x.go # or: go build -gcflags -S x.go

File:

package main

func main() {
    println(3)
}

Produces:

--- prog list "main" ---
0000 (x.go:3) TEXT    main+0(SB),$8-0
0001 (x.go:3) FUNCDATA $0,gcargs·0+0(SB)
0002 (x.go:3) FUNCDATA $1,gclocals·0+0(SB)
0003 (x.go:4) MOVQ    $3,(SP)
0004 (x.go:4) PCDATA  $0,$8
0005 (x.go:4) CALL    ,runtime.printint+0(SB)
0006 (x.go:4) PCDATA  $0,$-1
0007 (x.go:4) PCDATA  $0,$0
0008 (x.go:4) CALL    ,runtime.printnl+0(SB)
0009 (x.go:4) PCDATA  $0,$-1
0010 (x.go:5) RET     ,

So what I did was basically:

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

and it got the result I wanted!

renno
  • 2,659
  • 2
  • 27
  • 58
  • 1
    You might find the Godbolt compiler explorer useful for color highlighting to map source lines to asm. https://godbolt.org/g/yQ4gbQ has output for a simple function with both gc1.10.1 and gccgo 7.2. – Peter Cordes May 24 '18 at 01:03
  • That is a really nice tool – renno May 24 '18 at 01:15
2

The easiest way i found around for Mac users with XCode dev tools is with otool

$ otool -tV <executable>

Source

Lucas
  • 9,871
  • 5
  • 42
  • 52