github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/runtime/heapdump.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Implementation of runtime/debug.WriteHeapDump. Writes all 6 // objects in the heap plus additional info (roots, threads, 7 // finalizers, etc.) to a file. 8 9 // The format of the dumped file is described at 10 // https://golang.org/s/go15heapdump. 11 12 package runtime 13 14 import ( 15 "runtime/internal/sys" 16 "unsafe" 17 ) 18 19 //go:linkname runtime_debug_WriteHeapDump runtime/debug.WriteHeapDump 20 func runtime_debug_WriteHeapDump(fd uintptr) { 21 stopTheWorld("write heap dump") 22 23 systemstack(func() { 24 writeheapdump_m(fd) 25 }) 26 27 startTheWorld() 28 } 29 30 const ( 31 fieldKindEol = 0 32 fieldKindPtr = 1 33 fieldKindIface = 2 34 fieldKindEface = 3 35 tagEOF = 0 36 tagObject = 1 37 tagOtherRoot = 2 38 tagType = 3 39 tagGoroutine = 4 40 tagStackFrame = 5 41 tagParams = 6 42 tagFinalizer = 7 43 tagItab = 8 44 tagOSThread = 9 45 tagMemStats = 10 46 tagQueuedFinalizer = 11 47 tagData = 12 48 tagBSS = 13 49 tagDefer = 14 50 tagPanic = 15 51 tagMemProf = 16 52 tagAllocSample = 17 53 ) 54 55 var dumpfd uintptr // fd to write the dump to. 56 var tmpbuf []byte 57 58 // buffer of pending write data 59 const ( 60 bufSize = 4096 61 ) 62 63 var buf [bufSize]byte 64 var nbuf uintptr 65 66 func dwrite(data unsafe.Pointer, len uintptr) { 67 if len == 0 { 68 return 69 } 70 if nbuf+len <= bufSize { 71 copy(buf[nbuf:], (*[bufSize]byte)(data)[:len]) 72 nbuf += len 73 return 74 } 75 76 write(dumpfd, unsafe.Pointer(&buf), int32(nbuf)) 77 if len >= bufSize { 78 write(dumpfd, data, int32(len)) 79 nbuf = 0 80 } else { 81 copy(buf[:], (*[bufSize]byte)(data)[:len]) 82 nbuf = len 83 } 84 } 85 86 func dwritebyte(b byte) { 87 dwrite(unsafe.Pointer(&b), 1) 88 } 89 90 func flush() { 91 write(dumpfd, unsafe.Pointer(&buf), int32(nbuf)) 92 nbuf = 0 93 } 94 95 // Cache of types that have been serialized already. 96 // We use a type's hash field to pick a bucket. 97 // Inside a bucket, we keep a list of types that 98 // have been serialized so far, most recently used first. 99 // Note: when a bucket overflows we may end up 100 // serializing a type more than once. That's ok. 101 const ( 102 typeCacheBuckets = 256 103 typeCacheAssoc = 4 104 ) 105 106 type typeCacheBucket struct { 107 t [typeCacheAssoc]*_type 108 } 109 110 var typecache [typeCacheBuckets]typeCacheBucket 111 112 // dump a uint64 in a varint format parseable by encoding/binary 113 func dumpint(v uint64) { 114 var buf [10]byte 115 var n int 116 for v >= 0x80 { 117 buf[n] = byte(v | 0x80) 118 n++ 119 v >>= 7 120 } 121 buf[n] = byte(v) 122 n++ 123 dwrite(unsafe.Pointer(&buf), uintptr(n)) 124 } 125 126 func dumpbool(b bool) { 127 if b { 128 dumpint(1) 129 } else { 130 dumpint(0) 131 } 132 } 133 134 // dump varint uint64 length followed by memory contents 135 func dumpmemrange(data unsafe.Pointer, len uintptr) { 136 dumpint(uint64(len)) 137 dwrite(data, len) 138 } 139 140 func dumpslice(b []byte) { 141 dumpint(uint64(len(b))) 142 if len(b) > 0 { 143 dwrite(unsafe.Pointer(&b[0]), uintptr(len(b))) 144 } 145 } 146 147 func dumpstr(s string) { 148 sp := stringStructOf(&s) 149 dumpmemrange(sp.str, uintptr(sp.len)) 150 } 151 152 // dump information for a type 153 func dumptype(t *_type) { 154 if t == nil { 155 return 156 } 157 158 // If we've definitely serialized the type before, 159 // no need to do it again. 160 b := &typecache[t.hash&(typeCacheBuckets-1)] 161 if t == b.t[0] { 162 return 163 } 164 for i := 1; i < typeCacheAssoc; i++ { 165 if t == b.t[i] { 166 // Move-to-front 167 for j := i; j > 0; j-- { 168 b.t[j] = b.t[j-1] 169 } 170 b.t[0] = t 171 return 172 } 173 } 174 175 // Might not have been dumped yet. Dump it and 176 // remember we did so. 177 for j := typeCacheAssoc - 1; j > 0; j-- { 178 b.t[j] = b.t[j-1] 179 } 180 b.t[0] = t 181 182 // dump the type 183 dumpint(tagType) 184 dumpint(uint64(uintptr(unsafe.Pointer(t)))) 185 dumpint(uint64(t.size)) 186 if x := t.uncommon(); x == nil || t.nameOff(x.pkgpath).name() == "" { 187 dumpstr(t.string()) 188 } else { 189 pkgpathstr := t.nameOff(x.pkgpath).name() 190 pkgpath := stringStructOf(&pkgpathstr) 191 namestr := t.name() 192 name := stringStructOf(&namestr) 193 dumpint(uint64(uintptr(pkgpath.len) + 1 + uintptr(name.len))) 194 dwrite(pkgpath.str, uintptr(pkgpath.len)) 195 dwritebyte('.') 196 dwrite(name.str, uintptr(name.len)) 197 } 198 dumpbool(t.kind&kindDirectIface == 0 || t.kind&kindNoPointers == 0) 199 } 200 201 // dump an object 202 func dumpobj(obj unsafe.Pointer, size uintptr, bv bitvector) { 203 dumpint(tagObject) 204 dumpint(uint64(uintptr(obj))) 205 dumpmemrange(obj, size) 206 dumpfields(bv) 207 } 208 209 func dumpotherroot(description string, to unsafe.Pointer) { 210 dumpint(tagOtherRoot) 211 dumpstr(description) 212 dumpint(uint64(uintptr(to))) 213 } 214 215 func dumpfinalizer(obj unsafe.Pointer, fn *funcval, fint *_type, ot *ptrtype) { 216 dumpint(tagFinalizer) 217 dumpint(uint64(uintptr(obj))) 218 dumpint(uint64(uintptr(unsafe.Pointer(fn)))) 219 dumpint(uint64(uintptr(unsafe.Pointer(fn.fn)))) 220 dumpint(uint64(uintptr(unsafe.Pointer(fint)))) 221 dumpint(uint64(uintptr(unsafe.Pointer(ot)))) 222 } 223 224 type childInfo struct { 225 // Information passed up from the callee frame about 226 // the layout of the outargs region. 227 argoff uintptr // where the arguments start in the frame 228 arglen uintptr // size of args region 229 args bitvector // if args.n >= 0, pointer map of args region 230 sp *uint8 // callee sp 231 depth uintptr // depth in call stack (0 == most recent) 232 } 233 234 // dump kinds & offsets of interesting fields in bv 235 func dumpbv(cbv *bitvector, offset uintptr) { 236 for i := uintptr(0); i < uintptr(cbv.n); i++ { 237 if cbv.ptrbit(i) == 1 { 238 dumpint(fieldKindPtr) 239 dumpint(uint64(offset + i*sys.PtrSize)) 240 } 241 } 242 } 243 244 func dumpframe(s *stkframe, arg unsafe.Pointer) bool { 245 child := (*childInfo)(arg) 246 f := s.fn 247 248 // Figure out what we can about our stack map 249 pc := s.pc 250 pcdata := int32(-1) // Use the entry map at function entry 251 if pc != f.entry { 252 pc-- 253 pcdata = pcdatavalue(f, _PCDATA_StackMapIndex, pc, nil) 254 } 255 if pcdata == -1 { 256 // We do not have a valid pcdata value but there might be a 257 // stackmap for this function. It is likely that we are looking 258 // at the function prologue, assume so and hope for the best. 259 pcdata = 0 260 } 261 stkmap := (*stackmap)(funcdata(f, _FUNCDATA_LocalsPointerMaps)) 262 263 var bv bitvector 264 if stkmap != nil && stkmap.n > 0 { 265 bv = stackmapdata(stkmap, pcdata) 266 } else { 267 bv.n = -1 268 } 269 270 // Dump main body of stack frame. 271 dumpint(tagStackFrame) 272 dumpint(uint64(s.sp)) // lowest address in frame 273 dumpint(uint64(child.depth)) // # of frames deep on the stack 274 dumpint(uint64(uintptr(unsafe.Pointer(child.sp)))) // sp of child, or 0 if bottom of stack 275 dumpmemrange(unsafe.Pointer(s.sp), s.fp-s.sp) // frame contents 276 dumpint(uint64(f.entry)) 277 dumpint(uint64(s.pc)) 278 dumpint(uint64(s.continpc)) 279 name := funcname(f) 280 if name == "" { 281 name = "unknown function" 282 } 283 dumpstr(name) 284 285 // Dump fields in the outargs section 286 if child.args.n >= 0 { 287 dumpbv(&child.args, child.argoff) 288 } else { 289 // conservative - everything might be a pointer 290 for off := child.argoff; off < child.argoff+child.arglen; off += sys.PtrSize { 291 dumpint(fieldKindPtr) 292 dumpint(uint64(off)) 293 } 294 } 295 296 // Dump fields in the local vars section 297 if stkmap == nil { 298 // No locals information, dump everything. 299 for off := child.arglen; off < s.varp-s.sp; off += sys.PtrSize { 300 dumpint(fieldKindPtr) 301 dumpint(uint64(off)) 302 } 303 } else if stkmap.n < 0 { 304 // Locals size information, dump just the locals. 305 size := uintptr(-stkmap.n) 306 for off := s.varp - size - s.sp; off < s.varp-s.sp; off += sys.PtrSize { 307 dumpint(fieldKindPtr) 308 dumpint(uint64(off)) 309 } 310 } else if stkmap.n > 0 { 311 // Locals bitmap information, scan just the pointers in 312 // locals. 313 dumpbv(&bv, s.varp-uintptr(bv.n)*sys.PtrSize-s.sp) 314 } 315 dumpint(fieldKindEol) 316 317 // Record arg info for parent. 318 child.argoff = s.argp - s.fp 319 child.arglen = s.arglen 320 child.sp = (*uint8)(unsafe.Pointer(s.sp)) 321 child.depth++ 322 stkmap = (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps)) 323 if stkmap != nil { 324 child.args = stackmapdata(stkmap, pcdata) 325 } else { 326 child.args.n = -1 327 } 328 return true 329 } 330 331 func dumpgoroutine(gp *g) { 332 var sp, pc, lr uintptr 333 if gp.syscallsp != 0 { 334 sp = gp.syscallsp 335 pc = gp.syscallpc 336 lr = 0 337 } else { 338 sp = gp.sched.sp 339 pc = gp.sched.pc 340 lr = gp.sched.lr 341 } 342 343 dumpint(tagGoroutine) 344 dumpint(uint64(uintptr(unsafe.Pointer(gp)))) 345 dumpint(uint64(sp)) 346 dumpint(uint64(gp.goid)) 347 dumpint(uint64(gp.gopc)) 348 dumpint(uint64(readgstatus(gp))) 349 dumpbool(isSystemGoroutine(gp, false)) 350 dumpbool(false) // isbackground 351 dumpint(uint64(gp.waitsince)) 352 dumpstr(gp.waitreason.String()) 353 dumpint(uint64(uintptr(gp.sched.ctxt))) 354 dumpint(uint64(uintptr(unsafe.Pointer(gp.m)))) 355 dumpint(uint64(uintptr(unsafe.Pointer(gp._defer)))) 356 dumpint(uint64(uintptr(unsafe.Pointer(gp._panic)))) 357 358 // dump stack 359 var child childInfo 360 child.args.n = -1 361 child.arglen = 0 362 child.sp = nil 363 child.depth = 0 364 gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, dumpframe, noescape(unsafe.Pointer(&child)), 0) 365 366 // dump defer & panic records 367 for d := gp._defer; d != nil; d = d.link { 368 dumpint(tagDefer) 369 dumpint(uint64(uintptr(unsafe.Pointer(d)))) 370 dumpint(uint64(uintptr(unsafe.Pointer(gp)))) 371 dumpint(uint64(d.sp)) 372 dumpint(uint64(d.pc)) 373 dumpint(uint64(uintptr(unsafe.Pointer(d.fn)))) 374 dumpint(uint64(uintptr(unsafe.Pointer(d.fn.fn)))) 375 dumpint(uint64(uintptr(unsafe.Pointer(d.link)))) 376 } 377 for p := gp._panic; p != nil; p = p.link { 378 dumpint(tagPanic) 379 dumpint(uint64(uintptr(unsafe.Pointer(p)))) 380 dumpint(uint64(uintptr(unsafe.Pointer(gp)))) 381 eface := efaceOf(&p.arg) 382 dumpint(uint64(uintptr(unsafe.Pointer(eface._type)))) 383 dumpint(uint64(uintptr(unsafe.Pointer(eface.data)))) 384 dumpint(0) // was p->defer, no longer recorded 385 dumpint(uint64(uintptr(unsafe.Pointer(p.link)))) 386 } 387 } 388 389 func dumpgs() { 390 // goroutines & stacks 391 for i := 0; uintptr(i) < allglen; i++ { 392 gp := allgs[i] 393 status := readgstatus(gp) // The world is stopped so gp will not be in a scan state. 394 switch status { 395 default: 396 print("runtime: unexpected G.status ", hex(status), "\n") 397 throw("dumpgs in STW - bad status") 398 case _Gdead: 399 // ok 400 case _Grunnable, 401 _Gsyscall, 402 _Gwaiting: 403 dumpgoroutine(gp) 404 } 405 } 406 } 407 408 func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, ot *ptrtype) { 409 dumpint(tagQueuedFinalizer) 410 dumpint(uint64(uintptr(obj))) 411 dumpint(uint64(uintptr(unsafe.Pointer(fn)))) 412 dumpint(uint64(uintptr(unsafe.Pointer(fn.fn)))) 413 dumpint(uint64(uintptr(unsafe.Pointer(fint)))) 414 dumpint(uint64(uintptr(unsafe.Pointer(ot)))) 415 } 416 417 func dumproots() { 418 // TODO(mwhudson): dump datamask etc from all objects 419 // data segment 420 dumpint(tagData) 421 dumpint(uint64(firstmoduledata.data)) 422 dumpmemrange(unsafe.Pointer(firstmoduledata.data), firstmoduledata.edata-firstmoduledata.data) 423 dumpfields(firstmoduledata.gcdatamask) 424 425 // bss segment 426 dumpint(tagBSS) 427 dumpint(uint64(firstmoduledata.bss)) 428 dumpmemrange(unsafe.Pointer(firstmoduledata.bss), firstmoduledata.ebss-firstmoduledata.bss) 429 dumpfields(firstmoduledata.gcbssmask) 430 431 // MSpan.types 432 for _, s := range mheap_.allspans { 433 if s.state == mSpanInUse { 434 // Finalizers 435 for sp := s.specials; sp != nil; sp = sp.next { 436 if sp.kind != _KindSpecialFinalizer { 437 continue 438 } 439 spf := (*specialfinalizer)(unsafe.Pointer(sp)) 440 p := unsafe.Pointer(s.base() + uintptr(spf.special.offset)) 441 dumpfinalizer(p, spf.fn, spf.fint, spf.ot) 442 } 443 } 444 } 445 446 // Finalizer queue 447 iterate_finq(finq_callback) 448 } 449 450 // Bit vector of free marks. 451 // Needs to be as big as the largest number of objects per span. 452 var freemark [_PageSize / 8]bool 453 454 func dumpobjs() { 455 for _, s := range mheap_.allspans { 456 if s.state != mSpanInUse { 457 continue 458 } 459 p := s.base() 460 size := s.elemsize 461 n := (s.npages << _PageShift) / size 462 if n > uintptr(len(freemark)) { 463 throw("freemark array doesn't have enough entries") 464 } 465 466 for freeIndex := uintptr(0); freeIndex < s.nelems; freeIndex++ { 467 if s.isFree(freeIndex) { 468 freemark[freeIndex] = true 469 } 470 } 471 472 for j := uintptr(0); j < n; j, p = j+1, p+size { 473 if freemark[j] { 474 freemark[j] = false 475 continue 476 } 477 dumpobj(unsafe.Pointer(p), size, makeheapobjbv(p, size)) 478 } 479 } 480 } 481 482 func dumpparams() { 483 dumpint(tagParams) 484 x := uintptr(1) 485 if *(*byte)(unsafe.Pointer(&x)) == 1 { 486 dumpbool(false) // little-endian ptrs 487 } else { 488 dumpbool(true) // big-endian ptrs 489 } 490 dumpint(sys.PtrSize) 491 var arenaStart, arenaEnd uintptr 492 for i1 := range mheap_.arenas { 493 if mheap_.arenas[i1] == nil { 494 continue 495 } 496 for i, ha := range mheap_.arenas[i1] { 497 if ha == nil { 498 continue 499 } 500 base := arenaBase(arenaIdx(i1)<<arenaL1Shift | arenaIdx(i)) 501 if arenaStart == 0 || base < arenaStart { 502 arenaStart = base 503 } 504 if base+heapArenaBytes > arenaEnd { 505 arenaEnd = base + heapArenaBytes 506 } 507 } 508 } 509 dumpint(uint64(arenaStart)) 510 dumpint(uint64(arenaEnd)) 511 dumpstr(sys.GOARCH) 512 dumpstr(sys.Goexperiment) 513 dumpint(uint64(ncpu)) 514 } 515 516 func itab_callback(tab *itab) { 517 t := tab._type 518 dumptype(t) 519 dumpint(tagItab) 520 dumpint(uint64(uintptr(unsafe.Pointer(tab)))) 521 dumpint(uint64(uintptr(unsafe.Pointer(t)))) 522 } 523 524 func dumpitabs() { 525 iterate_itabs(itab_callback) 526 } 527 528 func dumpms() { 529 for mp := allm; mp != nil; mp = mp.alllink { 530 dumpint(tagOSThread) 531 dumpint(uint64(uintptr(unsafe.Pointer(mp)))) 532 dumpint(uint64(mp.id)) 533 dumpint(mp.procid) 534 } 535 } 536 537 func dumpmemstats() { 538 dumpint(tagMemStats) 539 dumpint(memstats.alloc) 540 dumpint(memstats.total_alloc) 541 dumpint(memstats.sys) 542 dumpint(memstats.nlookup) 543 dumpint(memstats.nmalloc) 544 dumpint(memstats.nfree) 545 dumpint(memstats.heap_alloc) 546 dumpint(memstats.heap_sys) 547 dumpint(memstats.heap_idle) 548 dumpint(memstats.heap_inuse) 549 dumpint(memstats.heap_released) 550 dumpint(memstats.heap_objects) 551 dumpint(memstats.stacks_inuse) 552 dumpint(memstats.stacks_sys) 553 dumpint(memstats.mspan_inuse) 554 dumpint(memstats.mspan_sys) 555 dumpint(memstats.mcache_inuse) 556 dumpint(memstats.mcache_sys) 557 dumpint(memstats.buckhash_sys) 558 dumpint(memstats.gc_sys) 559 dumpint(memstats.other_sys) 560 dumpint(memstats.next_gc) 561 dumpint(memstats.last_gc_unix) 562 dumpint(memstats.pause_total_ns) 563 for i := 0; i < 256; i++ { 564 dumpint(memstats.pause_ns[i]) 565 } 566 dumpint(uint64(memstats.numgc)) 567 } 568 569 func dumpmemprof_callback(b *bucket, nstk uintptr, pstk *uintptr, size, allocs, frees uintptr) { 570 stk := (*[100000]uintptr)(unsafe.Pointer(pstk)) 571 dumpint(tagMemProf) 572 dumpint(uint64(uintptr(unsafe.Pointer(b)))) 573 dumpint(uint64(size)) 574 dumpint(uint64(nstk)) 575 for i := uintptr(0); i < nstk; i++ { 576 pc := stk[i] 577 f := findfunc(pc) 578 if !f.valid() { 579 var buf [64]byte 580 n := len(buf) 581 n-- 582 buf[n] = ')' 583 if pc == 0 { 584 n-- 585 buf[n] = '0' 586 } else { 587 for pc > 0 { 588 n-- 589 buf[n] = "0123456789abcdef"[pc&15] 590 pc >>= 4 591 } 592 } 593 n-- 594 buf[n] = 'x' 595 n-- 596 buf[n] = '0' 597 n-- 598 buf[n] = '(' 599 dumpslice(buf[n:]) 600 dumpstr("?") 601 dumpint(0) 602 } else { 603 dumpstr(funcname(f)) 604 if i > 0 && pc > f.entry { 605 pc-- 606 } 607 file, line := funcline(f, pc) 608 dumpstr(file) 609 dumpint(uint64(line)) 610 } 611 } 612 dumpint(uint64(allocs)) 613 dumpint(uint64(frees)) 614 } 615 616 func dumpmemprof() { 617 iterate_memprof(dumpmemprof_callback) 618 for _, s := range mheap_.allspans { 619 if s.state != mSpanInUse { 620 continue 621 } 622 for sp := s.specials; sp != nil; sp = sp.next { 623 if sp.kind != _KindSpecialProfile { 624 continue 625 } 626 spp := (*specialprofile)(unsafe.Pointer(sp)) 627 p := s.base() + uintptr(spp.special.offset) 628 dumpint(tagAllocSample) 629 dumpint(uint64(p)) 630 dumpint(uint64(uintptr(unsafe.Pointer(spp.b)))) 631 } 632 } 633 } 634 635 var dumphdr = []byte("go1.7 heap dump\n") 636 637 func mdump() { 638 // make sure we're done sweeping 639 for _, s := range mheap_.allspans { 640 if s.state == mSpanInUse { 641 s.ensureSwept() 642 } 643 } 644 memclrNoHeapPointers(unsafe.Pointer(&typecache), unsafe.Sizeof(typecache)) 645 dwrite(unsafe.Pointer(&dumphdr[0]), uintptr(len(dumphdr))) 646 dumpparams() 647 dumpitabs() 648 dumpobjs() 649 dumpgs() 650 dumpms() 651 dumproots() 652 dumpmemstats() 653 dumpmemprof() 654 dumpint(tagEOF) 655 flush() 656 } 657 658 func writeheapdump_m(fd uintptr) { 659 _g_ := getg() 660 casgstatus(_g_.m.curg, _Grunning, _Gwaiting) 661 _g_.waitreason = waitReasonDumpingHeap 662 663 // Update stats so we can dump them. 664 // As a side effect, flushes all the MCaches so the MSpan.freelist 665 // lists contain all the free objects. 666 updatememstats() 667 668 // Set dump file. 669 dumpfd = fd 670 671 // Call dump routine. 672 mdump() 673 674 // Reset dump file. 675 dumpfd = 0 676 if tmpbuf != nil { 677 sysFree(unsafe.Pointer(&tmpbuf[0]), uintptr(len(tmpbuf)), &memstats.other_sys) 678 tmpbuf = nil 679 } 680 681 casgstatus(_g_.m.curg, _Gwaiting, _Grunning) 682 } 683 684 // dumpint() the kind & offset of each field in an object. 685 func dumpfields(bv bitvector) { 686 dumpbv(&bv, 0) 687 dumpint(fieldKindEol) 688 } 689 690 func makeheapobjbv(p uintptr, size uintptr) bitvector { 691 // Extend the temp buffer if necessary. 692 nptr := size / sys.PtrSize 693 if uintptr(len(tmpbuf)) < nptr/8+1 { 694 if tmpbuf != nil { 695 sysFree(unsafe.Pointer(&tmpbuf[0]), uintptr(len(tmpbuf)), &memstats.other_sys) 696 } 697 n := nptr/8 + 1 698 p := sysAlloc(n, &memstats.other_sys) 699 if p == nil { 700 throw("heapdump: out of memory") 701 } 702 tmpbuf = (*[1 << 30]byte)(p)[:n] 703 } 704 // Convert heap bitmap to pointer bitmap. 705 for i := uintptr(0); i < nptr/8+1; i++ { 706 tmpbuf[i] = 0 707 } 708 i := uintptr(0) 709 hbits := heapBitsForAddr(p) 710 for ; i < nptr; i++ { 711 if i != 1 && !hbits.morePointers() { 712 break // end of object 713 } 714 if hbits.isPointer() { 715 tmpbuf[i/8] |= 1 << (i % 8) 716 } 717 hbits = hbits.next() 718 } 719 return bitvector{int32(i), &tmpbuf[0]} 720 }