In the same Goroutine:

How does the call stack work for multiple deferred? How is the defer function called?

To explore the mystery, I 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)
}

Copy the code

Output: BBB 100 Hello AAA The output looks a lot like the stack data structure feature: LIFO (LIFO).

First of all from the assembly to see the execution process of xx() function, the command is 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 33 cdeccccebe80329f1fdbee7f5874cb (SB) 00033 (9). The main go: 0 x0021 FUNCDATAThe $1, gclocals 33 cdeccccebe80329f1fdbee7f5874cb (SB) 00033 (9). The main go: 0 x0021 FUNCDATA$39, gclocals fb7f0986f647f17cb53dda1484e0f7a (SB) 0 x0021 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.The $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	The $100, 16(SP)
	0x003d 00061 (main.go:10)	PCDATA	$2.The $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.The $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.The $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)
Copy the code

Find the arguments to aaa() and call the deferproc(SB) function:


 0x0021 00033 (main.go:10)   MOVL    $24, (SP)
 0x0028 00040 (main.go:10)   PCDATA  $2.The $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    The $100, 16(SP)
 0x003d 00061 (main.go:10)   PCDATA  $2.The $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)
Copy the code

The following is a unified description of the key code:

//1, (SP) puts 24 at the top of the stack (24 is the sum of the deferd parameter types described below). 0x0021 00033 (main.go:10) MOVL$24, (SP) //2, 8(SP) put aaa pointer into AX; Put the AAA function pointer into 8(SP). 0x0028 00040 (main.go:10) LEAQ"".aaa·f(SB), AX 0x002f 00047 (main.go:10) MOVQ AX, 8(SP) //3, 16(SP) put the first parameter of aaa into 16(SP). 0x0034 00052 (main.go:10) MOVQThe $100, 16(SP) //4, 24(SP) get the memory address of the second argument and assign it to AX; AX median 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 of 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)Copy the code

0(SP) = 24 //aaa(int, string) The sum of the parameter types

8(SP) = &aaa(int, string)//deferd pointer

16(SP) = 100// The first parameter value is 100

24(SP) = “hello aaa”// second argument

32(SP) = 9// The length of the second argument

As can be seen from the above two parts of assembly code, the function-related data is placed in SP and continuous. 2. Find out that 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
	ifgetg().m.curg ! =getg() {
		throw("defer on system stack")
	}
	sp := getcallersp()
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
	callerpc := getcallerpc()

	d := newdefer(siz)
	ifd._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()
}
Copy the code
deferproc(siz int32, fn *funcval)
Copy the code

It is found that the argument to this function is int32, *funcval. What do these two represent? We have GDB to track exactly what it means:

type stringStruct struct {
	str unsafe.Pointer
	len int
}
Copy the code

Unsafe.Pointer is 8 bytes, and int is 8 bytes. *funcval: The prototype of *funcval is as follows:

//runtime/runtime2.go

type funcval struct {
   fn uintptr
   // variable-size, fn-specific data here
}
Copy the code

Funcval is a struct whose member is a Fn uintptr, which is a pointer to a function.

BBB (int, string) is put into SP. The argument in func DeferProc (siz int32, fn * funcval) is that the run-time system takes siz and *fn from sp and calls DEFERProc (siz int32, fn * funcval).

Let’s use GDB to see what function fn refers to:

d := newdefer(siz)
Copy the code

Take a look at the prototype:

func newdefer(siz int32) *_defer
Copy the code

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
}
Copy the code

It’s a structure. We will first check the three parameters siz, FN and link. Other parameters will be explained below due to the limited space. Siz: The sum of bytes of the prototype length of the deferd function parameters.

Fn :deferd function pointer.

Link: What does it mean ??????

Take a look at the implementation of Newdefer (siz) with some questions:

{var d *_defer sc := uintptr(siz)) //ifSc < uintptr(len(p{}.deferpool)) {//iflen(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 the stack lock(& sched.deferLock) // Take some defer from the global deferpool and put it into p's local Deferpoolfor 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// 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.iz = siz // assign siz to D.link d.link = gp._defer // assign D to G._defer gP._defer = d return d }Copy the code

The above is the process of generating the defer. The general idea is to find the defer from the cache and create one if it doesn’t exist, and then assign size and link. Focus on the following code:

	d.link = gp._defer
	gp._defer = d
Copy the code

What they mean is that binding the just-generated defer to G._defer means putting the latest one on G._defer as the header. Then bind G._defer to D. Link, as shown below:

[current g]{_defer} => [new D1]{link} => [g]{old _defer}

If there is another newly generated defer(d2), the linked list is as follows:

G [current] {_defer} = > [new] d2 = > {link} {link} [new d1] = > [g] {old _defer}

Back in the deferproc(siz int32, fn *funcval) function, what does the second line above newdefer(siz) mean? :

argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
Copy the code

Continue tracing with GDB and find that argp is involved in this line, as shown in screenshot 2 below:

