| // 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" |
| #include "../../cmd/ld/textflag.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, |
| TagMemProf = 16, |
| TagAllocSample = 17, |
| |
| 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, |
| }; |
| #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]; |
| }; |
| 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); |
| 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 - (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; |
| 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 |
| 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; |
| |
| runtime·iterate_memprof(dumpmemprof_callback); |
| |
| allspans = runtime·mheap.allspans; |
| for(spanidx=0; spanidx<runtime·mheap.nspan; spanidx++) { |
| s = allspans[spanidx]; |
| if(s->state != 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->offset); |
| dumpint(TagAllocSample); |
| dumpint((uintptr)p); |
| dumpint((uintptr)spp->b); |
| } |
| } |
| } |
| |
| static void |
| mdump(G *gp) |
| { |
| 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.3 heap dump\n"; |
| write(hdr, runtime·findnull(hdr)); |
| dumpparams(); |
| dumpitabs(); |
| dumpobjs(); |
| dumpgs(); |
| dumpms(); |
| dumproots(); |
| dumpmemstats(); |
| dumpmemprof(); |
| dumpint(TagEOF); |
| flush(); |
| |
| gp->param = nil; |
| gp->status = Grunning; |
| runtime·gogo(&gp->sched); |
| } |
| |
| void |
| runtime∕debug·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; |
| } |
| } |
| } |