Dive into stack and defer/panic/recover in go

| 分类 编程语言  | 标签 golang 

Contiguous stacks

在C语言中,每个线程的栈的大小是固定不变的。Go语言中的每个goroutine都有一个栈。如果栈的大小固定,太小容易导致溢出,太大又容易浪费空间(goroutine的数量可能很多)。为此,Go引入了按需分配策略,在开始时分配很小的空间(8192字节),并且支持按需增长。Go1.3之后的版本都使用新的Contiguous stacks这篇文章详细讨论了为什么使用Contiguous stacks


func f1(){
func main(){
(gdb) disass 
Dump of assembler code for function main.main:
   0x0000000000002140 <+0>:	mov    %gs:0x8a0,%rcx
=> 0x0000000000002149 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000000214d <+13>:	jbe    0x2155 <main.main+21> //如果rsp <= 0x10(%rcx),则调用runtime.morestack_noctxt
   0x000000000000214f <+15>:	callq  0x2040 <main.f1>
   0x0000000000002154 <+20>:	retq   
   0x0000000000002155 <+21>:	callq  0x52d10 <runtime.morestack_noctxt>
   0x000000000000215a <+26>:	jmp    0x2140 <main.main>
   0x000000000000215c <+28>:	add    %al,(%rax)
   0x000000000000215e <+30>:	add    %al,(%rax)

(gdb) disass
Dump of assembler code for function main.f1:
=> 0x0000000000002040 <+0>:	mov    %gs:0x8a0,%rcx
   0x0000000000002049 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000000204d <+13>:	jbe    0x212c <main.f1+236>
   0x000000000000212c <+236>:	callq  0x52d10 <runtime.morestack_noctxt>
   0x0000000000002131 <+241>:	jmpq   0x2040 <main.f1>

alloc stack


struct	G
	// stackguard0 can be set to StackPreempt as opposed to stackguard
	uintptr	stackguard0;	// cannot move - also known to linker, libmach, runtime/cgo
	uintptr	stackbase;	// cannot move - also known to libmach, runtime/cgo
	uintptr	stackguard;	// same as stackguard0, but not set to StackPreempt
	uintptr	stack0;
	uintptr	stacksize;


stack0          stackguard (stackguard0)              stackbase 
| StackGuard    | STACK                               | Stktop        |
low          <--------------------------------------- SP              high


// runtime/proc.c
runtime·newproc1(FuncVal *fn, byte *argp, int32 narg, int32 nret, void *callerpc)
	if((newg = gfget(p)) != nil) {
		if(newg->stackguard - StackGuard != newg->stack0)
			runtime·throw("invalid stack in newg");
	} else {
		newg = runtime·malg(StackMin); //8192

// Allocate a new g, with a stack big enough for stacksize bytes.
runtime·malg(int32 stacksize)
	G *newg;
	byte *stk;
	newg = allocg();
	if(stacksize >= 0) {
		stacksize = runtime·round2(StackSystem + stacksize);
		if(g == m->g0) {
			// running on scheduler stack already.
			stk = runtime·stackalloc(newg, stacksize);
		} else {
			// have to call stackalloc on scheduler stack.
			newg->stacksize = stacksize;
			g->param = newg;
			stk = g->param;
			g->param = nil;
		newg->stack0 = (uintptr)stk;
		newg->stackguard = (uintptr)stk + StackGuard;
		newg->stackguard0 = newg->stackguard;
		newg->stackbase = (uintptr)stk + stacksize - sizeof(Stktop);
	return newg;



/* runtime/asm_amd64.s
 * support for morestack

// Called during function prolog when more stack is needed.
// Caller has already done get_tls(CX); MOVQ m(CX), BX.
// The traceback routines see morestack on a g0 as being
// the top of a stack (for example, morestack calling newstack
// calling the scheduler calling newm calling gc), so we must
// record an argument size. For that purpose, it has no arguments.
TEXT runtime·morestack(SB),NOSPLIT,$0-0
	// Cannot grow scheduler stack (m->g0).
	MOVQ	m_g0(BX), SI
	JNE	2(PC)
	INT	$3
	// Call newstack on m->g0's stack.
	MOVQ	m_g0(BX), BP
	MOVQ	(g_sched+gobuf_sp)(BP), SP
	CALL	runtime·newstack(SB)
	MOVQ	$0, 0x1003	// crash if newstack returns


// Called from runtime·newstackcall or from runtime·morestack when a new
// stack segment is needed.  Allocate a new stack big enough for
// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
// and then act as though runtime·lessstack called the function at
// m->morepc.
	// If every frame on the top segment is copyable, allocate a bigger segment
	// and move the segment instead of allocating a new segment.
	if(runtime·copystack) {
			runtime·throw("can't copy stacks without precise stacks");
		nframes = copyabletopsegment(gp);
		if(nframes != -1) {
			oldstk = (byte*)gp->stackguard - StackGuard;
			oldbase = (byte*)gp->stackbase + sizeof(Stktop);
			oldsize = oldbase - oldstk;
			newsize = oldsize * 2; ///增加为原来的2倍大小
			copystack(gp, nframes, newsize); ///拷贝栈
			if(StackDebug >= 1)
				runtime·printf("stack grow done\n");
			if(gp->stacksize > runtime·maxstacksize) {
				runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize);
				runtime·throw("stack overflow");
			gp->status = oldstatus;
			runtime·gogo(&gp->sched); ///重新执行G
		// TODO: if stack is uncopyable because we're in C code, patch return value at
		// end of C code to trigger a copy as soon as C code exits.  That way, we'll
		// have stack available if we get this deep again.



package main
import "sync"
var lock sync.Mutex
func test() {
    defer lock.Unlock()
func main() {
(gdb) disass
Dump of assembler code for function main.test:
   0x0000000000002040 <+0>:	mov    %gs:0x8a0,%rcx
   0x0000000000002049 <+9>:	cmp    0x10(%rcx),%rsp
   0x000000000000204d <+13>:	jbe    0x20a2 <main.test+98>
   0x000000000000204f <+15>:	sub    $0x18,%rsp
=> 0x0000000000002053 <+19>:	lea    0xd3266(%rip),%rbx        # 0xd52c0 <main.lock>
   0x000000000000205a <+26>:	mov    %rbx,(%rsp)
   0x000000000000205e <+30>:	callq  0x4e640 <sync.(*Mutex).Lock>
   0x0000000000002063 <+35>:	lea    0xd3256(%rip),%rbx        # 0xd52c0 <main.lock>
   0x000000000000206a <+42>:	mov    %rbx,0x10(%rsp)
   0x000000000000206f <+47>:	movl   $0x8,(%rsp) # 参数size入栈
   0x0000000000002076 <+54>:	lea    0x86403(%rip),%rax        # 0x88480 <sync.(*Mutex).Unlock.f>
   0x000000000000207d <+61>:	mov    %rax,0x8(%rsp) ## Unlock入栈
   0x0000000000002082 <+66>:	callq  0x207f0 <runtime.deferproc>
   0x0000000000002087 <+71>:	cmp    $0x0,%eax
   0x000000000000208a <+74>:	jne    0x2097 <main.test+87>
   0x000000000000208c <+76>:	nop
   0x000000000000208d <+77>:	callq  0x21970 <runtime.deferreturn>
   0x0000000000002092 <+82>:	add    $0x18,%rsp
   0x0000000000002096 <+86>:	retq   


  • runtime·deferproc


 * deferred subroutine calls
struct Defer
	int32	siz;
	bool	special;	// not part of defer frame
	byte*	argp;		// where args were copied from
	byte*	pc;
	FuncVal*	fn;
	Defer*	link;
	void*	args[1];	// padded to actual size

// runtime/panic.c
runtime·deferproc(int32 siz, FuncVal *fn, ...)
	Defer *d;
	// 创建Defer对象,加到G.defer链表
	d = newdefer(siz);
	d->fn = fn;
	d->pc = runtime·getcallerpc(&siz);

	/// fn的第一个参数地址,这个地址为调用者(即main.test)在调用deferproc之前的栈指针SP
	if(thechar == '5')
		d->argp = (byte*)(&fn+2);  // skip caller's saved link register
		d->argp = (byte*)(&fn+1);
	runtime·memmove(d->args, d->argp, d->siz);

	// deferproc returns 0 normally.
	// a deferred func that stops a panic
	// makes the deferproc return 1.
	// the code the compiler generates always
	// checks the return value and jumps to the
	// end of the function if deferproc returns != 0.
	return 0;


  • runtime·deferproc
// Run a deferred function if there is one.
// The compiler inserts a call to this at the end of any
// function which calls defer.
// If there is a deferred function, this will call runtime·jmpdefer,
// which will jump to the deferred function such that it appears
// to have been called by the caller of deferreturn at the point
// just before deferreturn was called.  The effect is that deferreturn
// is called again and again until there are no more deferred functions.
// Cannot split the stack because we reuse the caller's frame to
// call the deferred function.

// The single argument isn't actually used - it just has its address
// taken so it can be matched against pending defers.
#pragma textflag NOSPLIT
runtime·deferreturn(uintptr arg0)
	Defer *d;
	byte *argp;
	FuncVal *fn;

	d = g->defer;
	if(d == nil)
	argp = (byte*)&arg0; ///第一个参数的地址

	// d->argp为调用者(即main.test)在调用deferproc之前的栈指针SP,通过比较这两个地址,就可以确定是否是同
	// 一个调用函数(即main.test)的栈。如果不同,说明Defer不属于当前调用函数,从而中断deferreturn的循环调用
	if(d->argp != argp)

	// Moving arguments around.
	// Do not allow preemption here, because the garbage collector
	// won't know the form of the arguments until the jmpdefer can
	// flip the PC over to fn.
	runtime·memmove(argp, d->args, d->siz);
	fn = d->fn;
	g->defer = d->link;
	freedefer(d); /// 释放Defer对象
	if(m->locks == 0 && g->preempt)
		g->stackguard0 = StackPreempt;

	// 执行defer.fn
	runtime·jmpdefer(fn, argp);


// void jmpdefer(fn, sp);
// called from deferreturn.
// 1. pop the caller
// 2. sub 5 bytes from the callers return
// 3. jmp to the argument
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
	MOVQ	8(SP), DX	// fn
	MOVQ	16(SP), BX	// caller sp, 参数argp,也就是deferreturn的arg0的地址
	LEAQ	-8(BX), SP	// caller sp after CALL
	SUBQ	$5, (SP)	// return to CALL again
	JMP	BX	// but first run the deferred function, 调用defer函数

BX是deferreturn的arg0的地址,该参数保存在caller(main.test)调用runtime·deferreturn前的栈顶。而-8(BX)保存的刚好是runtime·deferreturn执行完后的返回地址(CALL会执行PUSH IP),即main.test的0x2092。SUBQ $5, (SP)减掉5使得保存在SP的返回地址刚好减掉了指令callq 0x21970 <runtime.deferreturn>的长度,即0x208d。至此,在执行JMP BX前,SP的值为指令callq 0x21970 <runtime.deferreturn>的地址,即0x208d。

runtime.deferreturn执行完返回时,会执行RET指令,从SP取出返回地址,又重新执行callq 0x21970 <runtime.deferreturn>,重而实现了runtime.deferreturn的递归调用:

  • stack in C


00000000004004c4 <test>:
  4004c4:       55                      push   %rbp
  4004c5:       48 89 e5                mov    %rsp,%rbp
  4004c8:       bf e8 05 40 00          mov    $0x4005e8,%edi
  4004cd:       e8 e6 fe ff ff          callq  4003b8 <puts@plt>
  4004d2:       c9                      leaveq  # 相当于 mov %rbp, %rsp; pop %rbp
  4004d3:       c3                      retq   

00000000004004d4 <main>:
  4004d4:       55                      push   %rbp
  4004d5:       48 89 e5                mov    %rsp,%rbp
  4004d8:       b8 00 00 00 00          mov    $0x0,%eax
  4004dd:       e8 e2 ff ff ff          callq  4004c4 <test>
  4004e2:       c9                      leaveq 
  4004e3:       c3                      retq  

panic and recover


  • panic


While executing a function F, an explicit call to panic or a run-time panic terminates the execution
of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by 
F's caller are run, and so on up to any deferred by the top-level function in the executing 
goroutine. At that point, the program is terminated and the error condition is reported, including
the value of the argument to panic. This termination sequence is called panicking. 


package main

import "fmt"

func main() {
	c := make(chan int)
	go func() {
		defer func() {
			c <- 1
	fmt.Println("main done")

func f() {
	fmt.Println("In f")
	fmt.Println("Exit f")

func g() {
	defer func() {
		fmt.Println("Gefer in g")
	panic("Panic in g")

	fmt.Println("Exit g")
In f
Gefer in g
panic: Panic in g

goroutine 5 [running]:
	/Users/yy/dev/go/src/github.com/hustcat/golangexample/c/panic_ex2.go:27 +0x86
	/Users/yy/dev/go/src/github.com/hustcat/golangexample/c/panic_ex2.go:19 +0xe3
	/Users/yy/dev/go/src/github.com/hustcat/golangexample/c/panic_ex2.go:11 +0x3f
created by main.main
	/Users/yy/dev/go/src/github.com/hustcat/golangexample/c/panic_ex2.go:12 +0x5a

goroutine 1 [runnable]:
	/Users/yy/dev/go/src/github.com/hustcat/golangexample/c/panic_ex2.go:13 +0x7d
exit status 2
  • recover


Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
func main() {
	c := make(chan int)
	go func() {
		defer func() {
			if r := recover(); r != nil {
				fmt.Println("Recovered in goroutine: ", r)
			c <- 1
	fmt.Println("main done")


In f
Gefer in g
Recovered in goroutine:  Panic in g
main done
  • runtime.panic
// go 1.3
(gdb) disass
Dump of assembler code for function main.g:
=> 0x0000000000002290 <+0>:	mov    %gs:0x8a0,%rcx
   0x0000000000002299 <+9>:	cmp    (%rcx),%rsp
   0x000000000000229c <+12>:	ja     0x22a5 <main.g+21>
   0x000000000000229e <+14>:	callq  0x284c0 <runtime.morestack00_noctxt>
   0x00000000000022a3 <+19>:	jmp    0x2290 <main.g>
   0x00000000000022a5 <+21>:	sub    $0x40,%rsp
   0x00000000000022a9 <+25>:	mov    $0xf26f8,%ecx
   0x00000000000022ae <+30>:	push   %rcx
   0x00000000000022af <+31>:	pushq  $0x0
   0x00000000000022b1 <+33>:	callq  0xfd90 <runtime.deferproc> #创建Defer对象
   0x00000000000022b6 <+38>:	pop    %rcx
   0x00000000000022b7 <+39>:	pop    %rcx
   0x00000000000022b8 <+40>:	test   %rax,%rax
   0x00000000000022bb <+43>:	jne    0x2307 <main.g+119>
   0x00000000000022bd <+45>:	lea    0xceec0,%rbx
   0x00000000000022c5 <+53>:	mov    (%rbx),%rbp
   0x00000000000022c8 <+56>:	mov    %rbp,0x30(%rsp)
   0x00000000000022cd <+61>:	mov    0x8(%rbx),%rbp
   0x00000000000022d1 <+65>:	mov    %rbp,0x38(%rsp)
   0x00000000000022d6 <+70>:	movq   $0x974c0,(%rsp)
   0x00000000000022de <+78>:	lea    0x30(%rsp),%rbx
   0x00000000000022e3 <+83>:	mov    %rbx,0x8(%rsp)
   0x00000000000022e8 <+88>:	callq  0x20d30 <runtime.convT2E>
   0x00000000000022ed <+93>:	lea    0x10(%rsp),%rbx
   0x00000000000022f2 <+98>:	lea    (%rsp),%rbp
   0x00000000000022f6 <+102>:	mov    %rbp,%rdi
   0x00000000000022f9 <+105>:	mov    %rbx,%rsi
   0x00000000000022fc <+108>:	movsq  %ds:(%rsi),%es:(%rdi)
   0x00000000000022fe <+110>:	movsq  %ds:(%rsi),%es:(%rdi)
   0x0000000000002300 <+112>:	callq  0x10080 <runtime.panic> #调用runtime.panic
   0x0000000000002305 <+117>:	ud2    
   0x0000000000002307 <+119>:	nop
   0x0000000000002308 <+120>:	callq  0xfe00 <runtime.deferreturn>
   0x000000000000230d <+125>:	add    $0x40,%rsp
   0x0000000000002311 <+129>:	retq   

// Called from panic.  Mimics morestack,
// reuses stack growth code to create a frame
// with the desired args running the desired function.
// func call(fn *byte, arg *byte, argsize uint32).
TEXT runtime·newstackcall(SB), NOSPLIT, $0-20
	MOVQ	m(CX), BX   ## m -> BX

	// Save our caller's state as the PC and SP to
	// restore when returning from f.
	MOVQ	0(SP), AX	// our caller's PC  ## ret IP -> M.morebuf.pc
	MOVQ	AX, (m_morebuf+gobuf_pc)(BX)
	LEAQ	8(SP), AX	// our caller's SP  ## caller's SP -> M.morebuf.sp
	MOVQ	AX, (m_morebuf+gobuf_sp)(BX)
	MOVQ	g(CX), AX						## g -> AX
	MOVQ	AX, (m_morebuf+gobuf_g)(BX)     ## g -> M.morebuf.g
	// Save our own state as the PC and SP to restore
	// if this goroutine needs to be restarted.
	MOVQ	$runtime·newstackcall(SB), (g_sched+gobuf_pc)(AX) ## ret IP -> g.sched.pc
	MOVQ	SP, (g_sched+gobuf_sp)(AX)	## SP -> g.sched.sp

	// Set up morestack arguments to call f on a new stack.
	// We set f's frame size to 1, as a hint to newstack
	// that this is a call from runtime·newstackcall.
	// If it turns out that f needs a larger frame than
	// the default stack, f's usual stack growth prolog will
	// allocate a new segment (and recopy the arguments).
	MOVQ	8(SP), AX	// fn
	MOVQ	16(SP), DX	// arg frame
	MOVL	24(SP), CX	// arg size

	MOVQ	AX, m_cret(BX)	// f's PC  ## fn -> M.cret
	MOVQ	DX, m_moreargp(BX)	// argument frame pointer
	MOVL	CX, m_moreargsize(BX)	// f's argument size
	MOVL	$1, m_moreframesize(BX)	// f's frame size

	// Call newstack on m->g0's stack.
	MOVQ	m_g0(BX), BP  ## m->g0 -> BP
	MOVQ	BP, g(CX) ## g0 -> g
	MOVQ	(g_sched+gobuf_sp)(BP), SP ##g0.sched.sp -> SP, SP指向了g0的栈
	CALL	runtime·newstack(SB)
	MOVQ	$0, 0x1103	// crash if newstack returns



// Serve a new connection.
func (c *conn) serve() {
    origConn := c.rwc // copy it before it's set nil on Close or Hijack
    defer func() {
        if err := recover(); err != nil {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        if !c.hijacked() {
            c.setState(origConn, StateClosed)

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each.  The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        tempDelay = 0
        c, err := srv.newConn(rw)
        if err != nil {
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve()


上一篇     下一篇