blob: e66dd6a7aed999eabe5ff5c5eac3b571f82a6d5d [file] [log] [blame]
// Copyright 2014 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.
// Implementation of runtime/debug.WriteHeapDump. Writes all
// objects in the heap plus additional info (roots, threads,
// finalizers, etc.) to a file.
// The format of the dumped file is described at
// http://code.google.com/p/go-wiki/wiki/heapdump13
#include "runtime.h"
#include "arch_GOARCH.h"
#include "malloc.h"
#include "mgc0.h"
#include "type.h"
#include "typekind.h"
#include "funcdata.h"
#include "zaexperiment.h"
extern byte data[];
extern byte edata[];
extern byte bss[];
extern byte ebss[];
extern byte gcdata[];
extern byte gcbss[];
enum {
FieldKindEol = 0,
FieldKindPtr = 1,
FieldKindString = 2,
FieldKindSlice = 3,
FieldKindIface = 4,
FieldKindEface = 5,
TagEOF = 0,
TagObject = 1,
TagOtherRoot = 2,
TagType = 3,
TagGoRoutine = 4,
TagStackFrame = 5,
TagParams = 6,
TagFinalizer = 7,
TagItab = 8,
TagOSThread = 9,
TagMemStats = 10,
TagQueuedFinalizer = 11,
TagData = 12,
TagBss = 13,
TagDefer = 14,
TagPanic = 15,
TypeInfo_Conservative = 127,
};
static uintptr* playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg);
static void dumpfields(uintptr *prog);
static void dumpefacetypes(void *obj, uintptr size, Type *type, uintptr kind);
static void dumpbvtypes(BitVector *bv, byte *base);
// fd to write the dump to.
static uintptr dumpfd;
// buffer of pending write data
enum {
BufSize = 4096,
};
static byte buf[BufSize];
static uintptr nbuf;
static void
write(byte *data, uintptr len)
{
if(len + nbuf <= BufSize) {
runtime·memmove(buf + nbuf, data, len);
nbuf += len;
return;
}
runtime·write(dumpfd, buf, nbuf);
if(len >= BufSize) {
runtime·write(dumpfd, data, len);
nbuf = 0;
} else {
runtime·memmove(buf, data, len);
nbuf = len;
}
}
static void
flush(void)
{
runtime·write(dumpfd, buf, nbuf);
nbuf = 0;
}
// Cache of types that have been serialized already.
// We use a type's hash field to pick a bucket.
// Inside a bucket, we keep a list of types that
// have been serialized so far, most recently used first.
// Note: when a bucket overflows we may end up
// serializing a type more than once. That's ok.
enum {
TypeCacheBuckets = 256, // must be a power of 2
TypeCacheAssoc = 4,
};
typedef struct TypeCacheBucket TypeCacheBucket;
struct TypeCacheBucket {
Type *t[TypeCacheAssoc];
};
static TypeCacheBucket typecache[TypeCacheBuckets];
// dump a uint64 in a varint format parseable by encoding/binary
static void
dumpint(uint64 v)
{
byte buf[10];
int32 n;
n = 0;
while(v >= 0x80) {
buf[n++] = v | 0x80;
v >>= 7;
}
buf[n++] = v;
write(buf, n);
}
static void
dumpbool(bool b)
{
dumpint(b ? 1 : 0);
}
// dump varint uint64 length followed by memory contents
static void
dumpmemrange(byte *data, uintptr len)
{
dumpint(len);
write(data, len);
}
static void
dumpstr(String s)
{
dumpmemrange(s.str, s.len);
}
static void
dumpcstr(int8 *c)
{
dumpmemrange((byte*)c, runtime·findnull((byte*)c));
}
// dump information for a type
static void
dumptype(Type *t)
{
TypeCacheBucket *b;
int32 i, j;
if(t == nil) {
return;
}
// If we've definitely serialized the type before,
// no need to do it again.
b = &typecache[t->hash & (TypeCacheBuckets-1)];
if(t == b->t[0]) return;
for(i = 1; i < TypeCacheAssoc; i++) {
if(t == b->t[i]) {
// Move-to-front
for(j = i; j > 0; j--) {
b->t[j] = b->t[j-1];
}
b->t[0] = t;
return;
}
}
// Might not have been dumped yet. Dump it and
// remember we did so.
for(j = TypeCacheAssoc-1; j > 0; j--) {
b->t[j] = b->t[j-1];
}
b->t[0] = t;
// dump the type
dumpint(TagType);
dumpint((uintptr)t);
dumpint(t->size);
if(t->x == nil || t->x->pkgPath == nil || t->x->name == nil) {
dumpstr(*t->string);
} else {
dumpint(t->x->pkgPath->len + 1 + t->x->name->len);
write(t->x->pkgPath->str, t->x->pkgPath->len);
write((byte*)".", 1);
write(t->x->name->str, t->x->name->len);
}
dumpbool(t->size > PtrSize || (t->kind & KindNoPointers) == 0);
dumpfields((uintptr*)t->gc + 1);
}
// returns true if object is scannable
static bool
scannable(byte *obj)
{
uintptr *b, off, shift;
off = (uintptr*)obj - (uintptr*)runtime·mheap.arena_start; // word offset
b = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
shift = off % wordsPerBitmapWord;
return ((*b >> shift) & bitScan) != 0;
}
// dump an object
static void
dumpobj(byte *obj, uintptr size, Type *type, uintptr kind)
{
if(type != nil) {
dumptype(type);
dumpefacetypes(obj, size, type, kind);
}
dumpint(TagObject);
dumpint((uintptr)obj);
dumpint((uintptr)type);
dumpint(kind);
dumpmemrange(obj, size);
}
static void
dumpotherroot(int8 *description, byte *to)
{
dumpint(TagOtherRoot);
dumpcstr(description);
dumpint((uintptr)to);
}
static void
dumpfinalizer(byte *obj, FuncVal *fn, Type* fint, PtrType *ot)
{
dumpint(TagFinalizer);
dumpint((uintptr)obj);
dumpint((uintptr)fn);
dumpint((uintptr)fn->fn);
dumpint((uintptr)fint);
dumpint((uintptr)ot);
}
typedef struct ChildInfo ChildInfo;
struct ChildInfo {
// Information passed up from the callee frame about
// the layout of the outargs region.
uintptr argoff; // where the arguments start in the frame
uintptr arglen; // size of args region
BitVector args; // if args.n >= 0, pointer map of args region
byte *sp; // callee sp
uintptr depth; // depth in call stack (0 == most recent)
};
// dump kinds & offsets of interesting fields in bv
static void
dumpbv(BitVector *bv, uintptr offset)
{
uintptr i;
for(i = 0; i < bv->n; i += BitsPerPointer) {
switch(bv->data[i/32] >> i%32 & 3) {
case BitsDead:
case BitsScalar:
break;
case BitsPointer:
dumpint(FieldKindPtr);
dumpint(offset + i / BitsPerPointer * PtrSize);
break;
case BitsMultiWord:
switch(bv->data[(i+BitsPerPointer)/32] >> (i+BitsPerPointer)%32 & 3) {
case BitsString:
dumpint(FieldKindString);
dumpint(offset + i / BitsPerPointer * PtrSize);
i += BitsPerPointer;
break;
case BitsSlice:
dumpint(FieldKindSlice);
dumpint(offset + i / BitsPerPointer * PtrSize);
i += 2 * BitsPerPointer;
break;
case BitsIface:
dumpint(FieldKindIface);
dumpint(offset + i / BitsPerPointer * PtrSize);
i += BitsPerPointer;
break;
case BitsEface:
dumpint(FieldKindEface);
dumpint(offset + i / BitsPerPointer * PtrSize);
i += BitsPerPointer;
break;
}
}
}
}
static bool
dumpframe(Stkframe *s, void *arg)
{
Func *f;
ChildInfo *child;
uintptr pc, off, size;
int32 pcdata;
StackMap *stackmap;
int8 *name;
BitVector bv;
child = (ChildInfo*)arg;
f = s->fn;
// Figure out what we can about our stack map
pc = s->pc;
if(pc != f->entry)
pc--;
pcdata = runtime·pcdatavalue(f, PCDATA_StackMapIndex, pc);
if(pcdata == -1) {
// We do not have a valid pcdata value but there might be a
// stackmap for this function. It is likely that we are looking
// at the function prologue, assume so and hope for the best.
pcdata = 0;
}
stackmap = runtime·funcdata(f, FUNCDATA_LocalsPointerMaps);
// Dump any types we will need to resolve Efaces.
if(child->args.n >= 0)
dumpbvtypes(&child->args, (byte*)s->sp + child->argoff);
if(stackmap != nil && stackmap->n > 0) {
bv = runtime·stackmapdata(stackmap, pcdata);
dumpbvtypes(&bv, s->varp - bv.n / BitsPerPointer * PtrSize);
} else {
bv.n = -1;
}
// Dump main body of stack frame.
dumpint(TagStackFrame);
dumpint(s->sp); // lowest address in frame
dumpint(child->depth); // # of frames deep on the stack
dumpint((uintptr)child->sp); // sp of child, or 0 if bottom of stack
dumpmemrange((byte*)s->sp, s->fp - s->sp); // frame contents
dumpint(f->entry);
dumpint(s->pc);
name = runtime·funcname(f);
if(name == nil)
name = "unknown function";
dumpcstr(name);
// Dump fields in the outargs section
if(child->args.n >= 0) {
dumpbv(&child->args, child->argoff);
} else {
// conservative - everything might be a pointer
for(off = child->argoff; off < child->argoff + child->arglen; off += PtrSize) {
dumpint(FieldKindPtr);
dumpint(off);
}
}
// Dump fields in the local vars section
if(stackmap == nil) {
// No locals information, dump everything.
for(off = child->arglen; off < s->varp - (byte*)s->sp; off += PtrSize) {
dumpint(FieldKindPtr);
dumpint(off);
}
} else if(stackmap->n < 0) {
// Locals size information, dump just the locals.
size = -stackmap->n;
for(off = s->varp - size - (byte*)s->sp; off < s->varp - (byte*)s->sp; off += PtrSize) {
dumpint(FieldKindPtr);
dumpint(off);
}
} else if(stackmap->n > 0) {
// Locals bitmap information, scan just the pointers in
// locals.
dumpbv(&bv, s->varp - bv.n / BitsPerPointer * PtrSize - (byte*)s->sp);
}
dumpint(FieldKindEol);
// Record arg info for parent.
child->argoff = s->argp - (byte*)s->fp;
child->arglen = s->arglen;
child->sp = (byte*)s->sp;
child->depth++;
stackmap = runtime·funcdata(f, FUNCDATA_ArgsPointerMaps);
if(stackmap != nil)
child->args = runtime·stackmapdata(stackmap, pcdata);
else
child->args.n = -1;
return true;
}
static void
dumpgoroutine(G *gp)
{
uintptr sp, pc, lr;
ChildInfo child;
Defer *d;
Panic *p;
if(gp->syscallstack != (uintptr)nil) {
sp = gp->syscallsp;
pc = gp->syscallpc;
lr = 0;
} else {
sp = gp->sched.sp;
pc = gp->sched.pc;
lr = gp->sched.lr;
}
dumpint(TagGoRoutine);
dumpint((uintptr)gp);
dumpint((uintptr)sp);
dumpint(gp->goid);
dumpint(gp->gopc);
dumpint(gp->status);
dumpbool(gp->issystem);
dumpbool(gp->isbackground);
dumpint(gp->waitsince);
dumpcstr(gp->waitreason);
dumpint((uintptr)gp->sched.ctxt);
dumpint((uintptr)gp->m);
dumpint((uintptr)gp->defer);
dumpint((uintptr)gp->panic);
// dump stack
child.args.n = -1;
child.arglen = 0;
child.sp = nil;
child.depth = 0;
if(!ScanStackByFrames)
runtime·throw("need frame info to dump stacks");
runtime·gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, dumpframe, &child, false);
// dump defer & panic records
for(d = gp->defer; d != nil; d = d->link) {
dumpint(TagDefer);
dumpint((uintptr)d);
dumpint((uintptr)gp);
dumpint((uintptr)d->argp);
dumpint((uintptr)d->pc);
dumpint((uintptr)d->fn);
dumpint((uintptr)d->fn->fn);
dumpint((uintptr)d->link);
}
for (p = gp->panic; p != nil; p = p->link) {
dumpint(TagPanic);
dumpint((uintptr)p);
dumpint((uintptr)gp);
dumpint((uintptr)p->arg.type);
dumpint((uintptr)p->arg.data);
dumpint((uintptr)p->defer);
dumpint((uintptr)p->link);
}
}
static void
dumpgs(void)
{
G *gp;
uint32 i;
// goroutines & stacks
for(i = 0; i < runtime·allglen; i++) {
gp = runtime·allg[i];
switch(gp->status){
default:
runtime·printf("unexpected G.status %d\n", gp->status);
runtime·throw("mark - bad status");
case Gdead:
break;
case Grunnable:
case Gsyscall:
case Gwaiting:
dumpgoroutine(gp);
break;
}
}
}
static void
finq_callback(FuncVal *fn, byte *obj, uintptr nret, Type *fint, PtrType *ot)
{
dumpint(TagQueuedFinalizer);
dumpint((uintptr)obj);
dumpint((uintptr)fn);
dumpint((uintptr)fn->fn);
dumpint((uintptr)fint);
dumpint((uintptr)ot);
USED(&nret);
}
static void
dumproots(void)
{
MSpan *s, **allspans;
uint32 spanidx;
Special *sp;
SpecialFinalizer *spf;
byte *p;
// data segment
dumpint(TagData);
dumpint((uintptr)data);
dumpmemrange(data, edata - data);
dumpfields((uintptr*)gcdata + 1);
// bss segment
dumpint(TagBss);
dumpint((uintptr)bss);
dumpmemrange(bss, ebss - bss);
dumpfields((uintptr*)gcbss + 1);
// MSpan.types
allspans = runtime·mheap.allspans;
for(spanidx=0; spanidx<runtime·mheap.nspan; spanidx++) {
s = allspans[spanidx];
if(s->state == MSpanInUse) {
// The garbage collector ignores type pointers stored in MSpan.types:
// - Compiler-generated types are stored outside of heap.
// - The reflect package has runtime-generated types cached in its data structures.
// The garbage collector relies on finding the references via that cache.
switch(s->types.compression) {
case MTypes_Empty:
case MTypes_Single:
break;
case MTypes_Words:
case MTypes_Bytes:
dumpotherroot("runtime type info", (byte*)s->types.data);
break;
}
// Finalizers
for(sp = s->specials; sp != nil; sp = sp->next) {
if(sp->kind != KindSpecialFinalizer)
continue;
spf = (SpecialFinalizer*)sp;
p = (byte*)((s->start << PageShift) + spf->offset);
dumpfinalizer(p, spf->fn, spf->fint, spf->ot);
}
}
}
// Finalizer queue
runtime·iterate_finq(finq_callback);
}
// Bit vector of free marks.
// Needs to be as big as the largest number of objects per span.
static byte free[PageSize/8];
static void
dumpobjs(void)
{
uintptr i, j, size, n, off, shift, *bitp, bits, ti, kind;
MSpan *s;
MLink *l;
byte *p;
Type *t;
for(i = 0; i < runtime·mheap.nspan; i++) {
s = runtime·mheap.allspans[i];
if(s->state != MSpanInUse)
continue;
runtime·MSpan_EnsureSwept(s);
p = (byte*)(s->start << PageShift);
size = s->elemsize;
n = (s->npages << PageShift) / size;
if(n > PageSize/8)
runtime·throw("free array doesn't have enough entries");
for(l = s->freelist; l != nil; l = l->next) {
free[((byte*)l - p) / size] = true;
}
for(j = 0; j < n; j++, p += size) {
if(free[j]) {
free[j] = false;
continue;
}
off = (uintptr*)p - (uintptr*)runtime·mheap.arena_start;
bitp = (uintptr*)runtime·mheap.arena_start - off/wordsPerBitmapWord - 1;
shift = off % wordsPerBitmapWord;
bits = *bitp >> shift;
// Skip FlagNoGC allocations (stacks)
if((bits & bitAllocated) == 0)
continue;
// extract type and kind
ti = runtime·gettype(p);
t = (Type*)(ti & ~(uintptr)(PtrSize-1));
kind = ti & (PtrSize-1);
// dump it
if(kind == TypeInfo_Chan)
t = ((ChanType*)t)->elem; // use element type for chan encoding
if(t == nil && scannable(p))
kind = TypeInfo_Conservative; // special kind for conservatively scanned objects
dumpobj(p, size, t, kind);
}
}
}
static void
dumpparams(void)
{
byte *x;
dumpint(TagParams);
x = (byte*)1;
if(*(byte*)&x == 1)
dumpbool(false); // little-endian ptrs
else
dumpbool(true); // big-endian ptrs
dumpint(PtrSize);
dumpint(runtime·Hchansize);
dumpint((uintptr)runtime·mheap.arena_start);
dumpint((uintptr)runtime·mheap.arena_used);
dumpint(thechar);
dumpcstr(GOEXPERIMENT);
dumpint(runtime·ncpu);
}
static void
itab_callback(Itab *tab)
{
Type *t;
dumpint(TagItab);
dumpint((uintptr)tab);
t = tab->type;
dumpbool(t->size > PtrSize || (t->kind & KindNoPointers) == 0);
}
static void
dumpitabs(void)
{
runtime·iterate_itabs(itab_callback);
}
static void
dumpms(void)
{
M *mp;
for(mp = runtime·allm; mp != nil; mp = mp->alllink) {
dumpint(TagOSThread);
dumpint((uintptr)mp);
dumpint(mp->id);
dumpint(mp->procid);
}
}
static void
dumpmemstats(void)
{
int32 i;
dumpint(TagMemStats);
dumpint(mstats.alloc);
dumpint(mstats.total_alloc);
dumpint(mstats.sys);
dumpint(mstats.nlookup);
dumpint(mstats.nmalloc);
dumpint(mstats.nfree);
dumpint(mstats.heap_alloc);
dumpint(mstats.heap_sys);
dumpint(mstats.heap_idle);
dumpint(mstats.heap_inuse);
dumpint(mstats.heap_released);
dumpint(mstats.heap_objects);
dumpint(mstats.stacks_inuse);
dumpint(mstats.stacks_sys);
dumpint(mstats.mspan_inuse);
dumpint(mstats.mspan_sys);
dumpint(mstats.mcache_inuse);
dumpint(mstats.mcache_sys);
dumpint(mstats.buckhash_sys);
dumpint(mstats.gc_sys);
dumpint(mstats.other_sys);
dumpint(mstats.next_gc);
dumpint(mstats.last_gc);
dumpint(mstats.pause_total_ns);
for(i = 0; i < 256; i++)
dumpint(mstats.pause_ns[i]);
dumpint(mstats.numgc);
}
static void
mdump(G *gp)
{
byte *hdr;
runtime·memclr((byte*)&typecache[0], sizeof(typecache));
hdr = (byte*)"go1.3 heap dump\n";
write(hdr, runtime·findnull(hdr));
dumpparams();
dumpitabs();
dumpobjs();
dumpgs();
dumpms();
dumproots();
dumpmemstats();
dumpint(TagEOF);
flush();
gp->param = nil;
gp->status = Grunning;
runtime·gogo(&gp->sched);
}
void
runtimedebug·WriteHeapDump(uintptr fd)
{
// Stop the world.
runtime·semacquire(&runtime·worldsema, false);
m->gcing = 1;
m->locks++;
runtime·stoptheworld();
// Update stats so we can dump them.
// As a side effect, flushes all the MCaches so the MSpan.freelist
// lists contain all the free objects.
runtime·updatememstats(nil);
// Set dump file.
dumpfd = fd;
// Call dump routine on M stack.
g->status = Gwaiting;
g->waitreason = "dumping heap";
runtime·mcall(mdump);
// Reset dump file.
dumpfd = 0;
// Start up the world again.
m->gcing = 0;
runtime·semrelease(&runtime·worldsema);
runtime·starttheworld();
m->locks--;
}
// Runs the specified gc program. Calls the callback for every
// pointer-like field specified by the program and passes to the
// callback the kind and offset of that field within the object.
// offset is the offset in the object of the start of the program.
// Returns a pointer to the opcode that ended the gc program (either
// GC_END or GC_ARRAY_NEXT).
static uintptr*
playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg)
{
uintptr len, elemsize, i, *end;
for(;;) {
switch(prog[0]) {
case GC_END:
return prog;
case GC_PTR:
callback(arg, FieldKindPtr, offset + prog[1]);
prog += 3;
break;
case GC_APTR:
callback(arg, FieldKindPtr, offset + prog[1]);
prog += 2;
break;
case GC_ARRAY_START:
len = prog[2];
elemsize = prog[3];
end = nil;
for(i = 0; i < len; i++) {
end = playgcprog(offset + prog[1] + i * elemsize, prog + 4, callback, arg);
if(end[0] != GC_ARRAY_NEXT)
runtime·throw("GC_ARRAY_START did not have matching GC_ARRAY_NEXT");
}
prog = end + 1;
break;
case GC_ARRAY_NEXT:
return prog;
case GC_CALL:
playgcprog(offset + prog[1], (uintptr*)((byte*)prog + *(int32*)&prog[2]), callback, arg);
prog += 3;
break;
case GC_CHAN_PTR:
callback(arg, FieldKindPtr, offset + prog[1]);
prog += 3;
break;
case GC_STRING:
callback(arg, FieldKindString, offset + prog[1]);
prog += 2;
break;
case GC_EFACE:
callback(arg, FieldKindEface, offset + prog[1]);
prog += 2;
break;
case GC_IFACE:
callback(arg, FieldKindIface, offset + prog[1]);
prog += 2;
break;
case GC_SLICE:
callback(arg, FieldKindSlice, offset + prog[1]);
prog += 3;
break;
case GC_REGION:
playgcprog(offset + prog[1], (uintptr*)prog[3] + 1, callback, arg);
prog += 4;
break;
default:
runtime·printf("%D\n", (uint64)prog[0]);
runtime·throw("bad gc op");
}
}
}
static void
dump_callback(void *p, uintptr kind, uintptr offset)
{
USED(&p);
dumpint(kind);
dumpint(offset);
}
// dumpint() the kind & offset of each field in an object.
static void
dumpfields(uintptr *prog)
{
playgcprog(0, prog, dump_callback, nil);
dumpint(FieldKindEol);
}
static void
dumpeface_callback(void *p, uintptr kind, uintptr offset)
{
Eface *e;
if(kind != FieldKindEface)
return;
e = (Eface*)((byte*)p + offset);
dumptype(e->type);
}
// The heap dump reader needs to be able to disambiguate
// Eface entries. So it needs to know every type that might
// appear in such an entry. The following two routines accomplish
// that.
// Dump all the types that appear in the type field of
// any Eface contained in obj.
static void
dumpefacetypes(void *obj, uintptr size, Type *type, uintptr kind)
{
uintptr i;
switch(kind) {
case TypeInfo_SingleObject:
playgcprog(0, (uintptr*)type->gc + 1, dumpeface_callback, obj);
break;
case TypeInfo_Array:
for(i = 0; i <= size - type->size; i += type->size)
playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj);
break;
case TypeInfo_Chan:
if(type->size == 0) // channels may have zero-sized objects in them
break;
for(i = runtime·Hchansize; i <= size - type->size; i += type->size)
playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj);
break;
}
}
// Dump all the types that appear in the type field of
// any Eface described by this bit vector.
static void
dumpbvtypes(BitVector *bv, byte *base)
{
uintptr i;
for(i = 0; i < bv->n; i += BitsPerPointer) {
if((bv->data[i/32] >> i%32 & 3) != BitsMultiWord)
continue;
switch(bv->data[(i+BitsPerPointer)/32] >> (i+BitsPerPointer)%32 & 3) {
case BitsString:
case BitsIface:
i += BitsPerPointer;
break;
case BitsSlice:
i += 2 * BitsPerPointer;
break;
case BitsEface:
dumptype(*(Type**)(base + i / BitsPerPointer * PtrSize));
i += BitsPerPointer;
break;
}
}
}