]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/6g, liblink, runtime: support saving base pointers
authorAustin Clements <austin@google.com>
Wed, 14 Jan 2015 16:09:50 +0000 (11:09 -0500)
committerAustin Clements <austin@google.com>
Mon, 2 Feb 2015 19:36:05 +0000 (19:36 +0000)
This adds a "framepointer" GOEXPERIMENT that that makes the amd64
toolchain maintain base pointer chains in the same way that gcc
-fno-omit-frame-pointer does.  Go doesn't use these saved base
pointers, but this does enable external tools like Linux perf and
VTune to unwind Go stacks when collecting system-wide profiles.

This requires support in the compilers to not clobber BP, support in
liblink for generating the BP-saving function prologue and unwinding
epilogue, and support in the runtime to save BPs across preemption, to
skip saved BPs during stack unwinding and, and to adjust saved BPs
during stack moving.

As with other GOEXPERIMENTs, everything from the toolchain to the
runtime must be compiled with this experiment enabled.  To do this,
run make.bash (or all.bash) with GOEXPERIMENT=framepointer.

Change-Id: I4024853beefb9539949e5ca381adfdd9cfada544
Reviewed-on: https://go-review.googlesource.com/2992
Reviewed-by: Russ Cox <rsc@golang.org>
12 files changed:
src/cmd/6g/gsubr.c
src/cmd/6g/reg.c
src/liblink/go.c
src/liblink/obj6.c
src/runtime/arch1_amd64.go
src/runtime/asm_amd64.s
src/runtime/cgocall.go
src/runtime/proc1.go
src/runtime/runtime2.go
src/runtime/stack1.go
src/runtime/traceback.go
test/nosplit.go

index ee6852d6f6cfd80c903e64d1535b02265ba1259b..1e996ae16264f99f4257e595fe927780bbca3e4f 100644 (file)
@@ -285,6 +285,9 @@ ginit(void)
        if(nacl) {
                reg[REG_BP]++;
                reg[REG_R15]++;
+       } else if(framepointer_enabled) {
+               // BP is part of the calling convention of framepointer_enabled.
+               reg[REG_BP]++;
        }
 }
 
@@ -298,6 +301,8 @@ gclean(void)
        if(nacl) {
                reg[REG_BP]--;
                reg[REG_R15]--;
+       } else if(framepointer_enabled) {
+               reg[REG_BP]--;
        }
 
 
index 2581128b5820a022fa14d87e0835b395fb7d36ed..7db44245f1d6804ec8135d5ba8f6d43ccc91258d 100644 (file)
@@ -1186,6 +1186,9 @@ BtoR(uint32 b)
        b &= 0xffffL;
        if(nacl)
                b &= ~((1<<(REG_BP-REG_AX)) | (1<<(REG_R15-REG_AX)));
+       else if(framepointer_enabled)
+               // BP is part of the calling convention if framepointer_enabled.
+               b &= ~(1<<(REG_BP-REG_AX));
        if(b == 0)
                return 0;
        return bitno(b) + REG_AX;
index e31c71ab9263cbe05b3d9b771cbb0ad40cbed7aa..3bc780b85281d57d6d03461db4b0dea128ef9127 100644 (file)
@@ -21,7 +21,7 @@ static struct {
        int *val;
 } exper[] = {
        {"fieldtrack", &fieldtrack_enabled},
-       {"basepointer", &framepointer_enabled}, 
+       {"framepointer", &framepointer_enabled},
 };
 
 static void
