In the same goroutine:
What is the call stack principle for multiple defer s?
How is the defer function called?
To explore the mystery, I've prepared the following code:
package main import "fmt" func main() { xx() } func xx() { defer aaa(100, "hello aaa") defer bbb("hello bbb") return } func aaa(x int, arg string) { fmt.Println(x, arg) } func bbb(arg string) { fmt.Println(arg) }
Output:
bbb
100 hello aaa
The output looks much like the data structure of a stack: LIFO.
Start with assembly to see how the xx() function is executed. The commands are as follows:
go tool compile -S main.go >> main.s
"".xx STEXT size=198 args=0x0 locals=0x30 0x0000 00000 (main.go:9) TEXT "".xx(SB), ABIInternal, $48-0 0x0000 00000 (main.go:9) MOVQ (TLS), CX 0x0009 00009 (main.go:9) CMPQ SP, 16(CX) 0x000d 00013 (main.go:9) JLS 188 0x0013 00019 (main.go:9) SUBQ $48, SP 0x0017 00023 (main.go:9) MOVQ BP, 40(SP) 0x001c 00028 (main.go:9) LEAQ 40(SP), BP 0x0021 00033 (main.go:9) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0021 00033 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0021 00033 (main.go:9) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB) 0x0021 00033 (main.go:10) PCDATA $2, $0 0x0021 00033 (main.go:10) PCDATA $0, $0 0x0021 00033 (main.go:10) MOVL $24, (SP) 0x0028 00040 (main.go:10) PCDATA $2, $1 0x0028 00040 (main.go:10) LEAQ "".aaa·f(SB), AX 0x002f 00047 (main.go:10) PCDATA $2, $0 0x002f 00047 (main.go:10) MOVQ AX, 8(SP) 0x0034 00052 (main.go:10) MOVQ $100, 16(SP) 0x003d 00061 (main.go:10) PCDATA $2, $1 0x003d 00061 (main.go:10) LEAQ go.string."hello aaa"(SB), AX 0x0044 00068 (main.go:10) PCDATA $2, $0 0x0044 00068 (main.go:10) MOVQ AX, 24(SP) 0x0049 00073 (main.go:10) MOVQ $9, 32(SP) 0x0052 00082 (main.go:10) CALL runtime.deferproc(SB) 0x0057 00087 (main.go:10) TESTL AX, AX 0x0059 00089 (main.go:10) JNE 172 0x005b 00091 (main.go:11) MOVL $16, (SP) 0x0062 00098 (main.go:11) PCDATA $2, $1 0x0062 00098 (main.go:11) LEAQ "".bbb·f(SB), AX 0x0069 00105 (main.go:11) PCDATA $2, $0 0x0069 00105 (main.go:11) MOVQ AX, 8(SP) 0x006e 00110 (main.go:11) PCDATA $2, $1 0x006e 00110 (main.go:11) LEAQ go.string."hello bbb"(SB), AX 0x0075 00117 (main.go:11) PCDATA $2, $0 0x0075 00117 (main.go:11) MOVQ AX, 16(SP) 0x007a 00122 (main.go:11) MOVQ $9, 24(SP) 0x0083 00131 (main.go:11) CALL runtime.deferproc(SB) 0x0088 00136 (main.go:11) TESTL AX, AX 0x008a 00138 (main.go:11) JNE 156 0x008c 00140 (main.go:12) XCHGL AX, AX 0x008d 00141 (main.go:12) CALL runtime.deferreturn(SB)
Find the parameters of the aaa() function and call function deferproc(SB):
0x0021 00033 (main.go:10) MOVL $24, (SP) 0x0028 00040 (main.go:10) PCDATA $2, $1 0x0028 00040 (main.go:10) LEAQ "".aaa·f(SB), AX 0x002f 00047 (main.go:10) PCDATA $2, $0 0x002f 00047 (main.go:10) MOVQ AX, 8(SP) 0x0034 00052 (main.go:10) MOVQ $100, 16(SP) 0x003d 00061 (main.go:10) PCDATA $2, $1 0x003d 00061 (main.go:10) LEAQ go.string."hello aaa"(SB), AX 0x0044 00068 (main.go:10) PCDATA $2, $0 0x0044 00068 (main.go:10) MOVQ AX, 24(SP) 0x0049 00073 (main.go:10) MOVQ $9, 32(SP) 0x0052 00082 (main.go:10) CALL runtime.deferproc(SB)
Unified description of the above key codes:
//1, (SP) puts 24 on the top of the stack (24 is actually the sum of the length of the deferd function parameter type referred to below). 0x0021 00033 (main.go:10) MOVL $24, (SP) //2, 8(SP) places the aaa function pointer in AX; the aaa function pointer in 8(SP). 0x0028 00040 (main.go:10) LEAQ "".aaa·f(SB), AX 0x002f 00047 (main.go:10) MOVQ AX, 8(SP) //3, 16(SP) places the first parameter 100 of function aaa into 16(SP). 0x0034 00052 (main.go:10) MOVQ $100, 16(SP) //4, 24(SP) takes the memory address of the second parameter and assigns it to AX; the AX median is assigned to 24(SP). 0x003d 00061 (main.go:10) LEAQ go.string."hello aaa"(SB), AX 0x0044 00068 (main.go:10) MOVQ AX, 24(SP) //5,32(SP), assign the second parameter string length 9 to 32(SP). 0x0049 00073 (main.go:10) MOVQ $9, 32(SP) //Call runtime.deferproc(SB) 0x0052 00082 (main.go:10) CALL runtime.deferproc(SB)
0(SP) = 24 //aaa(int, string) parameter type length and
8 (SP) = &aaa (int, string)//deferd function pointer
16 (SP) = 100// First parameter value 100
24(SP) = "hello aaa" //second parameter
32(SP) = 9//second parameter string length
From the above two parts of the assembly code, you can see that the function-related data is placed in SP and is continuous.2, Discover
The defer aaa(int, string) compiler inserts the deferproc(SB) function.
Take a look at the source code:
//runtime/panic.go func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn if getg().m.curg != getg() { throw("defer on system stack") } sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc() d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.fn = fn d.pc = callerpc d.sp = sp switch siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) } return0() }
deferproc(siz int32, fn *funcval)
The parameter found for this function is int32, *funcval.What do they both represent?We have a gdb to track what it means:
siz=0x18 means siz=24.The aaa(int, string) parameter int takes up 8 bytes and the string 16 bytes.Why do string types account for 16 bytes?
Because the prototype of string type is:
type stringStruct struct { str unsafe.Pointer len int }
unsafe.Pointer8 bytes and int 8 bytes.
Explanation of specific strings to see my previous articles string, encoding in golang
Next *funcval: Its prototype is as follows:
//runtime/runtime2.go type funcval struct { fn uintptr // variable-size, fn-specific data here }
funcval is a struct, and its members are fn uintptr, which is a pointer to a function by guessing what fn means literally.
As mentioned earlier, data about the bbb(int, string) function is put into SP. The parameter in func deferproc (siz int32, fn funcval) is that the runtime system takes siz and fn from SP and then calls deferproc(siz int32, fn * funcval).
Let's use gdb to see what exactly the function fn points to here is:
The original d.fn.fn was the specific instruction of the aaa(int, string) function.
What does d mean, Track Discovery:
d := newdefer(siz)
Take a look at its prototype:
func newdefer(siz int32) *_defer
Its return value is *_defer, take a look at its definition:
//runtime/runtime2.go type _defer struct { siz int32 started bool sp uintptr // sp at time of defer pc uintptr fn *funcval _panic *_panic // panic that is running defer link *_defer }
It is a structure.Let's first look at the three parameters siz, fn, link. The other parameters are explained below due to limited space.
The sum of the byte lengths of the siz:deferd function parameter prototype.
fn:deferd function pointer.
link: What does it mean?????
Take a look at the implementation of newdefer(siz) with questions:
func newdefer(siz int32) *_defer { var d *_defer sc := deferclass(uintptr(siz)) // g-struct object of current goroutine gp := getg() if sc < uintptr(len(p{}.deferpool)) { //p of the current goroutine binding pp := gp.m.p.ptr() if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { // Take the slow path on the system stack so // we don't grow newdefer's stack. systemstack(func() {//Switch to System Stack lock(&sched.deferlock) //Get some defers from the global deferpool and place them in the local deferpool of p for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) } unlock(&sched.deferlock) }) } if n := len(pp.deferpool[sc]); n > 0 { d = pp.deferpool[sc][n-1] pp.deferpool[sc][n-1] = nil pp.deferpool[sc] = pp.deferpool[sc][:n-1] } } if d == nil {//defer not created in cache // Allocate new defer+args. systemstack(func() { total := roundupsize(totaldefersize(uintptr(siz))) d = (*_defer)(mallocgc(total, deferType, true)) }) if debugCachedWork { // Duplicate the tail below so if there's a // crash in checkPut we can tell if d was just // allocated or came from the pool. d.siz = siz d.link = gp._defer gp._defer = d return d } } d.siz = siz //Assigning siz //Assign g's_defer to d.link d.link = gp._defer //d is assigned to g._defer gp._defer = d return d }
This is the defer generation process, which basically means finding a defer from the cache and creating one if it does not, then assigning size, link.
Focus on the following code:
d.link = gp._defer gp._defer = d
The above two lines of code implementation have already been explained, which is explained in more detail here:
By binding the defer just generated to g._defer, you are putting the latest defer on
g._defer as the chain header.Then bind g._defer to d.link, as illustrated below:
[current g]{defer} => [new d1]{link} => [g]{old_defer}
If there is another newly generated defer(d2), the list of chains is as follows:
[current g]{defer} => [new d2]{link} => [new d1]{link} => [g]{old_defer}
Returning to the deferproc(siz int32, fn *funcval) function, what does the second line above newdefer(siz) mean?:
argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
Continuing to track with gdb, we find that argp is involved in this line, as shown in Figure 2 below:
The memmove function was found, and it acts as a copy.Copy siz(24 bytes here) bytes from the argp position to the back of the d structure.
Run this line to see what data is copied to the back of the d structure?See Figure 3:
The first line in the red box in Figure 3 is 0x64 and its 10-digit representation is 100.This is proved to be the first parameter of the aaa function. Similarly, the second line 0x4b9621 is a pointer to the second parameter string to see if it is as expected, as shown in Figure 4:
The figure above is a 10-digit representation that makes it easy to find the corresponding character in ascii, and from the ASCII table you can see that it is really the second parameter of the AAA function, hello aaa.So I draw the conclusion that the parameter of the deferd function is behind the deferd structure.The third line represents the length of the string.That is, the second and third lines represent the values of the string prototype (struct).
Continue tracking function execution:
defer bbb("hello bbb")
The execution of bbb(string) is the same as that of the aaa(int, string) function above, and the demonstration is not repeated here.
Run the return after the deferproc stack is executed, as shown in Figure 5:
Then press s to enter the return implementation (to the deferreturn stack), as shown in Figure 6 below:
Take a look at its implementation:
//rutime/painc.go //go:nosplit func deferreturn(arg0 uintptr) { gp := getg() //Get the current g d := gp._defer //Get the _defer chain header for the current g //Why can d be nil because defer functions can be nested, for example: // defer a -> defer b -> defer c //The deferreturn function is called at least once, returning all defers in the list directly after execution. if d == nil { return } sp := getcallersp() if d.sp != sp { return } //Copy the deferd function parameter to arg0 in preparation for calling the deferd function. switch d.siz { case 0: // Do nothing. case sys.PtrSize://If the size of the siz is the pointer size, it is copied directly as follows to reduce the cpu operation. *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) default: memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) } fn := d.fn //Copy d.fn d.fn = nil //Set d.fn to null gp._defer = d.link//Bind the next defer of the current defer to the chain header. freedefer(d) //Release d //fn is the deferd function, the second parameter is the deferd function's parameter jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) }
fn := d.fn d.fn = nil gp._defer = d.link freedefer(d)
Explain the four lines above: Bind the next defer in the list to gp._defer.Release the current defer.See the following diagram:
[current g]{defer} => [new d2]{link} => [new d1]{link} => [g]{old_defer}
Run d2:
[current g]{defer} => [new d1]{link} => [g]{old_defer}
Then take a look at the following jmpdefer function:
jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
This function is where the defer function is executed. Before we see it implemented, remember the deferreturn entry address in Figure 7 below. This address will be described below.
The jmpdefer function implementation is shown in the following code:
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16 MOVQ fv+0(FP), DX // fn MOVQ argp+8(FP), BX // caller sp LEAQ -8(BX), SP // caller sp after CALL MOVQ -8(SP), BP // restore BP as if deferreturn returned (harmless if framepointers not in use) SUBQ $5, (SP) // return to CALL again MOVQ 0(DX), BX JMP BX // but first run the deferred function
Explain line by line:
MOVQ fv+0(FP), DX // fn
Copy the fn pointer, the first parameter of the function, to DX so that subsequent code can execute the deferd function by taking the fn pointer from DX.
MOVQ argp+8(FP), BX // caller sp
Copy the second parameter of the function, argp pointer, to BX, which is the address of the first parameter of the deferd function.
LEAQ -8(BX), SP // caller sp after CALL
From the second instruction above, you can see that BX stores the address of the first parameter of the deferd function.Because gbd is debugging the bbb(string) function at this time, the parameter is a string structure, accounting for 16 bytes in total, the first 8 bytes are data pointers, and the last 8 are lengths.What's in -8(BX), that is, what's the East (bottom) before the bbb(string) parameter value.Follow this command with gdb to see what the memory value in SP (because it is assigned to SP) is, as shown in Figure 8.
The first line is the -8(BX) we want to determine
The second line is a parameter in bbb(string), which is a string pointer in the string structure and points to a specific string.
The third line is the length of the string, which is 9 here.
Let's look at the stack as shown in Figure 9:
What is 0x4872c6, pointer?Try to see if it can point to specific memory as shown in Figure 10 below
Originally the main.xx directive.Remember Figure 7 just now, I'm going to cut off Figure 7, see Figure 11:
The next line at the red line is 0x4872c6, which is the same value as Figure 10.According to Figure 11, this address is the next instruction of rutime.deferreturn(SB), that is, the return address of rutime.deferreturn(SB).
Look closely at these two addresses:
0x4872c1 == rutime.deferreturn(SB)
0x4872c6 == rutime.deferreturn(SB) next instruction address (also called return address)
They were found to be five bytes apart.According to assembly knowledge, how does the cpu find the next instruction is determined by the number of bytes occupied by the current instruction.
len(0x4872c6) - len(0x4872c1) == 5 knowable
call runtime.deferreturn(sb)
It takes up five bytes, so 0x4872c1+5 gets the next instruction header address.
Line 4:
MOVQ -8(SP), BP // restore BP as if deferreturn returned (harmless if framepointers not in use)
Print BP value = 0xc000032778
Take a look at the stack, see Figure 12
The current stack is already main.xx.
Line 5:
SUBQ $5, (SP) # return to CALL again
From the explanation in line 3, if the data pointed at by SP (the return address of runtime.deferreturn) is minus 5, it is exactly the instruction entry for runtime.deferreturn(SB).See Figure 13:
Lines 6, 7:
MOVQ 0(DX), BX JMP BX // but first run the deferred function
Assign function instructions pointed to by DX to BX
Execute fn.fn, also known as bbb(string).
Execute to bbb(string), see Figure 14
rsp now moves 0x70 bytes to the low address.
Break the end of the bbb(string) and execute there as shown in Figure 15:
In Figure 14, the SP moves to the low address by 0x70.
In Figure 15, the SP moves to the high address by 0x70.
That is, the SP will return to its previous pointing state.Where did the previous SP point to?This is the runtime.deferreturn(SB) entry shown in Figure 13.
In Figure 15, add rsp, the next line of instruction 0x70 is a ret instruction.This function is not available in the bbb(string) function, which was added by the compiler to pop the eight bytes at the top of the current stack into the rip register so that the cpu executes the instructions in the rip into the runtime.deferreturn(SB), which implements a recursive call to deferreturn(SB).This in turn completes execution on the deferd chain.
Continue to runtime.deferreturn(SB)
The following code:
if d == nil { return }
This if statement is to determine if there is a deferd function on the defer chain, if not, it returns directly.This avoids an infinite recursive loop.
There are a few more lines of code in it:
sp := getcallersp() if d.sp != sp { return }
Interested buddies can try to see why this is so, and because of time constraints, research on this code is not going on here.
This article is mainly about the execution of defer. For space reasons, I'll explore panic, recover, and error-prone defer sentences in the next article. Please look forward to ~
Reference resources
Defer--go Language Core Programming Technology
defer in golang