1
0
mirror of https://github.com/golang/go synced 2024-11-11 23:20:24 -07:00

cmd/6g, liblink, runtime: support saving base pointers

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>
This commit is contained in:
Austin Clements 2015-01-14 11:09:50 -05:00
parent 20a6ff7261
commit 3c0fee10db
12 changed files with 134 additions and 8 deletions

View File

@ -285,6 +285,9 @@ ginit(void)
if(nacl) { if(nacl) {
reg[REG_BP]++; reg[REG_BP]++;
reg[REG_R15]++; 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) { if(nacl) {
reg[REG_BP]--; reg[REG_BP]--;
reg[REG_R15]--; reg[REG_R15]--;
} else if(framepointer_enabled) {
reg[REG_BP]--;
} }

View File

@ -1186,6 +1186,9 @@ BtoR(uint32 b)
b &= 0xffffL; b &= 0xffffL;
if(nacl) if(nacl)
b &= ~((1<<(REG_BP-REG_AX)) | (1<<(REG_R15-REG_AX))); 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) if(b == 0)
return 0; return 0;
return bitno(b) + REG_AX; return bitno(b) + REG_AX;

View File

@ -21,7 +21,7 @@ static struct {
int *val; int *val;
} exper[] = { } exper[] = {
{"fieldtrack", &fieldtrack_enabled}, {"fieldtrack", &fieldtrack_enabled},
{"basepointer", &framepointer_enabled}, {"framepointer", &framepointer_enabled},
}; };
static void static void

View File

