🍂 Mood: Putting on my fuzzy sweatpants for the first time this season.

🎵 Soundtrack: Sour

📚 Reading Github Issues

Read 13 github issues this morning.

🧶 Back to Go Fuzzing

There was another code review in my inbox for my “interesting” go fuzzing submission. The reviewer literally re-wrote the code and the commit message for me 🥲. Ah well, I learned more about style conventions. No making functions that are only used once even if it makes things cleaner! I re-submitted their code, it was approved in 3 minutes, but then the tests failed. Turns out they are failing on master for reasons that I read about in the github issues this morning. Oh well, hopefully that’ll be fixed and “my” commit can be merged soon.

🧩 What does this assembly mean?

With that go fuzz issue wrapped up I turned back to trying to learn about the go compiler.

🎥 Today I started by watching “GopherCon 2016: Rob Pike - The Design of the Go Assembler”. It was really interesting! Highly recommended.

Fun fact of the day: Some parts of the go standard library are written in assembler so that they can be very optimized, like goroutine switching and some math/big stuff.

I also found the first chapter of an abandoned book on go assembly. In the first chaper they go through an example and dissect it just like I am trying to do.

I still don’t understand it completely, but I’m getting closer!

Here is my code:

1 package main
2
3 import "fmt"
4
5 func main() {
6   sum := 1 + 1
7   fmt.Println(sum)
8 }

