golang morestack_noctxt解析

go version go16.2, maxOS Mojave
原理可以从https://juejin.cn/post/684490...,本片单纯解析源码

哪里调用

原函数

package main

func testMoreStack(a, b int) int {
   return testMoreStack(1, b)
}
func main() {
}

编译

go tool compile -N -l -S ./main2.go > ./main2~~~~.s

打开生成文件main2.s

"".testMoreStack STEXT size=93 args=0x18 locals=0x28 funcid=0x0
    0x0000 00000 (./main2.go:7)    TEXT    "".testMoreStack(SB), ABIInternal, $40-24
    0x0000 00000 (./main2.go:7)    MOVQ    (TLS), CX                   // CX = *g
    0x0009 00009 (./main2.go:7)    CMPQ    SP, 16(CX)                  // if SP > g.stackguard1
    0x000d 00013 (./main2.go:7)    PCDATA    $0, $-2
    0x000d 00013 (./main2.go:7)    JLS    86                             // true 跳到86
    0x000f 00015 (./main2.go:7)    PCDATA    $0, $-1
    0x000f 00015 (./main2.go:7)    SUBQ    $40, SP
    0x0013 00019 (./main2.go:7)    MOVQ    BP, 32(SP)
    0x0018 00024 (./main2.go:7)    LEAQ    32(SP), BP
    0x001d 00029 (./main2.go:7)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (./main2.go:7)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (./main2.go:7)    MOVQ    $0, "".~r2+64(SP)
    0x0026 00038 (./main2.go:8)    MOVQ    "".b+56(SP), AX
    0x002b 00043 (./main2.go:8)    MOVQ    $1, (SP)
    0x0033 00051 (./main2.go:8)    MOVQ    AX, 8(SP)
    0x0038 00056 (./main2.go:8)    PCDATA    $1, $0
    0x0038 00056 (./main2.go:8)    CALL    "".testMoreStack(SB)
    0x003d 00061 (./main2.go:8)    MOVQ    16(SP), AX
    0x0042 00066 (./main2.go:8)    MOVQ    AX, ""..autotmp_3+24(SP)
    0x0047 00071 (./main2.go:8)    MOVQ    AX, "".~r2+64(SP)
    0x004c 00076 (./main2.go:8)    MOVQ    32(SP), BP
    0x0051 00081 (./main2.go:8)    ADDQ    $40, SP
    0x0055 00085 (./main2.go:8)    RET
    0x0056 00086 (./main2.go:8)    NOP
    0x0056 00086 (./main2.go:7)    PCDATA    $1, $-1
    0x0056 00086 (./main2.go:7)    PCDATA    $0, $-2
    0x0056 00086 (./main2.go:7)    CALL    runtime.morestack_noctxt(SB)
    0x005b 00091 (./main2.go:7)    PCDATA    $0, $-1
    0x005b 00091 (./main2.go:7)    JMP    0                              // 跳到0
    0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 47 48  eH..%....H;a.vGH
    0x0010 83 ec 28 48 89 6c 24 20 48 8d 6c 24 20 48 c7 44  ..(H.l$ H.l$ H.D
    0x0020 24 40 00 00 00 00 48 8b 44 24 38 48 c7 04 24 01  [email protected]$8H..$.
    0x0030 00 00 00 48 89 44 24 08 e8 00 00 00 00 48 8b 44  ...H.D$......H.D
    0x0040 24 10 48 89 44 24 18 48 89 44 24 40 48 8b 6c 24  [email protected]$
    0x0050 20 48 83 c4 28 c3 e8 00 00 00 00 eb a3            H..(........
    rel 5+4 t=17 TLS+0
    rel 57+4 t=8 "".testMoreStack+0
    rel 87+4 t=8 runtime.morestack_noctxt+0

本函数栈40字节,调用栈24字节,函数返回PC8字节字节共72字节,
栈位置:

位置
64(SP) 返回值
56(SP) 参数b
48(SP) 参数a
40(SP) caller's PC
32(SP) base point
24(SP) 临时存调用testMoreStack的返回值
16(SP) 调用testMoreStack的返回值
8(SP) 调用testMoreStack的第二个参数
0(SP) 调用testMoreStack的第一个参数

伪代码

label0:
if SP > g.stackguard1 {
    CALL runtime.morestack_noctxt(SB)
    jump label0
} else {
    do something
}

开始执行

以asm_amd64.s为例

// morestack but not preserving ctxt.
TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0
    MOVL $0, DX                     // 一会传给g.sched.gobuf.ctxt
    JMP runtime·morestack(SB)


// Called during function prolog when more stack is needed.
//
// 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).
    get_tls(CX)                                 // CX = g
    MOVQ    g(CX), BX                           // BX = g
    MOVQ    g_m(BX), BX                         // BX = g.m
    MOVQ    m_g0(BX), SI                        // SI = m.g0
    CMPQ    g(CX), SI                           // if g == g0
    JNE    3(PC)                                   // 不相等的话跳到    3(PC)  
    CALL    runtime·badmorestackg0(SB)
    CALL    runtime·abort(SB)

    // Cannot grow signal stack (m->gsignal).
    MOVQ    m_gsignal(BX), SI                   // SI = m.gsignal
    CMPQ    g(CX), SI                           // if g == gsignal
    JNE    3(PC)                                   // 不相等的话跳到    3(PC)  
    CALL    runtime·badmorestackgsignal(SB)
    CALL    runtime·abort(SB)

    // Called from f: f就是前面的testMoreStack
    // Set m->morebuf to f's caller.
    NOP    SP                                        // tell vet SP changed - stop checking offsets
    MOVQ    8(SP), AX                            // f's caller's PC
    MOVQ    AX, (m_morebuf+gobuf_pc)(BX)        // m.morebuf.gobuf.pc = 8(SP)
    LEAQ    16(SP), AX                            // f's caller's SP
    MOVQ    AX, (m_morebuf+gobuf_sp)(BX)        // m.morebuf.gobuf.pc = 8(SP)
    get_tls(CX)                                 // CX = g
    MOVQ    g(CX), SI                           // SI = g
    MOVQ    SI, (m_morebuf+gobuf_g)(BX)         // m.morebuf.gobuf.g = g

    // Set g->sched to context in f.
    MOVQ    0(SP), AX                           // f's PC                 
    MOVQ    AX, (g_sched+gobuf_pc)(SI)          // m.morebuf.gobuf.pc = AX
    MOVQ    SI, (g_sched+gobuf_g)(SI)           // g.sched.gobuf.g = g
    LEAQ    8(SP), AX                           // f's SP
    MOVQ    AX, (g_sched+gobuf_sp)(SI)          // g.sched.gobuf.sp = AX
    MOVQ    BP, (g_sched+gobuf_bp)(SI)          // g.sched.gobuf.bp = BP
    MOVQ    DX, (g_sched+gobuf_ctxt)(SI)        // g.sched.gobuf.ctxt = DX = 0

    // Call newstack on m->g0's stack.
    MOVQ    m_g0(BX), BX                        // BX = m.g0
    MOVQ    BX, g(CX)                           // g = BX = g0
    MOVQ    (g_sched+gobuf_sp)(BX), SP          // g.sched.gobuf.sp = sp
    CALL    runtime·newstack(SB)                  
    CALL    runtime·abort(SB)    // crash if newstack returns
    RET

栈位置:

位置
16(SP) testMoreStack返回上一个函数的SP位置
8(SP) testMoreStack返回上一个函数的指针
0(SP) morestack返回testMoreStack的指针

newstack

// Called from runtime·morestack when more stack is needed.
// Allocate larger stack and relocate to new stack.
// Stack growth is multiplicative, for constant amortized cost.
//
// g->atomicstatus will be Grunning or Gscanrunning upon entry.
// If the scheduler is trying to stop this g, then it will set preemptStop.
//
// This must be nowritebarrierrec because it can be called as part of
// stack growth from other nowritebarrierrec functions, but the
// compiler doesn't check this.
//
//go:nowritebarrierrec
func newstack() {
    thisg := getg()
    // TODO: double check all gp. shouldn't be getg().
    if thisg.m.morebuf.g.ptr().stackguard0 == stackFork {
        throw("stack growth after fork")
    }
    if thisg.m.morebuf.g.ptr() != thisg.m.curg {
        print("runtime: newstack called from g=", hex(thisg.m.morebuf.g), "\n"+"\tm=", thisg.m, " m->curg=", thisg.m.curg, " m->g0=", thisg.m.g0, " m->gsignal=", thisg.m.gsignal, "\n")
        morebuf := thisg.m.morebuf
        traceback(morebuf.pc, morebuf.sp, morebuf.lr, morebuf.g.ptr())
        throw("runtime: wrong goroutine in newstack")
    }

    gp := thisg.m.curg

    if thisg.m.curg.throwsplit {
        // Update syscallsp, syscallpc in case traceback uses them.
        morebuf := thisg.m.morebuf
        gp.syscallsp = morebuf.sp
        gp.syscallpc = morebuf.pc
        pcname, pcoff := "(unknown)", uintptr(0)
        f := findfunc(gp.sched.pc)
        if f.valid() {
            pcname = funcname(f)
            pcoff = gp.sched.pc - f.entry
        }
        print("runtime: newstack at ", pcname, "+", hex(pcoff),
            " sp=", hex(gp.sched.sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
            "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
            "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")

        thisg.m.traceback = 2 // Include runtime frames
        traceback(morebuf.pc, morebuf.sp, morebuf.lr, gp)
        throw("runtime: stack split at bad time")
    }

    morebuf := thisg.m.morebuf
    thisg.m.morebuf.pc = 0
    thisg.m.morebuf.lr = 0
    thisg.m.morebuf.sp = 0
    thisg.m.morebuf.g = 0

    // NOTE: stackguard0 may change underfoot, if another thread
    // is about to try to preempt gp. Read it just once and use that same
    // value now and below.
    preempt := atomic.Loaduintptr(&gp.stackguard0) == stackPreempt

    // Be conservative about where we preempt.
    // We are interested in preempting user Go code, not runtime code.
    // If we're holding locks, mallocing, or preemption is disabled, don't
    // preempt.
    // This check is very early in newstack so that even the status change
    // from Grunning to Gwaiting and back doesn't happen in this case.
    // That status change by itself can be viewed as a small preemption,
    // because the GC might change Gwaiting to Gscanwaiting, and then
    // this goroutine has to wait for the GC to finish before continuing.
    // If the GC is in some way dependent on this goroutine (for example,
    // it needs a lock held by the goroutine), that small preemption turns
    // into a real deadlock.
    if preempt {
        if !canPreemptM(thisg.m) {
            // Let the goroutine keep running for now.
            // gp->preempt is set, so it will be preempted next time.
            gp.stackguard0 = gp.stack.lo + _StackGuard
            gogo(&gp.sched) // never return
        }
    }

    if gp.stack.lo == 0 {
        throw("missing stack in newstack")
    }
    sp := gp.sched.sp
    if sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM {
        // The call to morestack cost a word.
        sp -= sys.PtrSize
    }
    if stackDebug >= 1 || sp < gp.stack.lo {
        print("runtime: newstack sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n",
            "\tmorebuf={pc:", hex(morebuf.pc), " sp:", hex(morebuf.sp), " lr:", hex(morebuf.lr), "}\n",
            "\tsched={pc:", hex(gp.sched.pc), " sp:", hex(gp.sched.sp), " lr:", hex(gp.sched.lr), " ctxt:", gp.sched.ctxt, "}\n")
    }
    if sp < gp.stack.lo {
        print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->status=", hex(readgstatus(gp)), "\n ")
        print("runtime: split stack overflow: ", hex(sp), " < ", hex(gp.stack.lo), "\n")
        throw("runtime: split stack overflow")
    }

    if preempt {
        if gp == thisg.m.g0 {
            throw("runtime: preempt g0")
        }
        if thisg.m.p == 0 && thisg.m.locks == 0 {
            throw("runtime: g is running but p is not")
        }

        if gp.preemptShrink {
            // We're at a synchronous safe point now, so
            // do the pending stack shrink.
            gp.preemptShrink = false
            shrinkstack(gp)
        }

        if gp.preemptStop {
            preemptPark(gp) // never returns
        }

        // Act like goroutine called runtime.Gosched.
        gopreempt_m(gp) // never return
    }

    // Allocate a bigger segment and move the stack.
    oldsize := gp.stack.hi - gp.stack.lo
    newsize := oldsize * 2

    // Make sure we grow at least as much as needed to fit the new frame.
    // (This is just an optimization - the caller of morestack will
    // recheck the bounds on return.)
    if f := findfunc(gp.sched.pc); f.valid() {
        max := uintptr(funcMaxSPDelta(f))
        for newsize-oldsize < max+_StackGuard {
            newsize *= 2
        }
    }

    if newsize > maxstacksize || newsize > maxstackceiling {
        if maxstacksize < maxstackceiling {
            print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")
        } else {
            print("runtime: goroutine stack exceeds ", maxstackceiling, "-byte limit\n")
        }
        print("runtime: sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
        throw("stack overflow")
    }

    // The goroutine must be executing in order to call newstack,
    // so it must be Grunning (or Gscanrunning).
    casgstatus(gp, _Grunning, _Gcopystack)

    // The concurrent GC will not scan the stack while we are doing the copy since
    // the gp is in a Gcopystack status.
    copystack(gp, newsize)
    if stackDebug >= 1 {
        print("stack grow done\n")
    }
    casgstatus(gp, _Gcopystack, _Grunning)
    gogo(&gp.sched)
}

你可能感兴趣的:(golang)