index 696026300cfe0476aeebf843e8c54bd0de697f96..0ccb03924f8840ddcb6804d926ae05ef192a0888 100644 (file)
@@ -379,7 +379,7 @@ preprocess(Link *ctxt, LSym *cursym)
 {
        Prog *p, *q, *p1, *p2;
        int32 autoffset, deltasp;
-       int a, pcsize;
+       int a, pcsize, bpsize;
        vlong textstksiz, textarg;
 
        if(ctxt->tlsg == nil)
@@ -403,6 +403,18 @@ preprocess(Link *ctxt, LSym *cursym)
        if(autoffset < 0)
                autoffset = 0;
        
+       if(framepointer_enabled && autoffset > 0) {
+               // Make room for to save a base pointer.  If autoffset == 0,
+               // this might do something special like a tail jump to
+               // another function, so in that case we omit this.
+               bpsize = ctxt->arch->ptrsize;
+               autoffset += bpsize;
+               textstksiz += bpsize;
+               p->to.offset = ((uint64)p->to.offset & (0xffffffffull<<32)) | (uint32)autoffset;
+       } else {
+               bpsize = 0;
+       }
+
        cursym->args = p->to.offset>>32;
        cursym->locals = textstksiz;
 
@@ -447,6 +459,28 @@ preprocess(Link *ctxt, LSym *cursym)
        if(q != nil)
                q->pcond = p;
        deltasp = autoffset;
+
+       if(bpsize > 0) {
+               // Save caller's BP
+               p = appendp(ctxt, p);
+               p->as = AMOVQ;
+               p->from.type = TYPE_REG;
+               p->from.reg = REG_BP;
+               p->to.type = TYPE_MEM;
+               p->to.reg = REG_SP;
+               p->to.scale = 1;
+               p->to.offset = autoffset - bpsize;
+
+               // Move current frame to BP
+               p = appendp(ctxt, p);
+               p->as = ALEAQ;
+               p->from.type = TYPE_MEM;
+               p->from.reg = REG_SP;
+               p->from.scale = 1;
+               p->from.offset = autoffset - bpsize;
+               p->to.type = TYPE_REG;
+               p->to.reg = REG_BP;
+       }
        
        if(cursym->text->from.scale & WRAPPER) {
                // if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame
@@ -580,12 +614,12 @@ preprocess(Link *ctxt, LSym *cursym)
                pcsize = p->mode/8;
                a = p->from.name;
                if(a == NAME_AUTO)
-                       p->from.offset += deltasp;
+                       p->from.offset += deltasp - bpsize;
                if(a == NAME_PARAM)
                        p->from.offset += deltasp + pcsize;
                a = p->to.name;
                if(a == NAME_AUTO)
-                       p->to.offset += deltasp;
+                       p->to.offset += deltasp - bpsize;
                if(a == NAME_PARAM)
                        p->to.offset += deltasp + pcsize;
 
@@ -630,6 +664,18 @@ preprocess(Link *ctxt, LSym *cursym)
                        ctxt->diag("unbalanced PUSH/POP");
 
                if(autoffset) {
+                       if(bpsize > 0) {
+                               // Restore caller's BP
+                               p->as = AMOVQ;
+                               p->from.type = TYPE_MEM;
+                               p->from.reg = REG_SP;
+                               p->from.scale = 1;
+                               p->from.offset = autoffset - bpsize;
+                               p->to.type = TYPE_REG;
+                               p->to.reg = REG_BP;
+                               p = appendp(ctxt, p);
+                       }
+
                        p->as = AADJSP;
                        p->from.type = TYPE_CONST;
                        p->from.offset = -autoffset;
index 794b7f65c4899c0b716e91d110aee7bae140db41..7a7f3e75fce988531ee1570cdca719b60ac09df6 100644 (file)
@@ -8,7 +8,7 @@ const (
        thechar           = '6'
        _BigEndian        = 0
        _CacheLineSize    = 64
-       _RuntimeGogoBytes = 64 + (goos_plan9|goos_solaris|goos_windows)*16
+       _RuntimeGogoBytes = 80 + (goos_plan9|goos_solaris|goos_windows)*16
        _PhysPageSize     = 4096
        _PCQuantum        = 1
        _Int64Align       = 8
index b1bf4ca9878b8fa2543f9cf33d5bf165736208c8..f09e5ae250e16d4843310385da3b6b3b59b5a1ab 100644 (file)
@@ -134,6 +134,7 @@ TEXT runtime·gosave(SB), NOSPLIT, $0-8
        MOVQ    BX, gobuf_pc(AX)
        MOVQ    $0, gobuf_ret(AX)
        MOVQ    $0, gobuf_ctxt(AX)
+       MOVQ    BP, gobuf_bp(AX)
        get_tls(CX)
        MOVQ    g(CX), BX
        MOVQ    BX, gobuf_g(AX)
@@ -150,9 +151,11 @@ TEXT runtime·gogo(SB), NOSPLIT, $0-8
        MOVQ    gobuf_sp(BX), SP        // restore SP
        MOVQ    gobuf_ret(BX), AX
        MOVQ    gobuf_ctxt(BX), DX
+       MOVQ    gobuf_bp(BX), BP
        MOVQ    $0, gobuf_sp(BX)        // clear to help garbage collector
        MOVQ    $0, gobuf_ret(BX)
        MOVQ    $0, gobuf_ctxt(BX)
+       MOVQ    $0, gobuf_bp(BX)
        MOVQ    gobuf_pc(BX), BX
        JMP     BX
 
@@ -170,6 +173,7 @@ TEXT runtime·mcall(SB), NOSPLIT, $0-8
        LEAQ    fn+0(FP), BX    // caller's SP
        MOVQ    BX, (g_sched+gobuf_sp)(AX)
        MOVQ    AX, (g_sched+gobuf_g)(AX)
+       MOVQ    BP, (g_sched+gobuf_bp)(AX)
 
        // switch to m->g0 & its stack, call fn
        MOVQ    g(CX), BX
@@ -228,6 +232,7 @@ switch:
        MOVQ    SI, (g_sched+gobuf_pc)(AX)
        MOVQ    SP, (g_sched+gobuf_sp)(AX)
        MOVQ    AX, (g_sched+gobuf_g)(AX)
+       MOVQ    BP, (g_sched+gobuf_bp)(AX)
 
        // switch to g0
        MOVQ    DX, g(CX)
@@ -303,6 +308,7 @@ TEXT runtime·morestack(SB),NOSPLIT,$0-0
        LEAQ    8(SP), AX // f's SP
        MOVQ    AX, (g_sched+gobuf_sp)(SI)
        MOVQ    DX, (g_sched+gobuf_ctxt)(SI)
+       MOVQ    BP, (g_sched+gobuf_bp)(SI)
 
        // Call newstack on m->g0's stack.
        MOVQ    m_g0(BX), BX
@@ -592,6 +598,7 @@ TEXT gosave<>(SB),NOSPLIT,$0
        MOVQ    R9, (g_sched+gobuf_sp)(R8)
        MOVQ    $0, (g_sched+gobuf_ret)(R8)
        MOVQ    $0, (g_sched+gobuf_ctxt)(R8)
+       MOVQ    BP, (g_sched+gobuf_bp)(R8)
        RET
 
 // asmcgocall(void(*fn)(void*), void *arg)
@@ -747,17 +754,30 @@ havem:
        MOVQ    (g_sched+gobuf_sp)(SI), DI  // prepare stack as DI
        MOVQ    (g_sched+gobuf_pc)(SI), BX
        MOVQ    BX, -8(DI)
-       LEAQ    -(8+8)(DI), SP
+       // Compute the size of the frame, including return PC and, if
+       // GOEXPERIMENT=framepointer, the saved based pointer
+       LEAQ    x+0(FP), AX
+       SUBQ    SP, AX
+       SUBQ    AX, DI
+       MOVQ    DI, SP
+
        MOVQ    R8, 0(SP)
        CALL    runtime·cgocallbackg(SB)
        MOVQ    0(SP), R8
 
+       // Compute the size of the frame again.  FP and SP have
+       // completely different values here than they did above,
+       // but only their difference matters.
+       LEAQ    x+0(FP), AX
+       SUBQ    SP, AX
+
        // Restore g->sched (== m->curg->sched) from saved values.
        get_tls(CX)
        MOVQ    g(CX), SI
-       MOVQ    8(SP), BX
+       MOVQ    SP, DI
+       ADDQ    AX, DI
+       MOVQ    -8(DI), BX
        MOVQ    BX, (g_sched+gobuf_pc)(SI)
-       LEAQ    (8+8)(SP), DI
        MOVQ    DI, (g_sched+gobuf_sp)(SI)
 
        // Switch back to m->g0's stack and restore m->g0->sched.sp.
index 96873cc2da713b90f8b2846873f2ea21ca556b21..e7aeb7bee3e40d6f1679bd8def595b2aa3d38a7a 100644 (file)
@@ -225,6 +225,11 @@ func cgocallbackg1() {
                cb = (*args)(unsafe.Pointer(sp + 4*ptrSize))
        case "amd64":
                // On amd64, stack frame is one word, plus caller PC.
+               if framepointer_enabled {
+                       // In this case, there's also saved BP.
+                       cb = (*args)(unsafe.Pointer(sp + 3*ptrSize))
+                       break
+               }
                cb = (*args)(unsafe.Pointer(sp + 2*ptrSize))
        case "386":
                // On 386, stack frame is three words, plus caller PC.
index 8f5aaa863051cc741343c4de12073b1f0feadeb6..31bbd0d366477efc571ebfd9142cb5b21c9cf29c 100644 (file)
@@ -113,6 +113,9 @@ func schedinit() {
 
        sched.maxmcount = 10000
 
+       // Cache the framepointer experiment.  This affects stack unwinding.
+       framepointer_enabled = haveexperiment("framepointer")
+
        tracebackinit()
        symtabinit()
        stackinit()
index fd448901a67cbae79403965d027c1ddf018c5522..e38d11a59da08bc6ab46f91de2e2b81a6b64a5d3 100644 (file)
@@ -125,6 +125,7 @@ type gobuf struct {
        ctxt unsafe.Pointer // this has to be a pointer so that gc scans it
        ret  uintreg
        lr   uintptr
+       bp   uintptr // for GOEXPERIMENT=framepointer
 }
 
 // Known to compiler.
index 8ad331777cf4f18035335fba43371366f1c7f3e2..1e9ccfebb58a0e77c49a8a35d79e63d4e0f78a65 100644 (file)
@@ -46,6 +46,9 @@ var stackpoolmu mutex
 
 var stackfreequeue stack
 
+// Cached value of haveexperiment("framepointer")
+var framepointer_enabled bool
+
 func stackinit() {
        if _StackCacheSize&_PageMask != 0 {
                throw("cache size must be a multiple of page size")
@@ -308,6 +311,8 @@ var mapnames = []string{
 // | args from caller |
 // +------------------+ <- frame->argp
 // |  return address  |
+// +------------------+
+// |  caller's BP (*) | (*) if framepointer_enabled && varp < sp
 // +------------------+ <- frame->varp
 // |     locals       |
 // +------------------+
@@ -460,6 +465,18 @@ func adjustframe(frame *stkframe, arg unsafe.Pointer) bool {
                adjustpointers(unsafe.Pointer(frame.varp-size), &bv, adjinfo, f)
        }
 
+       // Adjust saved base pointer if there is one.
+       if thechar == '6' && frame.argp-frame.varp == 2*ptrSize {
+               if !framepointer_enabled {
+                       print("runtime: found space for saved base pointer, but no framepointer experiment")
+                       throw("bad frame layout")
+               }
+               if stackDebug >= 3 {
+                       print("      saved bp\n")
+               }
+               adjustpointer(adjinfo, unsafe.Pointer(frame.varp))
+       }
+
        // Adjust arguments.
        if frame.arglen > 0 {
                var bv bitvector
index 499256f42dab10a4e81d03e37462f4c761898272..c8134533991394f5de117938f9c086abd640bf2b 100644 (file)
@@ -232,6 +232,12 @@ func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf
                        frame.varp -= regSize
                }
 
+               // If framepointer_enabled and there's a frame, then
+               // there's a saved bp here.
+               if GOARCH == "amd64" && frame.varp > frame.sp && framepointer_enabled {
+                       frame.varp -= ptrSize
+               }
+
                // Derive size of arguments.
                // Most functions have a fixed-size argument block,
                // so we can use metadata about the function f.
index 93fb133ebb075988bba0f8ee2c896662bf47ffe1..a639150511c2fa1e8776d4fa687669880cb795c6 100644 (file)
@@ -185,6 +185,26 @@ func main() {
                goarch = runtime.GOARCH
        }
 
+       thechar := ""
+       if gochar, err := exec.Command("go", "env", "GOCHAR").Output(); err != nil {
+               bug()
+               fmt.Printf("running go env GOCHAR: %v\n", err)
+               return
+       } else {
+               thechar = strings.TrimSpace(string(gochar))
+       }
+
+       version, err := exec.Command("go", "tool", thechar+"g", "-V").Output()
+       if err != nil {
+               bug()
+               fmt.Printf("running go tool %sg -V: %v\n", thechar, err)
+               return
+       }
+       if strings.Contains(string(version), "framepointer") {
+               // Skip this test if GOEXPERIMENT=framepointer
+               return
+       }
+
        dir, err := ioutil.TempDir("", "go-test-nosplit")
        if err != nil {
                bug()