// 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://golang.org/s/go14heapdump. #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" #include "textflag.h" extern byte runtime·data[]; extern byte runtime·edata[]; extern byte runtime·bss[]; extern byte runtime·ebss[]; enum { FieldKindEol = 0, FieldKindPtr = 1, FieldKindIface = 2, FieldKindEface = 3, 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, TagMemProf = 16, TagAllocSample = 17, }; static uintptr* playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg); static void dumpfields(BitVector bv); static void dumpbvtypes(BitVector *bv, byte *base); static BitVector makeheapobjbv(byte *p, uintptr size); // fd to write the dump to. static uintptr dumpfd; #pragma dataflag NOPTR /* tmpbuf not a heap pointer at least */ static byte *tmpbuf; static uintptr tmpbufsize; // buffer of pending write data enum { BufSize = 4096, }; #pragma dataflag NOPTR 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]; }; #pragma dataflag NOPTR /* only initialized and used while world is stopped */ 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->kind & KindDirectIface) == 0 || (t->kind & KindNoPointers) == 0); } // dump an object static void dumpobj(byte *obj, uintptr size, BitVector bv) { dumpbvtypes(&bv, obj); dumpint(TagObject); dumpint((uintptr)obj); dumpmemrange(obj, size); dumpfields(bv); } 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->bytedata[i/8] >> i%8 & 3) { case BitsDead: // BitsDead has already been processed in makeheapobjbv. // We should only see it in stack maps, in which case we should continue processing. break; case BitsScalar: break; case BitsPointer: dumpint(FieldKindPtr); dumpint(offset + i / BitsPerPointer * PtrSize); break; case BitsMultiWord: switch(bv->bytedata[(i+BitsPerPointer)/8] >> (i+BitsPerPointer)%8 & 3) { default: runtime·throw("unexpected garbage collection bits"); 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, (byte*)(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); dumpint(s->continpc); 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 - 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 - s->sp; off < s->varp - 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 - s->sp); } dumpint(FieldKindEol); // Record arg info for parent. child->argoff = s->argp - 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; bool (*fn)(Stkframe*, void*); if(gp->syscallsp != (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(runtime·readgstatus(gp)); dumpbool(gp->issystem); dumpbool(false); // isbackground dumpint(gp->waitsince); dumpstr(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; fn = dumpframe; runtime·gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, &fn, &child, 0); // 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(0); // was p->defer, no longer recorded dumpint((uintptr)p->link); } } static void dumpgs(void) { G *gp; uint32 i; uint32 status; // goroutines & stacks for(i = 0; i < runtime·allglen; i++) { gp = runtime·allg[i]; status = runtime·readgstatus(gp); // The world is stopped so gp will not be in a scan state. switch(status){ default: runtime·printf("runtime: unexpected G.status %d\n", status); runtime·throw("dumpgs in STW - 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 dumpbvtypes(&runtime·gcdatamask, runtime·data); dumpint(TagData); dumpint((uintptr)runtime·data); dumpmemrange(runtime·data, runtime·edata - runtime·data); dumpfields(runtime·gcdatamask); // bss segment dumpbvtypes(&runtime·gcbssmask, runtime·bss); dumpint(TagBss); dumpint((uintptr)runtime·bss); dumpmemrange(runtime·bss, runtime·ebss - runtime·bss); dumpfields(runtime·gcbssmask); // MSpan.types allspans = runtime·mheap.allspans; for(spanidx=0; spanidxstate == MSpanInUse) { // Finalizers for(sp = s->specials; sp != nil; sp = sp->next) { if(sp->kind != KindSpecialFinalizer) continue; spf = (SpecialFinalizer*)sp; p = (byte*)((s->start << PageShift) + spf->special.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. #pragma dataflag NOPTR static byte free[PageSize/8]; static void dumpobjs(void) { uintptr i, j, size, n; MSpan *s; MLink *l; byte *p; for(i = 0; i < runtime·mheap.nspan; i++) { s = runtime·mheap.allspans[i]; if(s->state != MSpanInUse) continue; p = (byte*)(s->start << PageShift); size = s->elemsize; n = (s->npages << PageShift) / size; if(n > nelem(free)) 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; } dumpobj(p, size, makeheapobjbv(p, size)); } } } 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((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; t = tab->type; // Dump a map from itab* to the type of its data field. // We want this map so we can deduce types of interface referents. if((t->kind & KindDirectIface) == 0) { // indirect - data slot is a pointer to t. dumptype(t->ptrto); dumpint(TagItab); dumpint((uintptr)tab); dumpint((uintptr)t->ptrto); } else if((t->kind & KindNoPointers) == 0) { // t is pointer-like - data slot is a t. dumptype(t); dumpint(TagItab); dumpint((uintptr)tab); dumpint((uintptr)t); } else { // Data slot is a scalar. Dump type just for fun. // With pointer-only interfaces, this shouldn't happen. dumptype(t); dumpint(TagItab); dumpint((uintptr)tab); dumpint((uintptr)t); } } static void dumpitabs(void) { void (*fn)(Itab*); fn = itab_callback; runtime·iterate_itabs(&fn); } 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 dumpmemprof_callback(Bucket *b, uintptr nstk, uintptr *stk, uintptr size, uintptr allocs, uintptr frees) { uintptr i, pc; Func *f; byte buf[20]; String file; int32 line; dumpint(TagMemProf); dumpint((uintptr)b); dumpint(size); dumpint(nstk); for(i = 0; i < nstk; i++) { pc = stk[i]; f = runtime·findfunc(pc); if(f == nil) { runtime·snprintf(buf, sizeof(buf), "%X", (uint64)pc); dumpcstr((int8*)buf); dumpcstr("?"); dumpint(0); } else { dumpcstr(runtime·funcname(f)); // TODO: Why do we need to back up to a call instruction here? // Maybe profiler should do this. if(i > 0 && pc > f->entry) { if(thechar == '6' || thechar == '8') pc--; else pc -= 4; // arm, etc } line = runtime·funcline(f, pc, &file); dumpstr(file); dumpint(line); } } dumpint(allocs); dumpint(frees); } static void dumpmemprof(void) { MSpan *s, **allspans; uint32 spanidx; Special *sp; SpecialProfile *spp; byte *p; void (*fn)(Bucket*, uintptr, uintptr*, uintptr, uintptr, uintptr); fn = dumpmemprof_callback; runtime·iterate_memprof(&fn); allspans = runtime·mheap.allspans; for(spanidx=0; spanidxstate != MSpanInUse) continue; for(sp = s->specials; sp != nil; sp = sp->next) { if(sp->kind != KindSpecialProfile) continue; spp = (SpecialProfile*)sp; p = (byte*)((s->start << PageShift) + spp->special.offset); dumpint(TagAllocSample); dumpint((uintptr)p); dumpint((uintptr)spp->b); } } } static void mdump(void) { byte *hdr; uintptr i; MSpan *s; // make sure we're done sweeping for(i = 0; i < runtime·mheap.nspan; i++) { s = runtime·mheap.allspans[i]; if(s->state == MSpanInUse) runtime·MSpan_EnsureSwept(s); } runtime·memclr((byte*)&typecache[0], sizeof(typecache)); hdr = (byte*)"go1.4 heap dump\n"; write(hdr, runtime·findnull(hdr)); dumpparams(); dumpitabs(); dumpobjs(); dumpgs(); dumpms(); dumproots(); dumpmemstats(); dumpmemprof(); dumpint(TagEOF); flush(); } void runtime·writeheapdump_m(void) { uintptr fd; fd = g->m->scalararg[0]; g->m->scalararg[0] = 0; runtime·casgstatus(g->m->curg, Grunning, Gwaiting); g->waitreason = runtime·gostringnocopy((byte*)"dumping heap"); // 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. mdump(); // Reset dump file. dumpfd = 0; if(tmpbuf != nil) { runtime·SysFree(tmpbuf, tmpbufsize, &mstats.other_sys); tmpbuf = nil; tmpbufsize = 0; } runtime·casgstatus(g->m->curg, Gwaiting, Grunning); } // dumpint() the kind & offset of each field in an object. static void dumpfields(BitVector bv) { dumpbv(&bv, 0); dumpint(FieldKindEol); } // 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 routine accomplishes that. // 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->bytedata[i/8] >> i%8 & 3) != BitsMultiWord) continue; switch(bv->bytedata[(i+BitsPerPointer)/8] >> (i+BitsPerPointer)%8 & 3) { default: runtime·throw("unexpected garbage collection bits"); case BitsIface: i += BitsPerPointer; break; case BitsEface: dumptype(*(Type**)(base + i / BitsPerPointer * PtrSize)); i += BitsPerPointer; break; } } } static BitVector makeheapobjbv(byte *p, uintptr size) { uintptr off, nptr, i; byte shift, *bitp, bits; bool mw; // Extend the temp buffer if necessary. nptr = size/PtrSize; if(tmpbufsize < nptr*BitsPerPointer/8+1) { if(tmpbuf != nil) runtime·SysFree(tmpbuf, tmpbufsize, &mstats.other_sys); tmpbufsize = nptr*BitsPerPointer/8+1; tmpbuf = runtime·sysAlloc(tmpbufsize, &mstats.other_sys); if(tmpbuf == nil) runtime·throw("heapdump: out of memory"); } // Copy and compact the bitmap. mw = false; for(i = 0; i < nptr; i++) { off = (uintptr*)(p + i*PtrSize) - (uintptr*)runtime·mheap.arena_start; bitp = runtime·mheap.arena_start - off/wordsPerBitmapByte - 1; shift = (off % wordsPerBitmapByte) * gcBits; bits = (*bitp >> (shift + 2)) & BitsMask; if(!mw && bits == BitsDead) break; // end of heap object mw = !mw && bits == BitsMultiWord; tmpbuf[i*BitsPerPointer/8] &= ~(BitsMask<<((i*BitsPerPointer)%8)); tmpbuf[i*BitsPerPointer/8] |= bits<<((i*BitsPerPointer)%8); } return (BitVector){i*BitsPerPointer, tmpbuf}; }