Location>code7788 >text

Go plan9 assembly: talking through the function stack

Popularity:874 ℃/2024-09-02 00:41:14

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 the0x10x2 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:

  1. sub rsp, 0x28: rsp memory address minus0x28This means that the main function opens0x28 bytes of stack space.
  2. 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.
  3. 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:

image

(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:

  1. 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.
  2. mov rbp, rsp: give the value of the current rsp torbprbp because ofsum The bottom of the function stack.
  3. sub rsp, 0x10rsp 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:

image

Continue down the analysis:

  1. 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]:0x000000c00003e730rbx The value of register 2 is placed in the[rsp+0x28]:0x000000c00003e738
  2. mov qword ptr [rsp], 0x0: put 0 to[rsp] Center.
  3. 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.
  4. mov qword ptr [rsp+0x8], rax: put 3 to[rsp+0x8] Center.
  5. mov qword ptr [rsp], rax: put 3 to[rsp] Center.

Based on the above analysis, the memory distribution is drawn as follows:

image

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:

  1. add rsp, 0x10rsp register plus0x10 recall (a defective product)sum Stack space.
  2. pop rbp: will be stored in the0x000000c00003e720 value of0x000000c00003e758 move torbp Center.
  3. retsum 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 ismov 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:

  1. 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.
  2. call $: Call Print 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:

image

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.