Here is my unannotated assembly (cleaned up for clarity):

	0x0000 00000 (sum.go:5)	TEXT	"".main(SB), ABIInternal, $64-0
	0x0000 00000 (sum.go:5)	CMPQ	SP, 16(R14)
	0x0004 00004 (sum.go:5)	JLS	95
	0x0006 00006 (sum.go:5)	SUBQ	$64, SP
	0x000a 00010 (sum.go:5)	MOVQ	BP, 56(SP)
	0x000f 00015 (sum.go:5)	LEAQ	56(SP), BP
	0x0014 00020 (sum.go:7)	MOVL	$2, AX
	0x0019 00025 (sum.go:7)	CALL	runtime.convT64(SB)
	0x001e 00030 (sum.go:7)	MOVUPS	X15, ""..autotmp_11+40(SP)
	0x0024 00036 (sum.go:7)	LEAQ	type.int(SB), CX
	0x002b 00043 (sum.go:7)	MOVQ	CX, ""..autotmp_11+40(SP)
	0x0030 00048 (sum.go:7)	MOVQ	AX, ""..autotmp_11+48(SP)
	0x0035 00053 (<unknown line number>)	NOP
	0x0035 00053 (fmt/print.go:274)	MOVQ	os.Stdout(SB), BX
	0x003c 00060 (fmt/print.go:274)	LEAQ	go.itab.*os.File,io.Writer(SB), AX
	0x0043 00067 (fmt/print.go:274)	LEAQ	""..autotmp_11+40(SP), CX
	0x0048 00072 (fmt/print.go:274)	MOVL	$1, DI
	0x004d 00077 (fmt/print.go:274)	MOVQ	DI, SI
	0x0050 00080 (fmt/print.go:274)	CALL	fmt.Fprintln(SB)
	0x0055 00085 (sum.go:8)	MOVQ	56(SP), BP
	0x005a 00090 (sum.go:8)	ADDQ	$64, SP
	0x005e 00094 (sum.go:8)	RET
	0x005f 00095 (sum.go:8)	NOP
	0x005f 00095 (sum.go:5)	NOP
	0x0060 00096 (sum.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x0065 00101 (sum.go:5)	JMP	0

And now I will attempt to understand it by going line by line in mind-numbing detail. Some of these definitions are direct quotes from other resources that will be (shamefully) unattributed.

💻 0x0000 00000 (sum.go:5) TEXT ““.main(SB), ABIInternal, $64-0

  • The TEXT directive declares the symbol ““.main and the instructions that follow form the body of the function. The “” will be replaced with the name of the current package at link time. (Linking is what happens after compiling.)
  • SB is the virtual register that holds the “static base” pointer (ie the beginning of the address-space for the program).
  • ABIInternal (aka internal application binary interface) defines the layout of data in memory and the conventions for calling between Go functions.
  • ($64-0) is the frame size followed by the argument size. In this case the stack frame for the function is 64 bytes and the argument size is 0 bytes. This makes sense because there are 0 arguments to the main function.
  • I THINK this 64 bytes is for the float sum.
  • Summary: Make a function named main that requires 64 bytes and takes 0 arguments.

💻 0x0000 00000 (sum.go:5) CMPQ SP, 16(R14)

  • CMP means compare. Q indicates a quadword (8 bytes). So CMPQ means compare things that are 8 bytes.
  • SP is the stack pointer. It points to the highest address within the local stack frame. It is a pseudo-register that is used to refer to frame-local variables and arguments being prepared for function calls.
  • 16(R14) - I think this is pointing at something that is 16bytes offset from register R14. What is in R14? No idea.
  • Summary: Compare the value in the stack points (SP) and the value 16 bytes offset from R14 (whatever that is).

💻 0x0004 00004 (sum.go:5) JLS 95

  • JLS means jump if less than.
  • 95 is referring to line 95 (hex 0x005f) of the instructions.
  • Line 95 is basically exiting the function.
  • This instruction is paired with the compare (CMPQ) above.
  • I’m not sure what these instructions are for. Maybe some kind of safety check on the pointers to make sure everything is good before moving on to the logic of the function?
  • Summary: if the SP was less than 16(R14) (or maybe the other way around?) then jump to line 95.

💻 0x0006 00006 (sum.go:5) SUBQ $64, SP

  • SUBQ means subtract a quadword (8 bytes).
  • Remember: the stack grows down, so subtracting is actually growing the stack frame.
  • $64 - this means 64 bytes. This makes sense since (ugh) we learned above that this function requires a stack frame of 64 bytes. This is the code making that room for the function… I think.
  • Summary: grow the stack frame 64 bytes.

💻 0x000a 00010 (sum.go:5) MOVQ BP, 56(SP)

  • MOVQ - means moves a quadword (8 bytes) to the stack
  • BP is the base pointer for our function.
  • This says to move the value that is in BP to 56 offset from SP.
  • My rationalization for the placement of BP: 56 = 64 (the size of our function)
    • 8 (the size of this pointer).
  • This location of BP and SP relative to each other makes sense based on what I learned today. HOWEVER, I thought the function was 64 bytes because sum becomes a float. Idk how that is going to work if there is only 56 bytes left in this stack frame.
  • Summary: stores the base frame pointer in the stack frame

💻 0x000f 00015 (sum.go:5) LEAQ 56(SP), BP

  • LEAQ computes the address of the first argument and stores it in the second argument.
  • With the MOVQ command directly above, it seems like these commands are used in tandem to set BP and 56(SP) equal to each other.
  • Summary: set the base pointer to point at the correct place.

💻 0x0014 00020 (sum.go:7) MOVL $2, AX

  • MOVL means move a long-word (4 byte value).
  • $2 is the value 1 that is used in the addition in the function. I know this because echo 'obase=2;1' | bc is 1.
  • I only see 1 being stored once, not twice even though it is used twice. I assume it is smart enough to know that this value is hardcoded and it can use the same variable?
  • Summary: store the value 1 in AX.

💻 0x0019 00025 (sum.go:7) CALL runtime.convT64(SB)

  • CALL is a pseudo-instruction to call a function.
  • runtime.convT64 converts an int to a float (64 bytes).
  • Given that the add function returns a float I assume somewhere in there it calls this function.
  • SB is the static base pointer. Why is it calling convT64 on that and not AX?
  • I find it odd that I did not see any “CALL add AX, AX” or something
  • Summary: turn something (??) into a float

💻 0x001e 00030 (sum.go:7) MOVUPS X15, “”..autotmp_11+40(SP)

  • MOVUPS means to move a double quadword (16 bytes)
  • X15 - no idea what this is
  • auto_tmp… - based on this issue I learned that this is a compiler generated temporary variable. No idea what for though.
  • Summary: no clue.

💻 0x0024 00036 (sum.go:7) LEAQ type.int(SB), CX

  • Turn SB into an int and move it to CX.
  • Summary: no clue.

💻 0x002b 00043 (sum.go:7) MOVQ CX, “”..autotmp_11+40(SP)

  • Move CX to a temporary variable.
  • Summary: no clue.

💻 0x0030 00048 (sum.go:7) MOVQ AX, “”..autotmp_11+48(SP)

  • Move AX to a temporary variable.
  • Summary: no clue.

💻 0x0035 00053 (<unknown line number>) NOP

  • No op.
  • I read something about needing no ops for safety reasons? Whatever that means.
  • Summary: do nothing

💻 Some print stuff that I don’t know what it is for.

  • 0x0035 00053 (fmt/print.go:274) MOVQ os.Stdout(SB), BX
  • 0x003c 00060 (fmt/print.go:274) LEAQ go.itab.*os.File,io.Writer(SB), AX
  • 0x0043 00067 (fmt/print.go:274) LEAQ “”..autotmp_11+40(SP), CX
  • 0x0048 00072 (fmt/print.go:274) MOVL $1, DI
  • 0x004d 00077 (fmt/print.go:274) MOVQ DI, SI
  • 0x0050 00080 (fmt/print.go:274) CALL fmt.Fprintln(SB)

💻 0x0055 00085 (sum.go:8) MOVQ 56(SP), BP

  • Move whatever is offset SP by 56 bytes and move it into BP
  • Summary: no clue.

💻 0x005a 00090 (sum.go:8) ADDQ $64, SP

  • Add 64 bytes to the stack pointer.
  • Since the stack grows downwards this decreases the stack frame by 64 bytes.
  • I think this is part of the clean up of closing the function and removing the function’s stack frame from the stack.
  • Summary: shrink the stack frame by 64 bytes.

💻 0x005e 00094 (sum.go:8) RET

  • RET is a pseudo-instruction for returning.
  • Summary: Return from the function! Close up! We’re going home!

💻 0x005f 00095 (sum.go:8) NOP

  • Summary: do nothing

💻 0x005f 00095 (sum.go:5) NOP

  • Summary: do nothing

💻 0x0060 00096 (sum.go:5) CALL runtime.morestack_noctxt(SB)

  • Maybe some under-the-hood stuff about cleaning up the stack after this function is finished?
  • Summary: not sure.

💻 0x0065 00101 (sum.go:5) JMP 0

  • Jump to line 0.
  • Why would you jump to the top? Not sure. But probably is some kind of continuation of the clean up when finishing this function.
  • Summary: not sure.

😲 Wow, you made it to the end. I can’t believe you read all that.

It’s been two days of trying to understand the go compiler and compiling and assembly in general. Congrats on making it to the end of my notes. Here is a lollipop: 🍭.