| // 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 | |
| // https://golang.org/s/go14heapdump. | |
| package runtime | |
| import ( | |
| "runtime/internal/sys" | |
| "unsafe" | |
| ) | |
| //go:linkname runtime_debug_WriteHeapDump runtime/debug.WriteHeapDump | |
| func runtime_debug_WriteHeapDump(fd uintptr) { | |
| stopTheWorld("write heap dump") | |
| systemstack(func() { | |
| writeheapdump_m(fd) | |
| }) | |
| startTheWorld() | |
| } | |
| const ( | |
| 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 | |
| ) | |
| var dumpfd uintptr // fd to write the dump to. | |
| var tmpbuf []byte | |
| // buffer of pending write data | |
| const ( | |
| bufSize = 4096 | |
| ) | |
| var buf [bufSize]byte | |
| var nbuf uintptr | |
| func dwrite(data unsafe.Pointer, len uintptr) { | |
| if len == 0 { | |
| return | |
| } | |
| if nbuf+len <= bufSize { | |
| copy(buf[nbuf:], (*[bufSize]byte)(data)[:len]) | |
| nbuf += len | |
| return | |
| } | |
| write(dumpfd, unsafe.Pointer(&buf), int32(nbuf)) | |
| if len >= bufSize { | |
| write(dumpfd, data, int32(len)) | |
| nbuf = 0 | |
| } else { | |
| copy(buf[:], (*[bufSize]byte)(data)[:len]) | |
| nbuf = len | |
| } | |
| } | |
| func dwritebyte(b byte) { | |
| dwrite(unsafe.Pointer(&b), 1) | |
| } | |
| func flush() { | |
| write(dumpfd, unsafe.Pointer(&buf), int32(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. | |
| const ( | |
| typeCacheBuckets = 256 | |
| typeCacheAssoc = 4 | |
| ) | |
| type typeCacheBucket struct { | |
| t [typeCacheAssoc]*_type | |
| } | |
| var typecache [typeCacheBuckets]typeCacheBucket | |
| // dump a uint64 in a varint format parseable by encoding/binary | |
| func dumpint(v uint64) { | |
| var buf [10]byte | |
| var n int | |
| for v >= 0x80 { | |
| buf[n] = byte(v | 0x80) | |
| n++ | |
| v >>= 7 | |
| } | |
| buf[n] = byte(v) | |
| n++ | |
| dwrite(unsafe.Pointer(&buf), uintptr(n)) | |
| } | |
| func dumpbool(b bool) { | |
| if b { | |
| dumpint(1) | |
| } else { | |
| dumpint(0) | |
| } | |
| } | |
| // dump varint uint64 length followed by memory contents | |
| func dumpmemrange(data unsafe.Pointer, len uintptr) { | |
| dumpint(uint64(len)) | |
| dwrite(data, len) | |
| } | |
| func dumpslice(b []byte) { | |
| dumpint(uint64(len(b))) | |
| if len(b) > 0 { | |
| dwrite(unsafe.Pointer(&b[0]), uintptr(len(b))) | |
| } | |
| } | |
| func dumpstr(s string) { | |
| sp := stringStructOf(&s) | |
| dumpmemrange(sp.str, uintptr(sp.len)) | |
| } | |
| // dump information for a type | |
| func dumptype(t *_type) { | |
| 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(uint64(uintptr(unsafe.Pointer(t)))) | |
| dumpint(uint64(t.size)) | |
| if t.x == nil || t.x.pkgpath == nil || t.x.name == nil { | |
| dumpstr(*t._string) | |
| } else { | |
| pkgpath := stringStructOf(t.x.pkgpath) | |
| name := stringStructOf(t.x.name) | |
| dumpint(uint64(uintptr(pkgpath.len) + 1 + uintptr(name.len))) | |
| dwrite(pkgpath.str, uintptr(pkgpath.len)) | |
| dwritebyte('.') | |
| dwrite(name.str, uintptr(name.len)) | |
| } | |
| dumpbool(t.kind&kindDirectIface == 0 || t.kind&kindNoPointers == 0) | |
| } | |
| // dump an object | |
| func dumpobj(obj unsafe.Pointer, size uintptr, bv bitvector) { | |
| dumpbvtypes(&bv, obj) | |
| dumpint(tagObject) | |
| dumpint(uint64(uintptr(obj))) | |
| dumpmemrange(obj, size) | |
| dumpfields(bv) | |
| } | |
| func dumpotherroot(description string, to unsafe.Pointer) { | |
| dumpint(tagOtherRoot) | |
| dumpstr(description) | |
| dumpint(uint64(uintptr(to))) | |
| } | |
| func dumpfinalizer(obj unsafe.Pointer, fn *funcval, fint *_type, ot *ptrtype) { | |
| dumpint(tagFinalizer) | |
| dumpint(uint64(uintptr(obj))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fn.fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fint)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(ot)))) | |
| } | |
| type childInfo struct { | |
| // Information passed up from the callee frame about | |
| // the layout of the outargs region. | |
| argoff uintptr // where the arguments start in the frame | |
| arglen uintptr // size of args region | |
| args bitvector // if args.n >= 0, pointer map of args region | |
| sp *uint8 // callee sp | |
| depth uintptr // depth in call stack (0 == most recent) | |
| } | |
| // dump kinds & offsets of interesting fields in bv | |
| func dumpbv(cbv *bitvector, offset uintptr) { | |
| bv := gobv(*cbv) | |
| for i := uintptr(0); i < uintptr(bv.n); i++ { | |
| if bv.bytedata[i/8]>>(i%8)&1 == 1 { | |
| dumpint(fieldKindPtr) | |
| dumpint(uint64(offset + i*sys.PtrSize)) | |
| } | |
| } | |
| } | |
| func dumpframe(s *stkframe, arg unsafe.Pointer) bool { | |
| child := (*childInfo)(arg) | |
| f := s.fn | |
| // Figure out what we can about our stack map | |
| pc := s.pc | |
| if pc != f.entry { | |
| pc-- | |
| } | |
| pcdata := pcdatavalue(f, _PCDATA_StackMapIndex, pc, nil) | |
| 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 | |
| } | |
| stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps)) | |
| // Dump any types we will need to resolve Efaces. | |
| if child.args.n >= 0 { | |
| dumpbvtypes(&child.args, unsafe.Pointer(s.sp+child.argoff)) | |
| } | |
| var bv bitvector | |
| if stkmap != nil && stkmap.n > 0 { | |
| bv = stackmapdata(stkmap, pcdata) | |
| dumpbvtypes(&bv, unsafe.Pointer(s.varp-uintptr(bv.n*sys.PtrSize))) | |
| } else { | |
| bv.n = -1 | |
| } | |
| // Dump main body of stack frame. | |
| dumpint(tagStackFrame) | |
| dumpint(uint64(s.sp)) // lowest address in frame | |
| dumpint(uint64(child.depth)) // # of frames deep on the stack | |
| dumpint(uint64(uintptr(unsafe.Pointer(child.sp)))) // sp of child, or 0 if bottom of stack | |
| dumpmemrange(unsafe.Pointer(s.sp), s.fp-s.sp) // frame contents | |
| dumpint(uint64(f.entry)) | |
| dumpint(uint64(s.pc)) | |
| dumpint(uint64(s.continpc)) | |
| name := funcname(f) | |
| if name == "" { | |
| name = "unknown function" | |
| } | |
| dumpstr(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 += sys.PtrSize { | |
| dumpint(fieldKindPtr) | |
| dumpint(uint64(off)) | |
| } | |
| } | |
| // Dump fields in the local vars section | |
| if stkmap == nil { | |
| // No locals information, dump everything. | |
| for off := child.arglen; off < s.varp-s.sp; off += sys.PtrSize { | |
| dumpint(fieldKindPtr) | |
| dumpint(uint64(off)) | |
| } | |
| } else if stkmap.n < 0 { | |
| // Locals size information, dump just the locals. | |
| size := uintptr(-stkmap.n) | |
| for off := s.varp - size - s.sp; off < s.varp-s.sp; off += sys.PtrSize { | |
| dumpint(fieldKindPtr) | |
| dumpint(uint64(off)) | |
| } | |
| } else if stkmap.n > 0 { | |
| // Locals bitmap information, scan just the pointers in | |
| // locals. | |
| dumpbv(&bv, s.varp-uintptr(bv.n)*sys.PtrSize-s.sp) | |
| } | |
| dumpint(fieldKindEol) | |
| // Record arg info for parent. | |
| child.argoff = s.argp - s.fp | |
| child.arglen = s.arglen | |
| child.sp = (*uint8)(unsafe.Pointer(s.sp)) | |
| child.depth++ | |
| stkmap = (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps)) | |
| if stkmap != nil { | |
| child.args = stackmapdata(stkmap, pcdata) | |
| } else { | |
| child.args.n = -1 | |
| } | |
| return true | |
| } | |
| func dumpgoroutine(gp *g) { | |
| var sp, pc, lr uintptr | |
| if gp.syscallsp != 0 { | |
| sp = gp.syscallsp | |
| pc = gp.syscallpc | |
| lr = 0 | |
| } else { | |
| sp = gp.sched.sp | |
| pc = gp.sched.pc | |
| lr = gp.sched.lr | |
| } | |
| dumpint(tagGoroutine) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp)))) | |
| dumpint(uint64(sp)) | |
| dumpint(uint64(gp.goid)) | |
| dumpint(uint64(gp.gopc)) | |
| dumpint(uint64(readgstatus(gp))) | |
| dumpbool(isSystemGoroutine(gp)) | |
| dumpbool(false) // isbackground | |
| dumpint(uint64(gp.waitsince)) | |
| dumpstr(gp.waitreason) | |
| dumpint(uint64(uintptr(gp.sched.ctxt))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp.m)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp._defer)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp._panic)))) | |
| // dump stack | |
| var child childInfo | |
| child.args.n = -1 | |
| child.arglen = 0 | |
| child.sp = nil | |
| child.depth = 0 | |
| gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, dumpframe, noescape(unsafe.Pointer(&child)), 0) | |
| // dump defer & panic records | |
| for d := gp._defer; d != nil; d = d.link { | |
| dumpint(tagDefer) | |
| dumpint(uint64(uintptr(unsafe.Pointer(d)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp)))) | |
| dumpint(uint64(d.sp)) | |
| dumpint(uint64(d.pc)) | |
| dumpint(uint64(uintptr(unsafe.Pointer(d.fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(d.fn.fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(d.link)))) | |
| } | |
| for p := gp._panic; p != nil; p = p.link { | |
| dumpint(tagPanic) | |
| dumpint(uint64(uintptr(unsafe.Pointer(p)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(gp)))) | |
| eface := efaceOf(&p.arg) | |
| dumpint(uint64(uintptr(unsafe.Pointer(eface._type)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(eface.data)))) | |
| dumpint(0) // was p->defer, no longer recorded | |
| dumpint(uint64(uintptr(unsafe.Pointer(p.link)))) | |
| } | |
| } | |
| func dumpgs() { | |
| // goroutines & stacks | |
| for i := 0; uintptr(i) < allglen; i++ { | |
| gp := allgs[i] | |
| status := readgstatus(gp) // The world is stopped so gp will not be in a scan state. | |
| switch status { | |
| default: | |
| print("runtime: unexpected G.status ", hex(status), "\n") | |
| throw("dumpgs in STW - bad status") | |
| case _Gdead: | |
| // ok | |
| case _Grunnable, | |
| _Gsyscall, | |
| _Gwaiting: | |
| dumpgoroutine(gp) | |
| } | |
| } | |
| } | |
| func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, ot *ptrtype) { | |
| dumpint(tagQueuedFinalizer) | |
| dumpint(uint64(uintptr(obj))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fn.fn)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(fint)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(ot)))) | |
| } | |
| func dumproots() { | |
| // TODO(mwhudson): dump datamask etc from all objects | |
| // data segment | |
| dumpbvtypes(&firstmoduledata.gcdatamask, unsafe.Pointer(firstmoduledata.data)) | |
| dumpint(tagData) | |
| dumpint(uint64(firstmoduledata.data)) | |
| dumpmemrange(unsafe.Pointer(firstmoduledata.data), firstmoduledata.edata-firstmoduledata.data) | |
| dumpfields(firstmoduledata.gcdatamask) | |
| // bss segment | |
| dumpbvtypes(&firstmoduledata.gcbssmask, unsafe.Pointer(firstmoduledata.bss)) | |
| dumpint(tagBSS) | |
| dumpint(uint64(firstmoduledata.bss)) | |
| dumpmemrange(unsafe.Pointer(firstmoduledata.bss), firstmoduledata.ebss-firstmoduledata.bss) | |
| dumpfields(firstmoduledata.gcbssmask) | |
| // MSpan.types | |
| allspans := h_allspans | |
| for spanidx := uint32(0); spanidx < mheap_.nspan; spanidx++ { | |
| s := allspans[spanidx] | |
| if s.state == _MSpanInUse { | |
| // Finalizers | |
| for sp := s.specials; sp != nil; sp = sp.next { | |
| if sp.kind != _KindSpecialFinalizer { | |
| continue | |
| } | |
| spf := (*specialfinalizer)(unsafe.Pointer(sp)) | |
| p := unsafe.Pointer((uintptr(s.start) << _PageShift) + uintptr(spf.special.offset)) | |
| dumpfinalizer(p, spf.fn, spf.fint, spf.ot) | |
| } | |
| } | |
| } | |
| // Finalizer queue | |
| iterate_finq(finq_callback) | |
| } | |
| // Bit vector of free marks. | |
| // Needs to be as big as the largest number of objects per span. | |
| var freemark [_PageSize / 8]bool | |
| func dumpobjs() { | |
| for i := uintptr(0); i < uintptr(mheap_.nspan); i++ { | |
| s := h_allspans[i] | |
| if s.state != _MSpanInUse { | |
| continue | |
| } | |
| p := uintptr(s.start << _PageShift) | |
| size := s.elemsize | |
| n := (s.npages << _PageShift) / size | |
| if n > uintptr(len(freemark)) { | |
| throw("freemark array doesn't have enough entries") | |
| } | |
| for l := s.freelist; l.ptr() != nil; l = l.ptr().next { | |
| freemark[(uintptr(l)-p)/size] = true | |
| } | |
| for j := uintptr(0); j < n; j, p = j+1, p+size { | |
| if freemark[j] { | |
| freemark[j] = false | |
| continue | |
| } | |
| dumpobj(unsafe.Pointer(p), size, makeheapobjbv(p, size)) | |
| } | |
| } | |
| } | |
| func dumpparams() { | |
| dumpint(tagParams) | |
| x := uintptr(1) | |
| if *(*byte)(unsafe.Pointer(&x)) == 1 { | |
| dumpbool(false) // little-endian ptrs | |
| } else { | |
| dumpbool(true) // big-endian ptrs | |
| } | |
| dumpint(sys.PtrSize) | |
| dumpint(uint64(mheap_.arena_start)) | |
| dumpint(uint64(mheap_.arena_used)) | |
| dumpint(sys.TheChar) | |
| dumpstr(sys.Goexperiment) | |
| dumpint(uint64(ncpu)) | |
| } | |
| func itab_callback(tab *itab) { | |
| 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(uint64(uintptr(unsafe.Pointer(tab)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(t.ptrto)))) | |
| } else if t.kind&kindNoPointers == 0 { | |
| // t is pointer-like - data slot is a t. | |
| dumptype(t) | |
| dumpint(tagItab) | |
| dumpint(uint64(uintptr(unsafe.Pointer(tab)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(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(uint64(uintptr(unsafe.Pointer(tab)))) | |
| dumpint(uint64(uintptr(unsafe.Pointer(t)))) | |
| } | |
| } | |
| func dumpitabs() { | |
| iterate_itabs(itab_callback) | |
| } | |
| func dumpms() { | |
| for mp := allm; mp != nil; mp = mp.alllink { | |
| dumpint(tagOSThread) | |
| dumpint(uint64(uintptr(unsafe.Pointer(mp)))) | |
| dumpint(uint64(mp.id)) | |
| dumpint(mp.procid) | |
| } | |
| } | |
| func dumpmemstats() { | |
| dumpint(tagMemStats) | |
| dumpint(memstats.alloc) | |
| dumpint(memstats.total_alloc) | |
| dumpint(memstats.sys) | |
| dumpint(memstats.nlookup) | |
| dumpint(memstats.nmalloc) | |
| dumpint(memstats.nfree) | |
| dumpint(memstats.heap_alloc) | |
| dumpint(memstats.heap_sys) | |
| dumpint(memstats.heap_idle) | |
| dumpint(memstats.heap_inuse) | |
| dumpint(memstats.heap_released) | |
| dumpint(memstats.heap_objects) | |
| dumpint(memstats.stacks_inuse) | |
| dumpint(memstats.stacks_sys) | |
| dumpint(memstats.mspan_inuse) | |
| dumpint(memstats.mspan_sys) | |
| dumpint(memstats.mcache_inuse) | |
| dumpint(memstats.mcache_sys) | |
| dumpint(memstats.buckhash_sys) | |
| dumpint(memstats.gc_sys) | |
| dumpint(memstats.other_sys) | |
| dumpint(memstats.next_gc) | |
| dumpint(memstats.last_gc) | |
| dumpint(memstats.pause_total_ns) | |
| for i := 0; i < 256; i++ { | |
| dumpint(memstats.pause_ns[i]) | |
| } | |
| dumpint(uint64(memstats.numgc)) | |
| } | |
| func dumpmemprof_callback(b *bucket, nstk uintptr, pstk *uintptr, size, allocs, frees uintptr) { | |
| stk := (*[100000]uintptr)(unsafe.Pointer(pstk)) | |
| dumpint(tagMemProf) | |
| dumpint(uint64(uintptr(unsafe.Pointer(b)))) | |
| dumpint(uint64(size)) | |
| dumpint(uint64(nstk)) | |
| for i := uintptr(0); i < nstk; i++ { | |
| pc := stk[i] | |
| f := findfunc(pc) | |
| if f == nil { | |
| var buf [64]byte | |
| n := len(buf) | |
| n-- | |
| buf[n] = ')' | |
| if pc == 0 { | |
| n-- | |
| buf[n] = '0' | |
| } else { | |
| for pc > 0 { | |
| n-- | |
| buf[n] = "0123456789abcdef"[pc&15] | |
| pc >>= 4 | |
| } | |
| } | |
| n-- | |
| buf[n] = 'x' | |
| n-- | |
| buf[n] = '0' | |
| n-- | |
| buf[n] = '(' | |
| dumpslice(buf[n:]) | |
| dumpstr("?") | |
| dumpint(0) | |
| } else { | |
| dumpstr(funcname(f)) | |
| if i > 0 && pc > f.entry { | |
| pc-- | |
| } | |
| file, line := funcline(f, pc) | |
| dumpstr(file) | |
| dumpint(uint64(line)) | |
| } | |
| } | |
| dumpint(uint64(allocs)) | |
| dumpint(uint64(frees)) | |
| } | |
| func dumpmemprof() { | |
| iterate_memprof(dumpmemprof_callback) | |
| allspans := h_allspans | |
| for spanidx := uint32(0); spanidx < 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)(unsafe.Pointer(sp)) | |
| p := uintptr(s.start<<_PageShift) + uintptr(spp.special.offset) | |
| dumpint(tagAllocSample) | |
| dumpint(uint64(p)) | |
| dumpint(uint64(uintptr(unsafe.Pointer(spp.b)))) | |
| } | |
| } | |
| } | |
| var dumphdr = []byte("go1.6 heap dump\n") | |
| func mdump() { | |
| // make sure we're done sweeping | |
| for i := uintptr(0); i < uintptr(mheap_.nspan); i++ { | |
| s := h_allspans[i] | |
| if s.state == _MSpanInUse { | |
| s.ensureSwept() | |
| } | |
| } | |
| memclr(unsafe.Pointer(&typecache), unsafe.Sizeof(typecache)) | |
| dwrite(unsafe.Pointer(&dumphdr[0]), uintptr(len(dumphdr))) | |
| dumpparams() | |
| dumpitabs() | |
| dumpobjs() | |
| dumpgs() | |
| dumpms() | |
| dumproots() | |
| dumpmemstats() | |
| dumpmemprof() | |
| dumpint(tagEOF) | |
| flush() | |
| } | |
| func writeheapdump_m(fd uintptr) { | |
| _g_ := getg() | |
| casgstatus(_g_.m.curg, _Grunning, _Gwaiting) | |
| _g_.waitreason = "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. | |
| updatememstats(nil) | |
| // Set dump file. | |
| dumpfd = fd | |
| // Call dump routine. | |
| mdump() | |
| // Reset dump file. | |
| dumpfd = 0 | |
| if tmpbuf != nil { | |
| sysFree(unsafe.Pointer(&tmpbuf[0]), uintptr(len(tmpbuf)), &memstats.other_sys) | |
| tmpbuf = nil | |
| } | |
| casgstatus(_g_.m.curg, _Gwaiting, _Grunning) | |
| } | |
| // dumpint() the kind & offset of each field in an object. | |
| func dumpfields(bv bitvector) { | |
| 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. | |
| // TODO(rsc, khr): Delete - no longer possible. | |
| // Dump all the types that appear in the type field of | |
| // any Eface described by this bit vector. | |
| func dumpbvtypes(bv *bitvector, base unsafe.Pointer) { | |
| } | |
| func makeheapobjbv(p uintptr, size uintptr) bitvector { | |
| // Extend the temp buffer if necessary. | |
| nptr := size / sys.PtrSize | |
| if uintptr(len(tmpbuf)) < nptr/8+1 { | |
| if tmpbuf != nil { | |
| sysFree(unsafe.Pointer(&tmpbuf[0]), uintptr(len(tmpbuf)), &memstats.other_sys) | |
| } | |
| n := nptr/8 + 1 | |
| p := sysAlloc(n, &memstats.other_sys) | |
| if p == nil { | |
| throw("heapdump: out of memory") | |
| } | |
| tmpbuf = (*[1 << 30]byte)(p)[:n] | |
| } | |
| // Convert heap bitmap to pointer bitmap. | |
| for i := uintptr(0); i < nptr/8+1; i++ { | |
| tmpbuf[i] = 0 | |
| } | |
| i := uintptr(0) | |
| hbits := heapBitsForAddr(p) | |
| for ; i < nptr; i++ { | |
| if i >= 2 && !hbits.isMarked() { | |
| break // end of object | |
| } | |
| if hbits.isPointer() { | |
| tmpbuf[i/8] |= 1 << (i % 8) | |
| } | |
| hbits = hbits.next() | |
| } | |
| return bitvector{int32(i), &tmpbuf[0]} | |
| } |