mirror of
https://github.com/golang/go
synced 2024-10-05 00:11:21 -06:00
6fa3c89b77
Until now, the goroutine state has been scattered during the execution of newstack and oldstack. It's all there, and those routines know how to get back to a working goroutine, but other pieces of the system, like stack traces, do not. If something does interrupt the newstack or oldstack execution, the rest of the system can't understand the goroutine. For example, if newstack decides there is an overflow and calls throw, the stack tracer wouldn't dump the goroutine correctly. For newstack to save a useful state snapshot, it needs to be able to rewind the PC in the function that triggered the split back to the beginning of the function. (The PC is a few instructions in, just after the call to morestack.) To make that possible, we change the prologues to insert a jmp back to the beginning of the function after the call to morestack. That is, the prologue used to be roughly: TEXT myfunc check for split jmpcond nosplit call morestack nosplit: sub $xxx, sp Now an extra instruction is inserted after the call: TEXT myfunc start: check for split jmpcond nosplit call morestack jmp start nosplit: sub $xxx, sp The jmp is not executed directly. It is decoded and simulated by runtime.rewindmorestack to discover the beginning of the function, and then the call to morestack returns directly to the start label instead of to the jump instruction. So logically the jmp is still executed, just not by the cpu. The prologue thus repeats in the case of a function that needs a stack split, but against the cost of the split itself, the extra few instructions are noise. The repeated prologue has the nice effect of making a stack split double-check that the new stack is big enough: if morestack happens to return on a too-small stack, we'll now notice before corruption happens. The ability for newstack to rewind to the beginning of the function should help preemption too. If newstack decides that it was called for preemption instead of a stack split, it now has the goroutine state correctly paused if rescheduling is needed, and when the goroutine can run again, it can return to the start label on its original stack and re-execute the split check. Here is an example of a split stack overflow showing the full trace, without any special cases in the stack printer. (This one was triggered by making the split check incorrect.) runtime: newstack framesize=0x0 argsize=0x18 sp=0x6aebd0 stack=[0x6b0000, 0x6b0fa0] morebuf={pc:0x69f5b sp:0x6aebd8 lr:0x0} sched={pc:0x68880 sp:0x6aebd0 lr:0x0 ctxt:0x34e700} runtime: split stack overflow: 0x6aebd0 < 0x6b0000 fatal error: runtime: split stack overflow goroutine 1 [stack split]: runtime.mallocgc(0x290, 0x100000000, 0x1) /Users/rsc/g/go/src/pkg/runtime/zmalloc_darwin_amd64.c:21 fp=0x6aebd8 runtime.new() /Users/rsc/g/go/src/pkg/runtime/zmalloc_darwin_amd64.c:682 +0x5b fp=0x6aec08 go/build.(*Context).Import(0x5ae340, 0xc210030c71, 0xa, 0xc2100b4380, 0x1b, ...) /Users/rsc/g/go/src/pkg/go/build/build.go:424 +0x3a fp=0x6b00a0 main.loadImport(0xc210030c71, 0xa, 0xc2100b4380, 0x1b, 0xc2100b42c0, ...) /Users/rsc/g/go/src/cmd/go/pkg.go:249 +0x371 fp=0x6b01a8 main.(*Package).load(0xc21017c800, 0xc2100b42c0, 0xc2101828c0, 0x0, 0x0, ...) /Users/rsc/g/go/src/cmd/go/pkg.go:431 +0x2801 fp=0x6b0c98 main.loadPackage(0x369040, 0x7, 0xc2100b42c0, 0x0) /Users/rsc/g/go/src/cmd/go/pkg.go:709 +0x857 fp=0x6b0f80 ----- stack segment boundary ----- main.(*builder).action(0xc2100902a0, 0x0, 0x0, 0xc2100e6c00, 0xc2100e5750, ...) /Users/rsc/g/go/src/cmd/go/build.go:539 +0x437 fp=0x6b14a0 main.(*builder).action(0xc2100902a0, 0x0, 0x0, 0xc21015b400, 0x2, ...) /Users/rsc/g/go/src/cmd/go/build.go:528 +0x1d2 fp=0x6b1658 main.(*builder).test(0xc2100902a0, 0xc210092000, 0x0, 0x0, 0xc21008ff60, ...) /Users/rsc/g/go/src/cmd/go/test.go:622 +0x1b53 fp=0x6b1f68 ----- stack segment boundary ----- main.runTest(0x5a6b20, 0xc21000a020, 0x2, 0x2) /Users/rsc/g/go/src/cmd/go/test.go:366 +0xd09 fp=0x6a5cf0 main.main() /Users/rsc/g/go/src/cmd/go/main.go:161 +0x4f9 fp=0x6a5f78 runtime.main() /Users/rsc/g/go/src/pkg/runtime/proc.c:183 +0x92 fp=0x6a5fa0 runtime.goexit() /Users/rsc/g/go/src/pkg/runtime/proc.c:1266 fp=0x6a5fa8 And here is a seg fault during oldstack: SIGSEGV: segmentation violation PC=0x1b2a6 runtime.oldstack() /Users/rsc/g/go/src/pkg/runtime/stack.c:159 +0x76 runtime.lessstack() /Users/rsc/g/go/src/pkg/runtime/asm_amd64.s:270 +0x22 goroutine 1 [stack unsplit]: fmt.(*pp).printArg(0x2102e64e0, 0xe5c80, 0x2102c9220, 0x73, 0x0, ...) /Users/rsc/g/go/src/pkg/fmt/print.go:818 +0x3d3 fp=0x221031e6f8 fmt.(*pp).doPrintf(0x2102e64e0, 0x12fb20, 0x2, 0x221031eb98, 0x1, ...) /Users/rsc/g/go/src/pkg/fmt/print.go:1183 +0x15cb fp=0x221031eaf0 fmt.Sprintf(0x12fb20, 0x2, 0x221031eb98, 0x1, 0x1, ...) /Users/rsc/g/go/src/pkg/fmt/print.go:234 +0x67 fp=0x221031eb40 flag.(*stringValue).String(0x2102c9210, 0x1, 0x0) /Users/rsc/g/go/src/pkg/flag/flag.go:180 +0xb3 fp=0x221031ebb0 flag.(*FlagSet).Var(0x2102f6000, 0x293d38, 0x2102c9210, 0x143490, 0xa, ...) /Users/rsc/g/go/src/pkg/flag/flag.go:633 +0x40 fp=0x221031eca0 flag.(*FlagSet).StringVar(0x2102f6000, 0x2102c9210, 0x143490, 0xa, 0x12fa60, ...) /Users/rsc/g/go/src/pkg/flag/flag.go:550 +0x91 fp=0x221031ece8 flag.(*FlagSet).String(0x2102f6000, 0x143490, 0xa, 0x12fa60, 0x0, ...) /Users/rsc/g/go/src/pkg/flag/flag.go:563 +0x87 fp=0x221031ed38 flag.String(0x143490, 0xa, 0x12fa60, 0x0, 0x161950, ...) /Users/rsc/g/go/src/pkg/flag/flag.go:570 +0x6b fp=0x221031ed80 testing.init() /Users/rsc/g/go/src/pkg/testing/testing.go:-531 +0xbb fp=0x221031edc0 strings_test.init() /Users/rsc/g/go/src/pkg/strings/strings_test.go:1115 +0x62 fp=0x221031ef70 main.init() strings/_test/_testmain.go:90 +0x3d fp=0x221031ef78 runtime.main() /Users/rsc/g/go/src/pkg/runtime/proc.c:180 +0x8a fp=0x221031efa0 runtime.goexit() /Users/rsc/g/go/src/pkg/runtime/proc.c:1269 fp=0x221031efa8 goroutine 2 [runnable]: runtime.MHeap_Scavenger() /Users/rsc/g/go/src/pkg/runtime/mheap.c:438 runtime.goexit() /Users/rsc/g/go/src/pkg/runtime/proc.c:1269 created by runtime.main /Users/rsc/g/go/src/pkg/runtime/proc.c:166 rax 0x23ccc0 rbx 0x23ccc0 rcx 0x0 rdx 0x38 rdi 0x2102c0170 rsi 0x221032cfe0 rbp 0x221032cfa0 rsp 0x7fff5fbff5b0 r8 0x2102c0120 r9 0x221032cfa0 r10 0x221032c000 r11 0x104ce8 r12 0xe5c80 r13 0x1be82baac718 r14 0x13091135f7d69200 r15 0x0 rip 0x1b2a6 rflags 0x10246 cs 0x2b fs 0x0 gs 0x0 Fixes #5723. R=r, dvyukov, go.peter.90, dave, iant CC=golang-dev https://golang.org/cl/10360048
659 lines
15 KiB
C
659 lines
15 KiB
C
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Runtime symbol table parsing.
|
|
//
|
|
// The Go tools use a symbol table derived from the Plan 9 symbol table
|
|
// format. The symbol table is kept in its own section treated as
|
|
// read-only memory when the binary is running: the binary consults the
|
|
// table.
|
|
//
|
|
// The format used by Go 1.0 was basically the Plan 9 format. Each entry
|
|
// is variable sized but had this format:
|
|
//
|
|
// 4-byte value, big endian
|
|
// 1-byte type ([A-Za-z] + 0x80)
|
|
// name, NUL terminated (or for 'z' and 'Z' entries, double-NUL terminated)
|
|
// 4-byte Go type address, big endian (new in Go)
|
|
//
|
|
// In order to support greater interoperation with standard toolchains,
|
|
// Go 1.1 uses a more flexible yet smaller encoding of the entries.
|
|
// The overall structure is unchanged from Go 1.0 and, for that matter,
|
|
// from Plan 9.
|
|
//
|
|
// The Go 1.1 table is a re-encoding of the data in a Go 1.0 table.
|
|
// To identify a new table as new, it begins one of two eight-byte
|
|
// sequences:
|
|
//
|
|
// FF FF FF FD 00 00 00 xx - big endian new table
|
|
// FD FF FF FF 00 00 00 xx - little endian new table
|
|
//
|
|
// This sequence was chosen because old tables stop at an entry with type
|
|
// 0, so old code reading a new table will see only an empty table. The
|
|
// first four bytes are the target-endian encoding of 0xfffffffd. The
|
|
// final xx gives AddrSize, the width of a full-width address.
|
|
//
|
|
// After that header, each entry is encoded as follows.
|
|
//
|
|
// 1-byte type (0-51 + two flag bits)
|
|
// AddrSize-byte value, host-endian OR varint-encoded value
|
|
// AddrSize-byte Go type address OR nothing
|
|
// [n] name, terminated as before
|
|
//
|
|
// The type byte comes first, but 'A' encodes as 0 and 'a' as 26, so that
|
|
// the type itself is only in the low 6 bits. The upper two bits specify
|
|
// the format of the next two fields. If the 0x40 bit is set, the value
|
|
// is encoded as an full-width 4- or 8-byte target-endian word. Otherwise
|
|
// the value is a varint-encoded number. If the 0x80 bit is set, the Go
|
|
// type is present, again as a 4- or 8-byte target-endian word. If not,
|
|
// there is no Go type in this entry. The NUL-terminated name ends the
|
|
// entry.
|
|
|
|
#include "runtime.h"
|
|
#include "defs_GOOS_GOARCH.h"
|
|
#include "os_GOOS.h"
|
|
#include "arch_GOARCH.h"
|
|
#include "malloc.h"
|
|
|
|
extern byte pclntab[], epclntab[], symtab[], esymtab[];
|
|
|
|
typedef struct Sym Sym;
|
|
struct Sym
|
|
{
|
|
uintptr value;
|
|
byte symtype;
|
|
byte *name;
|
|
// byte *gotype;
|
|
};
|
|
|
|
static uintptr mainoffset;
|
|
|
|
extern void main·main(void);
|
|
|
|
static uintptr
|
|
readword(byte **pp, byte *ep)
|
|
{
|
|
byte *p;
|
|
|
|
p = *pp;
|
|
if(ep - p < sizeof(void*)) {
|
|
*pp = ep;
|
|
return 0;
|
|
}
|
|
*pp = p + sizeof(void*);
|
|
|
|
// Hairy, but only one of these four cases gets compiled.
|
|
if(sizeof(void*) == 8) {
|
|
if(BigEndian) {
|
|
return ((uint64)p[0]<<56) | ((uint64)p[1]<<48) | ((uint64)p[2]<<40) | ((uint64)p[3]<<32) |
|
|
((uint64)p[4]<<24) | ((uint64)p[5]<<16) | ((uint64)p[6]<<8) | ((uint64)p[7]);
|
|
}
|
|
return ((uint64)p[7]<<56) | ((uint64)p[6]<<48) | ((uint64)p[5]<<40) | ((uint64)p[4]<<32) |
|
|
((uint64)p[3]<<24) | ((uint64)p[2]<<16) | ((uint64)p[1]<<8) | ((uint64)p[0]);
|
|
}
|
|
if(BigEndian) {
|
|
return ((uint32)p[0]<<24) | ((uint32)p[1]<<16) | ((uint32)p[2]<<8) | ((uint32)p[3]);
|
|
}
|
|
return ((uint32)p[3]<<24) | ((uint32)p[2]<<16) | ((uint32)p[1]<<8) | ((uint32)p[0]);
|
|
}
|
|
|
|
// Walk over symtab, calling fn(&s) for each symbol.
|
|
static void
|
|
walksymtab(void (*fn)(Sym*))
|
|
{
|
|
byte *p, *ep, *q;
|
|
Sym s;
|
|
int32 widevalue, havetype, shift;
|
|
|
|
p = symtab;
|
|
ep = esymtab;
|
|
|
|
// Table must begin with correct magic number.
|
|
if(ep - p < 8 || p[4] != 0x00 || p[5] != 0x00 || p[6] != 0x00 || p[7] != sizeof(void*))
|
|
return;
|
|
if(BigEndian) {
|
|
if(p[0] != 0xff || p[1] != 0xff || p[2] != 0xff || p[3] != 0xfd)
|
|
return;
|
|
} else {
|
|
if(p[0] != 0xfd || p[1] != 0xff || p[2] != 0xff || p[3] != 0xff)
|
|
return;
|
|
}
|
|
p += 8;
|
|
|
|
while(p < ep) {
|
|
s.symtype = p[0]&0x3F;
|
|
widevalue = p[0]&0x40;
|
|
havetype = p[0]&0x80;
|
|
if(s.symtype < 26)
|
|
s.symtype += 'A';
|
|
else
|
|
s.symtype += 'a' - 26;
|
|
p++;
|
|
|
|
// Value, either full-width or varint-encoded.
|
|
if(widevalue) {
|
|
s.value = readword(&p, ep);
|
|
} else {
|
|
s.value = 0;
|
|
shift = 0;
|
|
while(p < ep && (p[0]&0x80) != 0) {
|
|
s.value |= (uintptr)(p[0]&0x7F)<<shift;
|
|
shift += 7;
|
|
p++;
|
|
}
|
|
if(p >= ep)
|
|
break;
|
|
s.value |= (uintptr)p[0]<<shift;
|
|
p++;
|
|
}
|
|
|
|
// Go type, if present. Ignored but must skip over.
|
|
if(havetype)
|
|
readword(&p, ep);
|
|
|
|
// Name.
|
|
if(ep - p < 2)
|
|
break;
|
|
|
|
s.name = p;
|
|
if(s.symtype == 'z' || s.symtype == 'Z') {
|
|
// path reference string - skip first byte,
|
|
// then 2-byte pairs ending at two zeros.
|
|
q = p+1;
|
|
for(;;) {
|
|
if(q+2 > ep)
|
|
return;
|
|
if(q[0] == '\0' && q[1] == '\0')
|
|
break;
|
|
q += 2;
|
|
}
|
|
p = q+2;
|
|
}else{
|
|
q = runtime·mchr(p, '\0', ep);
|
|
if(q == nil)
|
|
break;
|
|
p = q+1;
|
|
}
|
|
|
|
fn(&s);
|
|
}
|
|
}
|
|
|
|
// Symtab walker; accumulates info about functions.
|
|
|
|
static Func *func;
|
|
static int32 nfunc;
|
|
|
|
static byte **fname;
|
|
static int32 nfname;
|
|
|
|
static uintptr lastvalue;
|
|
|
|
static void
|
|
dofunc(Sym *sym)
|
|
{
|
|
Func *f;
|
|
uintgo cap;
|
|
|
|
switch(sym->symtype) {
|
|
case 't':
|
|
case 'T':
|
|
case 'l':
|
|
case 'L':
|
|
if(runtime·strcmp(sym->name, (byte*)"etext") == 0)
|
|
break;
|
|
if(sym->value < lastvalue) {
|
|
runtime·printf("runtime: symbols out of order: %p before %p\n", lastvalue, sym->value);
|
|
runtime·throw("malformed symbol table");
|
|
}
|
|
lastvalue = sym->value;
|
|
if(func == nil) {
|
|
nfunc++;
|
|
break;
|
|
}
|
|
f = &func[nfunc++];
|
|
f->name = runtime·gostringnocopy(sym->name);
|
|
f->entry = sym->value;
|
|
if(sym->symtype == 'L' || sym->symtype == 'l')
|
|
f->frame = -sizeof(uintptr);
|
|
break;
|
|
case 'm':
|
|
if(nfunc <= 0 || func == nil)
|
|
break;
|
|
if(runtime·strcmp(sym->name, (byte*)".frame") == 0)
|
|
func[nfunc-1].frame = sym->value;
|
|
else if(runtime·strcmp(sym->name, (byte*)".locals") == 0)
|
|
func[nfunc-1].locals = sym->value;
|
|
else if(runtime·strcmp(sym->name, (byte*)".args") == 0)
|
|
func[nfunc-1].args = sym->value;
|
|
else if(runtime·strcmp(sym->name, (byte*)".nptrs") == 0) {
|
|
if(sym->value != func[nfunc-1].args/sizeof(uintptr)) {
|
|
runtime·printf("runtime: pointer map size and argument size disagree\n");
|
|
runtime·throw("mangled symbol table");
|
|
}
|
|
cap = ROUND(sym->value, 32) / 32;
|
|
func[nfunc-1].ptrs.array = runtime·persistentalloc(cap*sizeof(uint32), sizeof(uint32));
|
|
func[nfunc-1].ptrs.len = 0;
|
|
func[nfunc-1].ptrs.cap = cap;
|
|
} else if(runtime·strcmp(sym->name, (byte*)".ptrs") == 0) {
|
|
if(func[nfunc-1].ptrs.len >= func[nfunc-1].ptrs.cap) {
|
|
runtime·printf("runtime: more pointer map entries read than argument words\n");
|
|
runtime·throw("mangled symbol table");
|
|
}
|
|
((uint32*)func[nfunc-1].ptrs.array)[func[nfunc-1].ptrs.len++] = sym->value;
|
|
} else {
|
|
runtime·printf("runtime: invalid '%c' symbol named '%s'\n", (int8)sym->symtype, sym->name);
|
|
runtime·throw("mangled symbol table");
|
|
}
|
|
break;
|
|
case 'f':
|
|
if(fname == nil) {
|
|
if(sym->value >= nfname) {
|
|
if(sym->value >= 0x10000) {
|
|
runtime·printf("runtime: invalid symbol file index %p\n", sym->value);
|
|
runtime·throw("mangled symbol table");
|
|
}
|
|
nfname = sym->value+1;
|
|
}
|
|
break;
|
|
}
|
|
fname[sym->value] = sym->name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// put together the path name for a z entry.
|
|
// the f entries have been accumulated into fname already.
|
|
// returns the length of the path name.
|
|
static int32
|
|
makepath(byte *buf, int32 nbuf, byte *path)
|
|
{
|
|
int32 n, len;
|
|
byte *p, *ep, *q;
|
|
|
|
if(nbuf <= 0)
|
|
return 0;
|
|
|
|
p = buf;
|
|
ep = buf + nbuf;
|
|
*p = '\0';
|
|
for(;;) {
|
|
if(path[0] == 0 && path[1] == 0)
|
|
break;
|
|
n = (path[0]<<8) | path[1];
|
|
path += 2;
|
|
if(n >= nfname)
|
|
break;
|
|
q = fname[n];
|
|
len = runtime·findnull(q);
|
|
if(p+1+len >= ep)
|
|
break;
|
|
if(p > buf && p[-1] != '/')
|
|
*p++ = '/';
|
|
runtime·memmove(p, q, len+1);
|
|
p += len;
|
|
}
|
|
return p - buf;
|
|
}
|
|
|
|
static String
|
|
gostringn(byte *p, int32 l)
|
|
{
|
|
String s;
|
|
|
|
if(l == 0)
|
|
return runtime·emptystring;
|
|
s.len = l;
|
|
s.str = runtime·persistentalloc(l, 1);
|
|
runtime·memmove(s.str, p, l);
|
|
return s;
|
|
}
|
|
|
|
static struct
|
|
{
|
|
String srcstring;
|
|
int32 aline;
|
|
int32 delta;
|
|
} *files;
|
|
|
|
enum { maxfiles = 200 };
|
|
|
|
// walk symtab accumulating path names for use by pc/ln table.
|
|
// don't need the full generality of the z entry history stack because
|
|
// there are no includes in go (and only sensible includes in our c);
|
|
// assume code only appear in top-level files.
|
|
static void
|
|
dosrcline(Sym *sym)
|
|
{
|
|
#pragma dataflag 16 // no pointers
|
|
static byte srcbuf[1000];
|
|
static int32 incstart;
|
|
static int32 nfunc, nfile, nhist;
|
|
Func *f;
|
|
int32 i, l;
|
|
|
|
switch(sym->symtype) {
|
|
case 't':
|
|
case 'T':
|
|
if(runtime·strcmp(sym->name, (byte*)"etext") == 0)
|
|
break;
|
|
f = &func[nfunc++];
|
|
// find source file
|
|
for(i = 0; i < nfile - 1; i++) {
|
|
if (files[i+1].aline > f->ln0)
|
|
break;
|
|
}
|
|
f->src = files[i].srcstring;
|
|
f->ln0 -= files[i].delta;
|
|
break;
|
|
case 'z':
|
|
if(sym->value == 1) {
|
|
// entry for main source file for a new object.
|
|
l = makepath(srcbuf, sizeof srcbuf, sym->name+1);
|
|
nhist = 0;
|
|
nfile = 0;
|
|
if(nfile == maxfiles)
|
|
return;
|
|
files[nfile].srcstring = gostringn(srcbuf, l);
|
|
files[nfile].aline = 0;
|
|
files[nfile++].delta = 0;
|
|
} else {
|
|
// push or pop of included file.
|
|
l = makepath(srcbuf, sizeof srcbuf, sym->name+1);
|
|
if(srcbuf[0] != '\0') {
|
|
if(nhist++ == 0)
|
|
incstart = sym->value;
|
|
if(nhist == 0 && nfile < maxfiles) {
|
|
// new top-level file
|
|
files[nfile].srcstring = gostringn(srcbuf, l);
|
|
files[nfile].aline = sym->value;
|
|
// this is "line 0"
|
|
files[nfile++].delta = sym->value - 1;
|
|
}
|
|
}else{
|
|
if(--nhist == 0)
|
|
files[nfile-1].delta += sym->value - incstart;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Interpret pc/ln table, saving the subpiece for each func.
|
|
static void
|
|
splitpcln(void)
|
|
{
|
|
int32 line;
|
|
uintptr pc;
|
|
byte *p, *ep;
|
|
Func *f, *ef;
|
|
int32 pcquant;
|
|
|
|
if(pclntab == epclntab || nfunc == 0)
|
|
return;
|
|
|
|
switch(thechar) {
|
|
case '5':
|
|
pcquant = 4;
|
|
break;
|
|
default: // 6, 8
|
|
pcquant = 1;
|
|
break;
|
|
}
|
|
|
|
// pc/ln table bounds
|
|
p = pclntab;
|
|
ep = epclntab;
|
|
|
|
f = func;
|
|
ef = func + nfunc;
|
|
pc = func[0].entry; // text base
|
|
f->pcln.array = p;
|
|
f->pc0 = pc;
|
|
line = 0;
|
|
for(;;) {
|
|
while(p < ep && *p > 128)
|
|
pc += pcquant * (*p++ - 128);
|
|
// runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line);
|
|
if(*p == 0) {
|
|
if(p+5 > ep)
|
|
break;
|
|
// 4 byte add to line
|
|
line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
|
|
p += 5;
|
|
} else if(*p <= 64)
|
|
line += *p++;
|
|
else
|
|
line -= *p++ - 64;
|
|
|
|
// pc, line now match.
|
|
// Because the state machine begins at pc==entry and line==0,
|
|
// it can happen - just at the beginning! - that the update may
|
|
// have updated line but left pc alone, to tell us the true line
|
|
// number for pc==entry. In that case, update f->ln0.
|
|
// Having the correct initial line number is important for choosing
|
|
// the correct file in dosrcline above.
|
|
if(f == func && pc == f->pc0) {
|
|
f->pcln.array = p;
|
|
f->pc0 = pc + pcquant;
|
|
f->ln0 = line;
|
|
}
|
|
|
|
if(f < ef && pc >= (f+1)->entry) {
|
|
f->pcln.len = p - f->pcln.array;
|
|
f->pcln.cap = f->pcln.len;
|
|
do
|
|
f++;
|
|
while(f < ef && pc >= (f+1)->entry);
|
|
f->pcln.array = p;
|
|
// pc0 and ln0 are the starting values for
|
|
// the loop over f->pcln, so pc must be
|
|
// adjusted by the same pcquant update
|
|
// that we're going to do as we continue our loop.
|
|
f->pc0 = pc + pcquant;
|
|
f->ln0 = line;
|
|
}
|
|
|
|
pc += pcquant;
|
|
}
|
|
if(f < ef) {
|
|
f->pcln.len = p - f->pcln.array;
|
|
f->pcln.cap = f->pcln.len;
|
|
}
|
|
}
|
|
|
|
|
|
// Return actual file line number for targetpc in func f.
|
|
// (Source file is f->src.)
|
|
// NOTE(rsc): If you edit this function, also edit extern.go:/FileLine
|
|
int32
|
|
runtime·funcline(Func *f, uintptr targetpc)
|
|
{
|
|
byte *p, *ep;
|
|
uintptr pc;
|
|
int32 line;
|
|
int32 pcquant;
|
|
|
|
enum {
|
|
debug = 0
|
|
};
|
|
|
|
switch(thechar) {
|
|
case '5':
|
|
pcquant = 4;
|
|
break;
|
|
default: // 6, 8
|
|
pcquant = 1;
|
|
break;
|
|
}
|
|
|
|
p = f->pcln.array;
|
|
ep = p + f->pcln.len;
|
|
pc = f->pc0;
|
|
line = f->ln0;
|
|
if(debug && !runtime·panicking)
|
|
runtime·printf("funcline start pc=%p targetpc=%p line=%d tab=%p+%d\n",
|
|
pc, targetpc, line, p, (int32)f->pcln.len);
|
|
for(;;) {
|
|
// Table is a sequence of updates.
|
|
|
|
// Each update says first how to adjust the pc,
|
|
// in possibly multiple instructions...
|
|
while(p < ep && *p > 128)
|
|
pc += pcquant * (*p++ - 128);
|
|
|
|
if(debug && !runtime·panicking)
|
|
runtime·printf("pc<%p targetpc=%p line=%d\n", pc, targetpc, line);
|
|
|
|
// If the pc has advanced too far or we're out of data,
|
|
// stop and the last known line number.
|
|
if(pc > targetpc || p >= ep)
|
|
break;
|
|
|
|
// ... and then how to adjust the line number,
|
|
// in a single instruction.
|
|
if(*p == 0) {
|
|
if(p+5 > ep)
|
|
break;
|
|
line += (p[1]<<24) | (p[2]<<16) | (p[3]<<8) | p[4];
|
|
p += 5;
|
|
} else if(*p <= 64)
|
|
line += *p++;
|
|
else
|
|
line -= *p++ - 64;
|
|
// Now pc, line pair is consistent.
|
|
if(debug && !runtime·panicking)
|
|
runtime·printf("pc=%p targetpc=%p line=%d\n", pc, targetpc, line);
|
|
|
|
// PC increments implicitly on each iteration.
|
|
pc += pcquant;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
void
|
|
runtime·funcline_go(Func *f, uintptr targetpc, String retfile, intgo retline)
|
|
{
|
|
retfile = f->src;
|
|
retline = runtime·funcline(f, targetpc);
|
|
FLUSH(&retfile);
|
|
FLUSH(&retline);
|
|
}
|
|
|
|
void
|
|
runtime·symtabinit(void)
|
|
{
|
|
extern byte etext[];
|
|
|
|
if(func != nil)
|
|
return;
|
|
|
|
// Memory profiling uses this code;
|
|
// can deadlock if the profiler ends
|
|
// up back here.
|
|
m->nomemprof++;
|
|
|
|
// count funcs, fnames
|
|
nfunc = 0;
|
|
nfname = 0;
|
|
lastvalue = 0;
|
|
walksymtab(dofunc);
|
|
|
|
// Initialize tables.
|
|
// Memory obtained from runtime·persistentalloc() is not scanned by GC,
|
|
// this is fine because all pointers either point into sections of the executable
|
|
// or also obtained from persistentalloc().
|
|
func = runtime·persistentalloc((nfunc+1)*sizeof func[0], 0);
|
|
func[nfunc].entry = (uint64)etext;
|
|
fname = runtime·persistentalloc(nfname*sizeof fname[0], 0);
|
|
nfunc = 0;
|
|
lastvalue = 0;
|
|
walksymtab(dofunc);
|
|
|
|
// split pc/ln table by func
|
|
splitpcln();
|
|
|
|
// record src file and line info for each func
|
|
files = runtime·malloc(maxfiles * sizeof(files[0]));
|
|
walksymtab(dosrcline);
|
|
files = nil;
|
|
|
|
m->nomemprof--;
|
|
}
|
|
|
|
Func*
|
|
runtime·findfunc(uintptr addr)
|
|
{
|
|
Func *f;
|
|
int32 nf, n;
|
|
|
|
if(nfunc == 0)
|
|
return nil;
|
|
if(addr < func[0].entry || addr >= func[nfunc].entry)
|
|
return nil;
|
|
|
|
// binary search to find func with entry <= addr.
|
|
f = func;
|
|
nf = nfunc;
|
|
while(nf > 0) {
|
|
n = nf/2;
|
|
if(f[n].entry <= addr && addr < f[n+1].entry)
|
|
return &f[n];
|
|
else if(addr < f[n].entry)
|
|
nf = n;
|
|
else {
|
|
f += n+1;
|
|
nf -= n+1;
|
|
}
|
|
}
|
|
|
|
// can't get here -- we already checked above
|
|
// that the address was in the table bounds.
|
|
// this can only happen if the table isn't sorted
|
|
// by address or if the binary search above is buggy.
|
|
runtime·prints("findfunc unreachable\n");
|
|
return nil;
|
|
}
|
|
|
|
static bool
|
|
hasprefix(String s, int8 *p)
|
|
{
|
|
int32 i;
|
|
|
|
for(i=0; i<s.len; i++) {
|
|
if(p[i] == 0)
|
|
return 1;
|
|
if(p[i] != s.str[i])
|
|
return 0;
|
|
}
|
|
return p[i] == 0;
|
|
}
|
|
|
|
static bool
|
|
contains(String s, int8 *p)
|
|
{
|
|
int32 i;
|
|
|
|
if(p[0] == 0)
|
|
return 1;
|
|
for(i=0; i<s.len; i++) {
|
|
if(s.str[i] != p[0])
|
|
continue;
|
|
if(hasprefix((String){s.str + i, s.len - i}, p))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
runtime·showframe(Func *f, G *gp)
|
|
{
|
|
static int32 traceback = -1;
|
|
|
|
if(m->throwing && gp != nil && (gp == m->curg || gp == m->caughtsig))
|
|
return 1;
|
|
if(traceback < 0)
|
|
traceback = runtime·gotraceback(nil);
|
|
return traceback > 1 || f != nil && contains(f->name, ".") && !hasprefix(f->name, "runtime.");
|
|
}
|