@ -379,7 +379,7 @@ preprocess(Link *ctxt, LSym *cursym)
{ {
Prog *p, *q, *p1, *p2; Prog *p, *q, *p1, *p2;
int32 autoffset, deltasp; int32 autoffset, deltasp;
int a, pcsize; int a, pcsize, bpsize;
vlong textstksiz, textarg; vlong textstksiz, textarg;
if(ctxt->tlsg == nil) if(ctxt->tlsg == nil)
@ -403,6 +403,18 @@ preprocess(Link *ctxt, LSym *cursym)
if(autoffset < 0) if(autoffset < 0)
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->args = p->to.offset>>32;
cursym->locals = textstksiz; cursym->locals = textstksiz;
@ -447,6 +459,28 @@ preprocess(Link *ctxt, LSym *cursym)
if(q != nil) if(q != nil)
q->pcond = p; q->pcond = p;
deltasp = autoffset; 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(cursym->text->from.scale & WRAPPER) {
// if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame // 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; pcsize = p->mode/8;
a = p->from.name; a = p->from.name;
if(a == NAME_AUTO) if(a == NAME_AUTO)
p->from.offset += deltasp; p->from.offset += deltasp - bpsize;
if(a == NAME_PARAM) if(a == NAME_PARAM)
p->from.offset += deltasp + pcsize; p->from.offset += deltasp + pcsize;
a = p->to.name; a = p->to.name;
if(a == NAME_AUTO) if(a == NAME_AUTO)
p->to.offset += deltasp; p->to.offset += deltasp - bpsize;
if(a == NAME_PARAM) if(a == NAME_PARAM)
p->to.offset += deltasp + pcsize; p->to.offset += deltasp + pcsize;
@ -630,6 +664,18 @@ preprocess(Link *ctxt, LSym *cursym)
ctxt->diag("unbalanced PUSH/POP"); ctxt->diag("unbalanced PUSH/POP");
if(autoffset) { 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->as = AADJSP;
p->from.type = TYPE_CONST; p->from.type = TYPE_CONST;
p->from.offset = -autoffset; p->from.offset = -autoffset;

View File

@ -8,7 +8,7 @@ const (
thechar = '6' thechar = '6'
_BigEndian = 0 _BigEndian = 0
_CacheLineSize = 64 _CacheLineSize = 64
_RuntimeGogoBytes = 64 + (goos_plan9|goos_solaris|goos_windows)*16 _RuntimeGogoBytes = 80 + (goos_plan9|goos_solaris|goos_windows)*16
_PhysPageSize = 4096 _PhysPageSize = 4096
_PCQuantum = 1 _PCQuantum = 1
_Int64Align = 8 _Int64Align = 8

View File

@ -134,6 +134,7 @@ TEXT runtime·gosave(SB), NOSPLIT, $0-8
MOVQ BX, gobuf_pc(AX) MOVQ BX, gobuf_pc(AX)
MOVQ $0, gobuf_ret(AX) MOVQ $0, gobuf_ret(AX)
MOVQ $0, gobuf_ctxt(AX) MOVQ $0, gobuf_ctxt(AX)
MOVQ BP, gobuf_bp(AX)
get_tls(CX) get_tls(CX)
MOVQ g(CX), BX MOVQ g(CX), BX
MOVQ BX, gobuf_g(AX) 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_sp(BX), SP // restore SP
MOVQ gobuf_ret(BX), AX MOVQ gobuf_ret(BX), AX
MOVQ gobuf_ctxt(BX), DX MOVQ gobuf_ctxt(BX), DX
MOVQ gobuf_bp(BX), BP
MOVQ $0, gobuf_sp(BX) // clear to help garbage collector MOVQ $0, gobuf_sp(BX) // clear to help garbage collector
MOVQ $0, gobuf_ret(BX) MOVQ $0, gobuf_ret(BX)
MOVQ $0, gobuf_ctxt(BX) MOVQ $0, gobuf_ctxt(BX)
MOVQ $0, gobuf_bp(BX)
MOVQ gobuf_pc(BX), BX MOVQ gobuf_pc(BX), BX
JMP BX JMP BX
@ -170,6 +173,7 @@ TEXT runtime·mcall(SB), NOSPLIT, $0-8
LEAQ fn+0(FP), BX // caller's SP LEAQ fn+0(FP), BX // caller's SP
MOVQ BX, (g_sched+gobuf_sp)(AX) MOVQ BX, (g_sched+gobuf_sp)(AX)
MOVQ AX, (g_sched+gobuf_g)(AX) MOVQ AX, (g_sched+gobuf_g)(AX)
MOVQ BP, (g_sched+gobuf_bp)(AX)
// switch to m->g0 & its stack, call fn // switch to m->g0 & its stack, call fn
MOVQ g(CX), BX MOVQ g(CX), BX
@ -228,6 +232,7 @@ switch:
MOVQ SI, (g_sched+gobuf_pc)(AX) MOVQ SI, (g_sched+gobuf_pc)(AX)
MOVQ SP, (g_sched+gobuf_sp)(AX) MOVQ SP, (g_sched+gobuf_sp)(AX)
MOVQ AX, (g_sched+gobuf_g)(AX) MOVQ AX, (g_sched+gobuf_g)(AX)
MOVQ BP, (g_sched+gobuf_bp)(AX)
// switch to g0 // switch to g0
MOVQ DX, g(CX) MOVQ DX, g(CX)
@ -303,6 +308,7 @@ TEXT runtime·morestack(SB),NOSPLIT,$0-0
LEAQ 8(SP), AX // f's SP LEAQ 8(SP), AX // f's SP
MOVQ AX, (g_sched+gobuf_sp)(SI) MOVQ AX, (g_sched+gobuf_sp)(SI)
MOVQ DX, (g_sched+gobuf_ctxt)(SI) MOVQ DX, (g_sched+gobuf_ctxt)(SI)
MOVQ BP, (g_sched+gobuf_bp)(SI)
// Call newstack on m->g0's stack. // Call newstack on m->g0's stack.
MOVQ m_g0(BX), BX MOVQ m_g0(BX), BX
@ -592,6 +598,7 @@ TEXT gosave<>(SB),NOSPLIT,$0
MOVQ R9, (g_sched+gobuf_sp)(R8) MOVQ R9, (g_sched+gobuf_sp)(R8)
MOVQ $0, (g_sched+gobuf_ret)(R8) MOVQ $0, (g_sched+gobuf_ret)(R8)
MOVQ $0, (g_sched+gobuf_ctxt)(R8) MOVQ $0, (g_sched+gobuf_ctxt)(R8)
MOVQ BP, (g_sched+gobuf_bp)(R8)
RET RET
// asmcgocall(void(*fn)(void*), void *arg) // 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_sp)(SI), DI // prepare stack as DI
MOVQ (g_sched+gobuf_pc)(SI), BX MOVQ (g_sched+gobuf_pc)(SI), BX
MOVQ BX, -8(DI) 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) MOVQ R8, 0(SP)
CALL runtime·cgocallbackg(SB) CALL runtime·cgocallbackg(SB)
MOVQ 0(SP), R8 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. // Restore g->sched (== m->curg->sched) from saved values.
get_tls(CX) get_tls(CX)
MOVQ g(CX), SI 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) MOVQ BX, (g_sched+gobuf_pc)(SI)
LEAQ (8+8)(SP), DI
MOVQ DI, (g_sched+gobuf_sp)(SI) MOVQ DI, (g_sched+gobuf_sp)(SI)
// Switch back to m->g0's stack and restore m->g0->sched.sp. // Switch back to m->g0's stack and restore m->g0->sched.sp.

View File

@ -225,6 +225,11 @@ func cgocallbackg1() {
cb = (*args)(unsafe.Pointer(sp + 4*ptrSize)) cb = (*args)(unsafe.Pointer(sp + 4*ptrSize))
case "amd64": case "amd64":
// On amd64, stack frame is one word, plus caller PC. // 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)) cb = (*args)(unsafe.Pointer(sp + 2*ptrSize))
case "386": case "386":
// On 386, stack frame is three words, plus caller PC. // On 386, stack frame is three words, plus caller PC.

View File

@ -113,6 +113,9 @@ func schedinit() {
sched.maxmcount = 10000 sched.maxmcount = 10000
// Cache the framepointer experiment. This affects stack unwinding.
framepointer_enabled = haveexperiment("framepointer")
tracebackinit() tracebackinit()
symtabinit() symtabinit()
stackinit() stackinit()

View File

@ -125,6 +125,7 @@ type gobuf struct {
ctxt unsafe.Pointer // this has to be a pointer so that gc scans it ctxt unsafe.Pointer // this has to be a pointer so that gc scans it
ret uintreg ret uintreg
lr uintptr lr uintptr
bp uintptr // for GOEXPERIMENT=framepointer
} }
// Known to compiler. // Known to compiler.

View File

@ -46,6 +46,9 @@ var stackpoolmu mutex
var stackfreequeue stack var stackfreequeue stack
// Cached value of haveexperiment("framepointer")
var framepointer_enabled bool
func stackinit() { func stackinit() {
if _StackCacheSize&_PageMask != 0 { if _StackCacheSize&_PageMask != 0 {
throw("cache size must be a multiple of page size") throw("cache size must be a multiple of page size")
@ -308,6 +311,8 @@ var mapnames = []string{
// | args from caller | // | args from caller |
// +------------------+ <- frame->argp // +------------------+ <- frame->argp
// | return address | // | return address |
// +------------------+
// | caller's BP (*) | (*) if framepointer_enabled && varp < sp
// +------------------+ <- frame->varp // +------------------+ <- frame->varp
// | locals | // | locals |
// +------------------+ // +------------------+
@ -460,6 +465,18 @@ func adjustframe(frame *stkframe, arg unsafe.Pointer) bool {
adjustpointers(unsafe.Pointer(frame.varp-size), &bv, adjinfo, f) 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. // Adjust arguments.
if frame.arglen > 0 { if frame.arglen > 0 {
var bv bitvector var bv bitvector

View File

@ -232,6 +232,12 @@ func gentraceback(pc0 uintptr, sp0 uintptr, lr0 uintptr, gp *g, skip int, pcbuf
frame.varp -= regSize 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. // Derive size of arguments.
// Most functions have a fixed-size argument block, // Most functions have a fixed-size argument block,
// so we can use metadata about the function f. // so we can use metadata about the function f.

View File

@ -185,6 +185,26 @@ func main() {
goarch = runtime.GOARCH 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") dir, err := ioutil.TempDir("", "go-test-nosplit")
if err != nil { if err != nil {
bug() bug()