777 lines
17 KiB
C
777 lines
17 KiB
C
|
// 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.h"
|
||
|
#include "malloc.h"
|
||
|
#include "mgc0.h"
|
||
|
#include "go-type.h"
|
||
|
#include "go-defer.h"
|
||
|
#include "go-panic.h"
|
||
|
|
||
|
#define hash __hash
|
||
|
#define KindNoPointers GO_NO_POINTERS
|
||
|
|
||
|
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, const Type *type, uintptr kind);
|
||
|
|
||
|
// 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
|
||
|
hwrite(const 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 {
|
||
|
const 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;
|
||
|
hwrite(buf, n);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dumpbool(bool b)
|
||
|
{
|
||
|
dumpint(b ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
// dump varint uint64 length followed by memory contents
|
||
|
static void
|
||
|
dumpmemrange(const byte *data, uintptr len)
|
||
|
{
|
||
|
dumpint(len);
|
||
|
hwrite(data, len);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dumpstr(String s)
|
||
|
{
|
||
|
dumpmemrange(s.str, s.len);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dumpcstr(const int8 *c)
|
||
|
{
|
||
|
dumpmemrange((const byte*)c, runtime_findnull((const byte*)c));
|
||
|
}
|
||
|
|
||
|
// dump information for a type
|
||
|
static void
|
||
|
dumptype(const 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->__uncommon == nil || t->__uncommon->__pkg_path == nil || t->__uncommon->__name == nil) {
|
||
|
dumpstr(*t->__reflection);
|
||
|
} else {
|
||
|
dumpint(t->__uncommon->__pkg_path->len + 1 + t->__uncommon->__name->len);
|
||
|
hwrite(t->__uncommon->__pkg_path->str, t->__uncommon->__pkg_path->len);
|
||
|
hwrite((const byte*)".", 1);
|
||
|
hwrite(t->__uncommon->__name->str, t->__uncommon->__name->len);
|
||
|
}
|
||
|
dumpbool(t->__size > PtrSize || (t->__code & 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, const 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(const char *description, byte *to)
|
||
|
{
|
||
|
dumpint(TagOtherRoot);
|
||
|
dumpcstr((const int8 *)description);
|
||
|
dumpint((uintptr)to);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dumpfinalizer(byte *obj, FuncVal *fn, const FuncType* ft, const PtrType *ot)
|
||
|
{
|
||
|
dumpint(TagFinalizer);
|
||
|
dumpint((uintptr)obj);
|
||
|
dumpint((uintptr)fn);
|
||
|
dumpint((uintptr)fn->fn);
|
||
|
dumpint((uintptr)ft);
|
||
|
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)
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
dumpgoroutine(G *gp)
|
||
|
{
|
||
|
// ChildInfo child;
|
||
|
Defer *d;
|
||
|
Panic *p;
|
||
|
|
||
|
dumpint(TagGoRoutine);
|
||
|
dumpint((uintptr)gp);
|
||
|
dumpint((uintptr)0);
|
||
|
dumpint(gp->goid);
|
||
|
dumpint(gp->gopc);
|
||
|
dumpint(gp->status);
|
||
|
dumpbool(gp->issystem);
|
||
|
dumpbool(gp->isbackground);
|
||
|
dumpint(gp->waitsince);
|
||
|
dumpcstr((const int8 *)gp->waitreason);
|
||
|
dumpint((uintptr)0);
|
||
|
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->__next) {
|
||
|
dumpint(TagDefer);
|
||
|
dumpint((uintptr)d);
|
||
|
dumpint((uintptr)gp);
|
||
|
dumpint((uintptr)d->__arg);
|
||
|
dumpint((uintptr)d->__frame);
|
||
|
dumpint((uintptr)d->__pfn);
|
||
|
dumpint((uintptr)0);
|
||
|
dumpint((uintptr)d->__next);
|
||
|
}
|
||
|
for (p = gp->panic; p != nil; p = p->__next) {
|
||
|
dumpint(TagPanic);
|
||
|
dumpint((uintptr)p);
|
||
|
dumpint((uintptr)gp);
|
||
|
dumpint((uintptr)p->__arg.__type_descriptor);
|
||
|
dumpint((uintptr)p->__arg.__object);
|
||
|
dumpint((uintptr)0);
|
||
|
dumpint((uintptr)p->__next);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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, void *obj, const FuncType *ft, const PtrType *ot)
|
||
|
{
|
||
|
dumpint(TagQueuedFinalizer);
|
||
|
dumpint((uintptr)obj);
|
||
|
dumpint((uintptr)fn);
|
||
|
dumpint((uintptr)fn->fn);
|
||
|
dumpint((uintptr)ft);
|
||
|
dumpint((uintptr)ot);
|
||
|
}
|
||
|
|
||
|
|
||
|
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->ft, 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 hfree[PageSize/8];
|
||
|
|
||
|
static void
|
||
|
dumpobjs(void)
|
||
|
{
|
||
|
uintptr i, j, size, n, off, shift, *bitp, bits, ti, kind;
|
||
|
MSpan *s;
|
||
|
MLink *l;
|
||
|
byte *p;
|
||
|
const 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) {
|
||
|
hfree[((byte*)l - p) / size] = true;
|
||
|
}
|
||
|
for(j = 0; j < n; j++, p += size) {
|
||
|
if(hfree[j]) {
|
||
|
hfree[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 = ((const ChanType*)t)->__element_type; // 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(0);
|
||
|
dumpcstr((const int8 *)"");
|
||
|
dumpint(runtime_ncpu);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dumpms(void)
|
||
|
{
|
||
|
M *mp;
|
||
|
|
||
|
for(mp = runtime_allm; mp != nil; mp = mp->alllink) {
|
||
|
dumpint(TagOSThread);
|
||
|
dumpint((uintptr)mp);
|
||
|
dumpint(mp->id);
|
||
|
dumpint(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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, Location *stk, uintptr size, uintptr allocs, uintptr frees)
|
||
|
{
|
||
|
uintptr i, pc;
|
||
|
byte buf[20];
|
||
|
|
||
|
dumpint(TagMemProf);
|
||
|
dumpint((uintptr)b);
|
||
|
dumpint(size);
|
||
|
dumpint(nstk);
|
||
|
for(i = 0; i < nstk; i++) {
|
||
|
pc = stk[i].pc;
|
||
|
if(stk[i].function.len == 0) {
|
||
|
runtime_snprintf(buf, sizeof(buf), "%X", (uint64)pc);
|
||
|
dumpcstr((int8*)buf);
|
||
|
dumpcstr((const int8*)"?");
|
||
|
dumpint(0);
|
||
|
} else {
|
||
|
dumpstr(stk[i].function);
|
||
|
dumpstr(stk[i].filename);
|
||
|
dumpint(stk[i].lineno);
|
||
|
}
|
||
|
}
|
||
|
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)
|
||
|
{
|
||
|
const 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 = (const byte*)"go1.3 heap dump\n";
|
||
|
hwrite(hdr, runtime_findnull(hdr));
|
||
|
dumpparams();
|
||
|
dumpobjs();
|
||
|
dumpgs();
|
||
|
dumpms();
|
||
|
dumproots();
|
||
|
dumpmemstats();
|
||
|
dumpmemprof();
|
||
|
dumpint(TagEOF);
|
||
|
flush();
|
||
|
|
||
|
gp->param = nil;
|
||
|
gp->status = Grunning;
|
||
|
runtime_gogo(gp);
|
||
|
}
|
||
|
|
||
|
void runtime_debug_WriteHeapDump(uintptr)
|
||
|
__asm__(GOSYM_PREFIX "runtime_debug.WriteHeapDump");
|
||
|
|
||
|
void
|
||
|
runtime_debug_WriteHeapDump(uintptr fd)
|
||
|
{
|
||
|
M *m;
|
||
|
G *g;
|
||
|
|
||
|
// Stop the world.
|
||
|
runtime_semacquire(&runtime_worldsema, false);
|
||
|
m = runtime_m();
|
||
|
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 = runtime_g();
|
||
|
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_descriptor);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
// 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 __attribute__ ((unused)), uintptr size, const 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;
|
||
|
}
|
||
|
}
|