• Go Function Calls Redux
  • By Phil Pearl
  • The Nuggets translation Project
  • Translator: xiaoyusilen
  • Proofreader: 1992chenlu, Zheaoli

A while back in an article I promised to write a further analysis of how function calls and stack calls work in Go. Now I’ve found a succinct way to show you that, hence this article.

What is a stack call? It is an area of memory that holds local variables and call parameters, and keeps track of where each function should go back. Each Goroutine has its own stack. You could even say that each Goroutine is its own stack.

Here is the code I used to demonstrate the stack. The main() function calls F1 (0xdeadbeef), then f2(0xAbad1dea), then F3 (0xBaddCafe). F3 () then takes one of these as its argument and stores it in a local variable named local. It then gets the memory address of local and prints from there. Because local is in the stack, the output is the stack.

package main

import (
    "fmt"
    "runtime"
    "unsafe"
)

func main(a) {
    f1(0xdeadbeef)}func f1(val int) {
    f2(0xabad1dea)
}

func f2(val int) {
    f3(0xbaddcafe)
}

func f3(val int) {
    local := val + 1

    display(uintptr(unsafe.Pointer(&local)))
}

func display(ptr uintptr) {
    mem := *(*[20]uintptr)(unsafe.Pointer(ptr))
    for i, x := range mem {
        fmt.Printf("%X: %X\n", ptr+uintptr(i*8), x)
    }

    showFunc(mem[2])
    showFunc(mem[5])
    showFunc(mem[8])
    showFunc(mem[11])}func showFunc(at uintptr) {
    iff := runtime.FuncForPC(at); f ! =nil {
        file, line := f.FileLine(at)
        fmt.Printf("%X is %s %s %d\n", at, f.Name(), file, line)
    }
}Copy the code

Here is the output of the code above. It is a memory dump starting at the local address, and is presented in hexadecimal format as an 8-byte list. The left side is the storage address of each integer, and the right side is the integer stored within the address.

We know that local should be equal to 0xBADDCAFE + 1, or 0xBADDCAFF, which is exactly what we saw at the beginning of the dump.

C42003FF28: BADDCAFF
C42003FF30: C42003FF48
C42003FF38: 1088BEB
C42003FF40: BADDCAFE
C42003FF48: C42003FF60
C42003FF50: 1088BAB
C42003FF58: ABAD1DEA
C42003FF60: C42003FF78
C42003FF68: 1088B6B
C42003FF70: DEADBEEF
C42003FF78: C42003FFD0
C42003FF80: 102752A
C42003FF88: C420064000
C42003FF90: 0
C42003FF98: C420064000
C42003FFA0: 0
C42003FFA8: 0
C42003FFB0: 0
C42003FFB8: 0
C42003FFC0: C4200001A0

1088BEB is main.f2 /Users/phil/go/src/github.com/philpearl/stack/main.go 19

1088BAB is main.f1 /Users/phil/go/src/github.com/philpearl/stack/main.go 15

1088B6B is main.main /Users/phil/go/src/github.com/philpearl/stack/main.go 11

102752A is runtime.main /usr/local/Cellar/go/1.8/libexec/src/runtime/proc.go 194Copy the code
  • The next number is 0xC42003FF48, which is the address of the fifth row of the dump.
  • And then we get 0x1088BEB. This is actually the address of the executable code if we take it asruntime.FuncForPC“, which we know is the address of line 19 of main.go and the last line of f2(). This is the address we got when f3() returned.
  • And then we get 0xBADDCAFE, which is what we callf3()When.

If we continue we will see output similar to the above. Below I have marked the memory dump, showing how the stack pointer tracks the dump, the parameters, and where the return address is.

  C42003FF28: BADDCAFF    Local variable in f3()
+-C42003FF30: C42003FF48 
| C42003FF38: 1088BEB     return to f2() main.go line 19
| C42003FF40: BADDCAFE    f3() parameter
+-C42003FF48: C42003FF60
| C42003FF50: 1088BAB     return to f1() main.go line 15
| C42003FF58: ABAD1DEA    f2() parameter
+-C42003FF60: C42003FF78
| C42003FF68: 1088B6B     return to main() main.go line 11
| C42003FF70: DEADBEEF    f1() parameter
+-C42003FF78: C42003FFD0
  C42003FF80: 102752A     return to runtime.main()Copy the code

From these we can see:

  • First, the stack starts at a high address, which gets smaller with the function call.
  • When a function call is made, the caller puts the arguments on the stack, followed by the return address (the address to call the next instruction in the function), followed by a pointer to the higher part of the stack.
  • When the call returns, this pointer is used to look up the previously called function in the stack.
  • Local variables are stored after the stack pointer.

We can use the same technique to analyze some slightly more complex function calls. This time, I added more arguments, and the f2() function returned more values.

package main

import (
    "fmt"
    "runtime"
    "unsafe"
)

func main(a) {
    f1(0xdeadbeef)}func f1(val int) {
    f2(0xabad1dea0001, 0xabad1dea0002)
}

func f2(val1, val2 int) (r1, r2 int) {
    f3(0xbaddcafe)
    return
}

func f3(val int) {
    local := val + 1

    display(uintptr(unsafe.Pointer(&local)))
}Copy the code

This time let’s go straight to the output that I’ve marked.

  C42003FF10: BADDCAFF      local variable in f3()
+-C42003FF18: C42003FF30
| C42003FF20: 1088BFB       return to f2()
| C42003FF28: BADDCAFE      f3() parameter
+-C42003FF30: C42003FF60
| C42003FF38: 1088BBF       return to f1()
| C42003FF40: ABAD1DEA0001  f2() first parameter
| C42003FF48: ABAD1DEA0002  f2() second parameter
| C42003FF50: 110A100       space for f2() return value
| C42003FF58: C42000E240    space for f2() return value
+-C42003FF60: C42003FF78
| C42003FF68: 1088B6B       return to main()
| C42003FF70: DEADBEEF      f1() parameter
+-C42003FF78: C42003FFD0
  C42003FF80: 102752A       return to runtime.main()Copy the code

From the results, we can see:

  • Calling a function provides space for the return value of the called function before the function argument. (Note that these values are not initialized because this function has not returned yet!)
  • Arguments are pushed in the reverse order.

I hope I made that clear. Now that you’ve seen it, please give me a thumbs up if you like my article or can learn something from it. Otherwise I won’t get the points.

Phil in during the dayravelin.comYour job is to prevent online fraud and you can join himAngel. Co/ravelin/job…

The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. Android, iOS, React, front end, back end, product, design, etc. Keep an eye on the Nuggets Translation project for more quality translations.