0. Preface
Functions are first-class citizens of Go, and this article takes a look at what some of the functions we commonly use are doing from an assembly perspective.
1. Functions
1.1 main function
The sum of the two numbers is calculated in the main function as follows:
package main
func main() {
x, y := 1, 2
z := x + y
print(z)
}
utilizationdlv
Debugging Functions (don't knowdlv
please seeGo plan9 compilation: Getting through the application to the ground floor):
# dlv debug
Type 'help' for list of commands.
(dlv) b
Breakpoint 1 set at 0x45feca for () ./:3
(dlv) c
> () ./:3 (hits goroutine(1):1 total:1) (PC: 0x45feca)
1: package main
2:
=> 3: func main() {
4: x, y := 1, 2
5: z := x + y
6: print(z)
7: }
disass
View the corresponding assembly instructions:
(dlv)
TEXT (SB) /root/go/src/foundation/ex4/
:3 0x45fec0 493b6610 cmp rsp, qword ptr [r14+0x10]
:3 0x45fec4 763d jbe 0x45ff03
:3 0x45fec6 55 push rbp
:3 0x45fec7 4889e5 mov rbp, rsp
=> :3 0x45feca* 4883ec20 sub rsp, 0x20
:4 0x45fece 48c744241801000000 mov qword ptr [rsp+0x18], 0x1
:4 0x45fed7 48c744241002000000 mov qword ptr [rsp+0x10], 0x2
:5 0x45fee0 48c744240803000000 mov qword ptr [rsp+0x8], 0x3
:6 0x45fee9 e8d249fdff call $
:6 0x45feee 488b442408 mov rax, qword ptr [rsp+0x8]
:6 0x45fef3 e86850fdff call $
:6 0x45fef8 e8234afdff call $
:7 0x45fefd 4883c420 add rsp, 0x20
:7 0x45ff01 5d pop rbp
:7 0x45ff02 c3 ret
:3 0x45ff03 e8d8cdffff call $runtime.morestack_noctxt
:3 0x45ff08 ebb6 jmp $
(dlv) regs
Rsp = 0x000000c00003e758
I'm sure I've seen it.Go plan9 compilation: Getting through the application to the ground floor of students who already have some understanding of the above assembly instructions.
Here we go to the main function, which executes tosub rsp, 0x20
instruction, which opens up 0x20 bytes of memory for the main function. Continuing down the line, each of the0x1
,0x2
cap (a poem)0x3
put into[rsp+0x18]
,[rsp+0x10]
cap (a poem)[rsp+0x8]
(I don't think I see it in the assembly instructions)z := x + y
(the addition of which is reasonably suspected to have been optimized by the compiler).
Continue.mov rax, qword ptr [rsp+0x8]
commander-in-chief (military)[rsp+0x8]
Value of the address0x3
put intorax
in the register. Then, call thecall $
printablerax
The value of the After realizing the output of two numbers. We'll skip the subsequent instructions and not go into them.
1.2 Function calls
By implementing the sum of two numbers in the main function, we don't have a way to see what's going on in the function call.
Next, define the sum function to implement the sum of two numbers, and call sum in the main function. focus on what the function does when it is called.
Examples are shown below:
package main
func main() {
a, b := 1, 2
println(sum(a, b))
}
func sum(x, y int) int {
z := x + y
return z
}
utilizationdlv
Debugging Functions:
# dlv debug
Type 'help' for list of commands.
(dlv) b
Breakpoint 1 set at 0x45feca for () ./:3
(dlv) c
> () ./:3 (hits goroutine(1):1 total:1) (PC: 0x45feca)
1: package main
2:
=> 3: func main() {
4: a, b := 1, 2
5: println(sum(a, b))
6: }
7:
8: func sum(x, y int) int {
(dlv) disass
Sending output to pager...
TEXT (SB) /root/go/src/foundation/ex6/
:3 0x45fec0 493b6610 cmp rsp, qword ptr [r14+0x10]
:3 0x45fec4 764f jbe 0x45ff15
:3 0x45fec6 55 push rbp
:3 0x45fec7 4889e5 mov rbp, rsp
=> :3 0x45feca* 4883ec28 sub rsp, 0x28
:4 0x45fece 48c744241801000000 mov qword ptr [rsp+0x18], 0x1
:4 0x45fed7 48c744241002000000 mov qword ptr [rsp+0x10], 0x2
:5 0x45fee0 b801000000 mov eax, 0x1
:5 0x45fee5 bb02000000 mov ebx, 0x2
:5 0x45feea e831000000 call $
:5 0x45feef 4889442420 mov qword ptr [rsp+0x20], rax
:5 0x45fef4 e8c749fdff call $
:5 0x45fef9 488b442420 mov rax, qword ptr [rsp+0x20]
regs
View register status:
(dlv) regs
Rip = 0x000000000045feca
Rsp = 0x000000c00003e758
Rbp = 0x000000c00003e758
...
Continue on to analyze the execution of the instruction:
-
sub rsp, 0x28
:rsp
memory address minus0x28
This means that the main function opens0x28
bytes of stack space. -
mov qword ptr [rsp+0x18], 0x1
cap (a poem)mov qword ptr [rsp+0x10], 0x2
: Will0x1
cap (a poem)0x2
Put them in the memory address[rsp+0x18]
cap (a poem)[rsp+0x10]
Center. -
mov eax, 0x1
cap (a poem)mov ebx, 0x2
: Will0x1
cap (a poem)0x2
Put them in the registers.eax
cap (a poem)ebx
Center.
jump to0x45feea
Instructions:
(dlv) b *0x45feea
Breakpoint 2 set at 0x45feea for () ./:5
(dlv) c
> () ./:5 (hits goroutine(1):1 total:1) (PC: 0x45feea)
1: package main
2:
3: func main() {
4: a, b := 1, 2
=> 5: println(sum(a, b))
6: }
7:
8: func sum(x, y int) int {
9: z := x + y
10: return z
(dlv) disass
Sending output to pager...
TEXT (SB) /root/go/src/foundation/ex6/
:3 0x45fec0 493b6610 cmp rsp, qword ptr [r14+0x10]
:3 0x45fec4 764f jbe 0x45ff15
:3 0x45fec6 55 push rbp
:3 0x45fec7 4889e5 mov rbp, rsp
:3 0x45feca* 4883ec28 sub rsp, 0x28
:4 0x45fece 48c744241801000000 mov qword ptr [rsp+0x18], 0x1
:4 0x45fed7 48c744241002000000 mov qword ptr [rsp+0x10], 0x2
:5 0x45fee0 b801000000 mov eax, 0x1
:5 0x45fee5 bb02000000 mov ebx, 0x2
=> :5 0x45feea* e831000000 call $
:5 0x45feef 4889442420 mov qword ptr [rsp+0x20], rax
:5 0x45fef4 e8c749fdff call $
:5 0x45fef9 488b442420 mov rax, qword ptr [rsp+0x20]
:5 0x45fefe 6690 data16 nop
in implementingcall $
Before, let's look at the memory distribution:
(the green part indicates the main function stack)
carry on withcall $
:
(dlv) si
> () ./:8 (PC: 0x45ff20)
TEXT (SB) /root/go/src/foundation/ex6/
=> :8 0x45ff20 55 push rbp
:8 0x45ff21 4889e5 mov rbp, rsp
:8 0x45ff24 4883ec10 sub rsp, 0x10
:8 0x45ff28 4889442420 mov qword ptr [rsp+0x20], rax
:8 0x45ff2d 48895c2428 mov qword ptr [rsp+0x28], rbx
:8 0x45ff32 48c7042400000000 mov qword ptr [rsp], 0x0
:9 0x45ff3a 4801d8 add rax, rbx
:9 0x45ff3d 4889442408 mov qword ptr [rsp+0x8], rax
:10 0x45ff42 48890424 mov qword ptr [rsp], rax
:10 0x45ff46* 4883c410 add rsp, 0x10
:10 0x45ff4a 5d pop rbp
:10 0x45ff4b c3 ret
(dlv) regs
Rip = 0x000000000045ff20
Rsp = 0x000000c00003e728
Rbp = 0x000000c00003e758
As you can see, the Rsp register is decremented by 8 bytes, and the stack opens up 8 bytes of space. Continue to analyze the instruction down the line:
-
push rbp
: Willrbp
The value of the register is stacked, and the address is stored in the rbp.0x000000c00003e758
. As a result of the stacking operation, here theRsp
will be decremented by 8 bytes. -
mov rbp, rsp
: give the value of the current rsp torbp
,rbp
because ofsum
The bottom of the function stack. -
sub rsp, 0x10
:rsp
drop down0X10
bytes, opening up 16 bytes of space for thesum
function stack, at which point thersp
The address of the0x000000c00003e710
, represents the top of the function stack.
Up to this point in the execution, we draw the memory distribution as follows:
Continue down the analysis:
-
mov qword ptr [rsp+0x20], rax
cap (a poem)mov qword ptr [rsp+0x28], rbx
: Separate therax
The value of register 1 is placed in the[rsp+0x20]:0x000000c00003e730
,rbx
The value of register 2 is placed in the[rsp+0x28]:0x000000c00003e738
。 -
mov qword ptr [rsp], 0x0
: put 0 to[rsp]
Center. -
add rax, rbx
: The values of rax and rbx are added together and the result is put into rax, and the value in rax is 3 after addition. -
mov qword ptr [rsp+0x8], rax
: put 3 to[rsp+0x8]
Center. -
mov qword ptr [rsp], rax
: put 3 to[rsp]
Center.
Based on the above analysis, the memory distribution is drawn as follows:
As you can see, the formal parameters x and y passed to sum are actually allocated on the main function stack.
Continue down the line:
-
add rsp, 0x10
:rsp
register plus0x10
recall (a defective product)sum
Stack space. -
pop rbp
: will be stored in the0x000000c00003e720
value of0x000000c00003e758
move torbp
Center. -
ret
:sum
function returns.
in implementingret
A final look at the status of the registers before the instruction:
(dlv) regs
Rip = 0x000000000045ff4b
Rsp = 0x000000c00003e728
Rbp = 0x000000c00003e758
We know.Rip
The register stores the memory address where the running instruction is located, so the question arises when the function returns to execute the next instruction of the calling function:
TEXT (SB) /root/go/src/foundation/ex6/
:5 0x45feea* e831000000 call $
:5 0x45feef 4889442420 mov qword ptr [rsp+0x20], rax
Here we need The next instruction executed after the return is
mov qword ptr [rsp+0x20], rax
. But.Rip
Command How to get the address where the command is located0x45feef
And?
The answer is in thecall $
Here, this instruction stacks the next instruction in thesum
function callret
On return, the instruction previously stacked is moved to theRip
in a register. The memory address of this stack is0x000000c00003e728
, to see what's in it:
(dlv) print *(*int)(uintptr(0x000000c00003e728))
4587247
4587247
The hexadecimal equivalent of0x45feef
。
fulfillmentret
:
(dlv) si
> () ./:5 (PC: 0x45feef)
:4 0x45fece 48c744241801000000 mov qword ptr [rsp+0x18], 0x1
:4 0x45fed7 48c744241002000000 mov qword ptr [rsp+0x10], 0x2
:5 0x45fee0 b801000000 mov eax, 0x1
:5 0x45fee5 bb02000000 mov ebx, 0x2
:5 0x45feea* e831000000 call $
=> :5 0x45feef 4889442420 mov qword ptr [rsp+0x20], rax
:5 0x45fef4 e8c749fdff call $
:5 0x45fef9 488b442420 mov rax, qword ptr [rsp+0x20]
:5 0x45fefe 6690 data16 nop
:5 0x45ff00 e85b50fdff call $
:5 0x45ff05 e8f64bfdff call $
(dlv) regs
Rip = 0x000000000045feef
Rsp = 0x000000c00003e730
Rbp = 0x000000c00003e758
can be seenRip
Pointed to the location of the next command.
Continue down the line:
-
mov qword ptr [rsp+0x20], rax
: put 3 to[rsp+0x20]
Middle.[rsp+0x20]
That's where it's stored.sum
The memory address of the function's return value. -
call $
: CallPrint the return value 3.
After analyzing the above process of calling a function we can draw the complete memory distribution of a function stack call as follows:
2. Summary
This article looks at the process of function calls from the assembly point of view, trying to achieve a more thorough understanding of function calls.