mirror of
https://github.com/golang/go
synced 2024-11-11 23:20:24 -07:00
runtime: WriteHeapDump dumps the heap to a file.
See http://golang.org/s/go13heapdump for the file format. LGTM=rsc R=rsc, bradfitz, dvyukov, khr CC=golang-codereviews https://golang.org/cl/37540043
This commit is contained in:
parent
bee3848f4e
commit
fff63c2448
@ -146,3 +146,8 @@ func SetMaxThreads(threads int) int {
|
||||
// SetPanicOnFault applies only to the current goroutine.
|
||||
// It returns the previous setting.
|
||||
func SetPanicOnFault(enabled bool) bool
|
||||
|
||||
// WriteHeapDump writes a description of the heap and the objects in
|
||||
// it to the given file descriptor.
|
||||
// The heap dump format is defined at http://golang.org/s/go13heapdump.
|
||||
func WriteHeapDump(fd uintptr)
|
||||
|
890
src/pkg/runtime/heapdump.c
Normal file
890
src/pkg/runtime/heapdump.c
Normal file
@ -0,0 +1,890 @@
|
||||
// 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);
|
||||
dumpstr(*t->string);
|
||||
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 not nil, 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 != nil)
|
||||
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 = nil;
|
||||
}
|
||||
|
||||
// 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 != nil) {
|
||||
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->sp;
|
||||
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 = nil;
|
||||
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 = nil;
|
||||
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
|
||||
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", 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; i += type->size)
|
||||
playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj);
|
||||
break;
|
||||
case TypeInfo_Chan:
|
||||
for(i = runtime·Hchansize; i < 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -134,6 +134,20 @@ out:
|
||||
return m;
|
||||
}
|
||||
|
||||
// call the callback for every itab that is currently allocated.
|
||||
void
|
||||
runtime·iterate_itabs(void (*callback)(Itab*))
|
||||
{
|
||||
int32 i;
|
||||
Itab *tab;
|
||||
|
||||
for(i = 0; i < nelem(hash); i++) {
|
||||
for(tab = hash[i]; tab != nil; tab = tab->link) {
|
||||
callback(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
copyin(Type *t, void *src, void **dst)
|
||||
{
|
||||
|
@ -277,6 +277,7 @@ struct MStats
|
||||
|
||||
#define mstats runtime·memStats
|
||||
extern MStats mstats;
|
||||
void runtime·updatememstats(GCStats *stats);
|
||||
|
||||
// Size classes. Computed and initialized by InitSizes.
|
||||
//
|
||||
|
@ -64,14 +64,8 @@
|
||||
enum {
|
||||
Debug = 0,
|
||||
CollectStats = 0,
|
||||
ScanStackByFrames = 1,
|
||||
IgnorePreciseGC = 0,
|
||||
ConcurrentSweep = 1,
|
||||
|
||||
// Four bits per word (see #defines below).
|
||||
wordsPerBitmapWord = sizeof(void*)*8/4,
|
||||
bitShift = sizeof(void*)*8/4,
|
||||
|
||||
WorkbufSize = 16*1024,
|
||||
FinBlockSize = 4*1024,
|
||||
|
||||
@ -146,38 +140,6 @@ clearpools(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Bits in per-word bitmap.
|
||||
// #defines because enum might not be able to hold the values.
|
||||
//
|
||||
// Each word in the bitmap describes wordsPerBitmapWord words
|
||||
// of heap memory. There are 4 bitmap bits dedicated to each heap word,
|
||||
// so on a 64-bit system there is one bitmap word per 16 heap words.
|
||||
// The bits in the word are packed together by type first, then by
|
||||
// heap location, so each 64-bit bitmap word consists of, from top to bottom,
|
||||
// the 16 bitMarked bits for the corresponding heap words,
|
||||
// then the 16 bitScan/bitBlockBoundary bits, then the 16 bitAllocated bits.
|
||||
// This layout makes it easier to iterate over the bits of a given type.
|
||||
//
|
||||
// The bitmap starts at mheap.arena_start and extends *backward* from
|
||||
// there. On a 64-bit system the off'th word in the arena is tracked by
|
||||
// the off/16+1'th word before mheap.arena_start. (On a 32-bit system,
|
||||
// the only difference is that the divisor is 8.)
|
||||
//
|
||||
// To pull out the bits corresponding to a given pointer p, we use:
|
||||
//
|
||||
// off = p - (uintptr*)mheap.arena_start; // word offset
|
||||
// b = (uintptr*)mheap.arena_start - off/wordsPerBitmapWord - 1;
|
||||
// shift = off % wordsPerBitmapWord
|
||||
// bits = *b >> shift;
|
||||
// /* then test bits & bitAllocated, bits & bitMarked, etc. */
|
||||
//
|
||||
#define bitAllocated ((uintptr)1<<(bitShift*0)) /* block start; eligible for garbage collection */
|
||||
#define bitScan ((uintptr)1<<(bitShift*1)) /* when bitAllocated is set */
|
||||
#define bitMarked ((uintptr)1<<(bitShift*2)) /* when bitAllocated is set */
|
||||
#define bitBlockBoundary ((uintptr)1<<(bitShift*1)) /* when bitAllocated is NOT set - mark for FlagNoGC objects */
|
||||
|
||||
#define bitMask (bitAllocated | bitScan | bitMarked)
|
||||
|
||||
// Holding worldsema grants an M the right to try to stop the world.
|
||||
// The procedure is:
|
||||
//
|
||||
@ -1270,6 +1232,7 @@ markroot(ParFor *desc, uint32 i)
|
||||
|
||||
USED(&desc);
|
||||
wbuf = getempty(nil);
|
||||
// Note: if you add a case here, please also update heapdump.c:dumproots.
|
||||
switch(i) {
|
||||
case RootData:
|
||||
enqueue1(&wbuf, (Obj){data, edata - data, (uintptr)gcdata});
|
||||
@ -1714,6 +1677,21 @@ runtime·queuefinalizer(byte *p, FuncVal *fn, uintptr nret, Type *fint, PtrType
|
||||
runtime·unlock(&gclock);
|
||||
}
|
||||
|
||||
void
|
||||
runtime·iterate_finq(void (*callback)(FuncVal*, byte*, uintptr, Type*, PtrType*))
|
||||
{
|
||||
FinBlock *fb;
|
||||
Finalizer *f;
|
||||
uintptr i;
|
||||
|
||||
for(fb = allfin; fb; fb = fb->alllink) {
|
||||
for(i = 0; i < fb->cnt; i++) {
|
||||
f = &fb->fin[i];
|
||||
callback(f->fn, f->arg, f->nret, f->fint, f->ot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
runtime·MSpan_EnsureSwept(MSpan *s)
|
||||
{
|
||||
@ -2120,8 +2098,8 @@ flushallmcaches(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
updatememstats(GCStats *stats)
|
||||
void
|
||||
runtime·updatememstats(GCStats *stats)
|
||||
{
|
||||
M *mp;
|
||||
MSpan *s;
|
||||
@ -2388,7 +2366,7 @@ gc(struct gc_args *args)
|
||||
|
||||
if(runtime·debug.gctrace) {
|
||||
heap1 = mstats.heap_alloc;
|
||||
updatememstats(&stats);
|
||||
runtime·updatememstats(&stats);
|
||||
if(heap1 != mstats.heap_alloc) {
|
||||
runtime·printf("runtime: mstats skew: heap=%D/%D\n", heap1, mstats.heap_alloc);
|
||||
runtime·throw("mstats skew");
|
||||
@ -2488,7 +2466,7 @@ runtime·ReadMemStats(MStats *stats)
|
||||
runtime·semacquire(&runtime·worldsema, false);
|
||||
m->gcing = 1;
|
||||
runtime·stoptheworld();
|
||||
updatememstats(nil);
|
||||
runtime·updatememstats(nil);
|
||||
// Size of the trailing by_size array differs between Go and C,
|
||||
// NumSizeClasses was changed, but we can not change Go struct because of backward compatibility.
|
||||
runtime·memcopy(runtime·sizeof_C_MStats, stats, &mstats);
|
||||
|
@ -44,3 +44,44 @@ enum {
|
||||
// - at most GC_STACK_CAPACITY allocations because of GC_ARRAY_START
|
||||
GC_STACK_CAPACITY = 8,
|
||||
};
|
||||
|
||||
enum {
|
||||
ScanStackByFrames = 1,
|
||||
IgnorePreciseGC = 0,
|
||||
|
||||
// Four bits per word (see #defines below).
|
||||
wordsPerBitmapWord = sizeof(void*)*8/4,
|
||||
bitShift = sizeof(void*)*8/4,
|
||||
};
|
||||
|
||||
// Bits in per-word bitmap.
|
||||
// #defines because enum might not be able to hold the values.
|
||||
//
|
||||
// Each word in the bitmap describes wordsPerBitmapWord words
|
||||
// of heap memory. There are 4 bitmap bits dedicated to each heap word,
|
||||
// so on a 64-bit system there is one bitmap word per 16 heap words.
|
||||
// The bits in the word are packed together by type first, then by
|
||||
// heap location, so each 64-bit bitmap word consists of, from top to bottom,
|
||||
// the 16 bitMarked bits for the corresponding heap words,
|
||||
// then the 16 bitScan/bitBlockBoundary bits, then the 16 bitAllocated bits.
|
||||
// This layout makes it easier to iterate over the bits of a given type.
|
||||
//
|
||||
// The bitmap starts at mheap.arena_start and extends *backward* from
|
||||
// there. On a 64-bit system the off'th word in the arena is tracked by
|
||||
// the off/16+1'th word before mheap.arena_start. (On a 32-bit system,
|
||||
// the only difference is that the divisor is 8.)
|
||||
//
|
||||
// To pull out the bits corresponding to a given pointer p, we use:
|
||||
//
|
||||
// off = p - (uintptr*)mheap.arena_start; // word offset
|
||||
// b = (uintptr*)mheap.arena_start - off/wordsPerBitmapWord - 1;
|
||||
// shift = off % wordsPerBitmapWord
|
||||
// bits = *b >> shift;
|
||||
// /* then test bits & bitAllocated, bits & bitMarked, etc. */
|
||||
//
|
||||
#define bitAllocated ((uintptr)1<<(bitShift*0)) /* block start; eligible for garbage collection */
|
||||
#define bitScan ((uintptr)1<<(bitShift*1)) /* when bitAllocated is set */
|
||||
#define bitMarked ((uintptr)1<<(bitShift*2)) /* when bitAllocated is set */
|
||||
#define bitBlockBoundary ((uintptr)1<<(bitShift*1)) /* when bitAllocated is NOT set - mark for FlagNoGC objects */
|
||||
|
||||
#define bitMask (bitAllocated | bitScan | bitMarked)
|
||||
|
@ -950,6 +950,8 @@ void _rt0_go(void);
|
||||
void* runtime·funcdata(Func*, int32);
|
||||
int32 runtime·setmaxthreads(int32);
|
||||
G* runtime·timejump(void);
|
||||
void runtime·iterate_itabs(void (*callback)(Itab*));
|
||||
void runtime·iterate_finq(void (*callback)(FuncVal*, byte*, uintptr, Type*, PtrType*));
|
||||
|
||||
#pragma varargck argpos runtime·printf 1
|
||||
#pragma varargck type "c" int32
|
||||
|
Loading…
Reference in New Issue
Block a user