The first line in the red box in Figure 3 is 0x64, which is represented in base 10 as 100. Verify that this is the first argument to the AAA function, and the second line 0x4B9621 is a pointer to the second argument string, to see if it is as expected, as shown in Figure 4:

hello aaa

Continue tracing function execution:

 defer bbb("hello bbb")
Copy the code

BBB (string) is executed in the same way as aaa(int, string). The deferProc stack is finished and the return is run, 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() //d can be nil because the defer function can be nested, for example: //defer a -> defer b -> defer C // deferReturn is called at least once to defer all the items in the list.if d == nil {
		return
	}
	sp := getcallersp()
	ifd.sp ! = sp {return} // Copy the deferd function arguments to arg0 in preparation for calling the deferd function. switch d.siz {case 0:
		// Do nothing.
	caseSys. PtrSize:// If the size of siz is the size of a pointer, copy it directly as follows to reduce CPU processing. *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) default: memmove(unsafe.Pointer(&arg0), deferArgs(d), Uintptr (d.iz))} fn := d.fin // make a copy of d.fin = nil // set d.fin to empty Gp._defer = d.link// bind the next defer that is currently deferred to the linked header. Jmpdefer (fn, uintptr(unsafe.pointer (&arg0)))}Copy the code
fn := d.fn
d.fn = nil 
gp._defer = d.link
freedefer(d) 

Copy the code

Focus on the four lines above: bind the next defer in the linked list to gP._defer. Release the current defer. See the schematic below:

G [current] {_defer} = > [new] d2 = > {link} {link} [new d1] = > [g] {old _defer}

Run d2:

[current g]{_defer} => [new D1]{link} => [g]{old _defer}

Then take a look at the jMPdefer function below:

    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
Copy the code

This is where the defer function will be implemented. Before we look at its implementation, remember the deferReturn entry address in Figure 7, which we’ll talk about below.

The jmpdefer function is implemented in the following code:

The TEXT, the 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	A $5, (SP)	// return to CALL again
	MOVQ	0(DX), BX
	JMP	BX	// but first run the deferred function
Copy the code

Line by line:

    MOVQ    fv+0(FP), DX    // fn
Copy the code

Copy the function’s first fn pointer to DX so that subsequent code can take fn’s pointer from DX to execute the deferd function.

    MOVQ    argp+8(FP), BX  // caller sp
Copy the code

Copy the argp pointer to the second argument to BX, which is the address of the first argument in deferd.

    LEAQ    -8(BX), SP  // caller sp after CALL
Copy the code

BX stores the address of the first deferd parameter. Because GBD debugs BBB (string) at this time, the parameter is a string structure, a total of 16 bytes, the first 8 bytes are data Pointers, the last 8 bytes are length. What’s in -8(BX)? What’s in front of the BBB (string) parameter. After executing this instruction with GDB, see what the value of memory in SP (since assigned to SP) is, as shown in Figure 8.

What is 0x4872C6, a pointer? Try to see if it points to specific memory as shown in Figure 10 below

0x4872c6

0x4872c1 == rutime.deferreturn(SB)

0x4872C6 == next instruction address of rutime.DeferReturn (SB) (also called return address)

It turns out they’re five bytes apart. How the CPU finds the next instruction is determined by the number of bytes in the current instruction. Len (0x4872C6) -len (0x4872C1) == 5

call runtime.deferreturn(sb)
Copy the code

It takes 5 bytes, so 0x4872C1 +5 will get the first address of the next instruction.

Line 4:

    MOVQ    -8(SP), BP  // restore BP as if deferreturn returned (harmless if framepointers not in use)
Copy the code

Print the BP value = 0xC000032778 to see what the stack looks like, as shown in Figure 12

Line 5:

SUBQ  A $5, (SP)  # return to CALL again
Copy the code

As explained in line 3, SP points to a runtime.deferReturn (SB) directive entry if we subtract 5 from the runtime.deferReturn (SB). As shown in figure 13:

Line 6 and 7:

MOVQ    0(DX), BX
JMP BX  // but first run the deferred function
Copy the code

Assign the function instruction pointed to by DX to BX execute fn. Fn is BBB (string). Execute to BBB (string), as shown in Figure 14

add rsp, 0x70
ret

Continue with the following code in runtime.DeferReturn (SB) :

    if d == nil {
        return
    }
Copy the code

This if statement determines whether deferd is still on the deferred chain and returns it if it isn’t. So you don’t have to go through an infinite recursive loop. There are a few more lines of code:

sp := getcallersp()
    ifd.sp ! = sp {return
    }
Copy the code

If you’re interested, you can try to see why it’s written here, but I’m not going to study this code here because I don’t have enough time.

This article mainly explains the implementation process of defer. Due to the length, I will explain the exploration of panic, Recover and error-prone defer statements in the next article. Please look forward to it

Refer to defer — in Golang, the core programming technology of the GO language