libgo: reduce overhead for memory/block/mutex profiling
authorIan Lance Taylor <ian@gcc.gnu.org>
Tue, 14 May 2019 14:59:42 +0000 (14:59 +0000)
committerIan Lance Taylor <ian@gcc.gnu.org>
Tue, 14 May 2019 14:59:42 +0000 (14:59 +0000)
    Revise the gccgo version of memory/block/mutex profiling to reduce
    runtime overhead. The main change is to collect raw stack traces while
    the profile is on line, then post-process the stacks just prior to the
    point where we are ready to use the final product. Memory profiling
    (at a very low sampling rate) is enabled by default, and the overhead
    of the symbolization / DWARF-reading from backtrace_full was slowing
    things down relative to the main Go runtime.

    Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/171497

From-SVN: r271172

gcc/go/gofrontend/MERGE
libgo/go/runtime/heapdump.go
libgo/go/runtime/mgcmark.go
libgo/go/runtime/mprof.go
libgo/go/runtime/panic.go
libgo/go/runtime/string.go
libgo/go/runtime/symtab.go
libgo/go/runtime/traceback_gccgo.go
libgo/runtime/go-caller.c
libgo/runtime/go-callers.c
libgo/runtime/runtime.h

index bdf57761a4e5ff9a54c7992df46bd684f2251b8e..672ad671bb31097d515cbf96b381e79aba82789f 100644 (file)
@@ -1,4 +1,4 @@
-3f015e128bf6d1d9279f3d43e26f60f0927019cb
+6112f9b8fa9d57d2db8a709cc8b44a94d778d08a
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
index 3aa9e8a23f29494abe407a87a974ff971dba2590..b0506a8e47526289360032a2dda97c3bd68aa9f1 100644 (file)
@@ -437,17 +437,15 @@ func dumpmemstats() {
        dumpint(uint64(memstats.numgc))
 }
 
-func dumpmemprof_callback(b *bucket, nstk uintptr, pstk *location, size, allocs, frees uintptr) {
-       stk := (*[100000]location)(unsafe.Pointer(pstk))
+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].pc
-               fn := stk[i].function
-               file := stk[i].filename
-               line := stk[i].lineno
+               pc := stk[i]
+               fn, file, line, _ := funcfileline(pc, -1)
                if fn == "" {
                        var buf [64]byte
                        n := len(buf)
index dc5e797739ed6ce19ea701e926169dfc689df127..1b8a7a3ddd77a416bc67c255d1cf68b2031b05c0 100644 (file)
@@ -1085,7 +1085,7 @@ func scanstackblockwithmap(pc, b0, n0 uintptr, ptrmask *uint8, gcw *gcWork) {
                                                span != nil && span.state != mSpanManual &&
                                                        (obj < span.base() || obj >= span.limit || span.state != mSpanInUse) {
                                                print("runtime: found in object at *(", hex(b), "+", hex(i), ") = ", hex(obj), ", pc=", hex(pc), "\n")
-                                               name, file, line := funcfileline(pc, -1)
+                                               name, file, line, _ := funcfileline(pc, -1)
                                                print(name, "\n", file, ":", line, "\n")
                                                //gcDumpObject("object", b, i)
                                                throw("found bad pointer in Go stack (incorrect use of unsafe or cgo?)")
index ab975696fe67da8fedf99d00e8104a26fa0c5f38..9238e2bb012651521066356cf6fe8fdb351f85c1 100644 (file)
@@ -24,6 +24,10 @@ const (
        blockProfile
        mutexProfile
 
+       // a profile bucket from one of the categories above whose stack
+       // trace has been fixed up / pruned.
+       prunedProfile
+
        // size of bucket hash table
        buckHashSize = 179999
 
@@ -138,11 +142,13 @@ type blockRecord struct {
 }
 
 var (
-       mbuckets  *bucket // memory profile buckets
-       bbuckets  *bucket // blocking profile buckets
-       xbuckets  *bucket // mutex profile buckets
-       buckhash  *[179999]*bucket
-       bucketmem uintptr
+       mbuckets    *bucket // memory profile buckets
+       bbuckets    *bucket // blocking profile buckets
+       xbuckets    *bucket // mutex profile buckets
+       sbuckets    *bucket // pre-symbolization profile buckets (stacks fixed up)
+       freebuckets *bucket // freelist of unused fixed up profile buckets
+       buckhash    *[179999]*bucket
+       bucketmem   uintptr
 
        mProf struct {
                // All fields in mProf are protected by proflock.
@@ -158,12 +164,35 @@ var (
 
 const mProfCycleWrap = uint32(len(memRecord{}.future)) * (2 << 24)
 
+// payloadOffset() returns a pointer into the part of a bucket
+// containing the profile payload (skips past the bucket struct itself
+// and then the stack trace).
+func payloadOffset(typ bucketType, nstk uintptr) uintptr {
+       if typ == prunedProfile {
+               // To allow reuse of prunedProfile buckets between different
+               // collections, allocate them with the max stack size (the portion
+               // of the stack used will vary from trace to trace).
+               nstk = maxStack
+       }
+       return unsafe.Sizeof(bucket{}) + uintptr(nstk)*unsafe.Sizeof(uintptr)
+}
+
+func max(x, y uintptr) uintptr {
+       if x > y {
+               return x
+       }
+       return y
+}
+
 // newBucket allocates a bucket with the given type and number of stack entries.
 func newBucket(typ bucketType, nstk int) *bucket {
-       size := unsafe.Sizeof(bucket{}) + uintptr(nstk)*unsafe.Sizeof(location{})
+       size := payloadOffset(typ, uintptr(nstk))
        switch typ {
        default:
                throw("invalid profile bucket type")
+       case prunedProfile:
+               // stack-fixed buckets are large enough to accommodate any payload.
+               size += max(unsafe.Sizeof(memRecord{}), unsafe.Sizeof(blockRecord{}))
        case memProfile:
                size += unsafe.Sizeof(memRecord{})
        case blockProfile, mutexProfile:
@@ -178,31 +207,29 @@ func newBucket(typ bucketType, nstk int) *bucket {
 }
 
 // stk returns the slice in b holding the stack.
-func (b *bucket) stk() []location {
-       stk := (*[maxStack]location)(add(unsafe.Pointer(b), unsafe.Sizeof(*b)))
+func (b *bucket) stk() []uintptr {
+       stk := (*[maxStack]uintptr)(add(unsafe.Pointer(b), unsafe.Sizeof(*b)))
        return stk[:b.nstk:b.nstk]
 }
 
 // mp returns the memRecord associated with the memProfile bucket b.
 func (b *bucket) mp() *memRecord {
-       if b.typ != memProfile {
+       if b.typ != memProfile && b.typ != prunedProfile {
                throw("bad use of bucket.mp")
        }
-       data := add(unsafe.Pointer(b), unsafe.Sizeof(*b)+b.nstk*unsafe.Sizeof(location{}))
-       return (*memRecord)(data)
+       return (*memRecord)(add(unsafe.Pointer(b), payloadOffset(b.typ, b.nstk)))
 }
 
 // bp returns the blockRecord associated with the blockProfile bucket b.
 func (b *bucket) bp() *blockRecord {
-       if b.typ != blockProfile && b.typ != mutexProfile {
+       if b.typ != blockProfile && b.typ != mutexProfile && b.typ != prunedProfile {
                throw("bad use of bucket.bp")
        }
-       data := add(unsafe.Pointer(b), unsafe.Sizeof(*b)+b.nstk*unsafe.Sizeof(location{}))
-       return (*blockRecord)(data)
+       return (*blockRecord)(add(unsafe.Pointer(b), payloadOffset(b.typ, b.nstk)))
 }
 
 // Return the bucket for stk[0:nstk], allocating new bucket if needed.
-func stkbucket(typ bucketType, size uintptr, stk []location, alloc bool) *bucket {
+func stkbucket(typ bucketType, size uintptr, stk []uintptr, alloc bool) *bucket {
        if buckhash == nil {
                buckhash = (*[buckHashSize]*bucket)(sysAlloc(unsafe.Sizeof(*buckhash), &memstats.buckhash_sys))
                if buckhash == nil {
@@ -212,8 +239,8 @@ func stkbucket(typ bucketType, size uintptr, stk []location, alloc bool) *bucket
 
        // Hash stack.
        var h uintptr
-       for _, loc := range stk {
-               h += loc.pc
+       for _, pc := range stk {
+               h += pc
                h += h << 10
                h ^= h >> 6
        }
@@ -249,6 +276,9 @@ func stkbucket(typ bucketType, size uintptr, stk []location, alloc bool) *bucket
        } else if typ == mutexProfile {
                b.allnext = xbuckets
                xbuckets = b
+       } else if typ == prunedProfile {
+               b.allnext = sbuckets
+               sbuckets = b
        } else {
                b.allnext = bbuckets
                bbuckets = b
@@ -256,7 +286,7 @@ func stkbucket(typ bucketType, size uintptr, stk []location, alloc bool) *bucket
        return b
 }
 
-func eqslice(x, y []location) bool {
+func eqslice(x, y []uintptr) bool {
        if len(x) != len(y) {
                return false
        }
@@ -338,8 +368,8 @@ func mProf_PostSweep() {
 
 // Called by malloc to record a profiled block.
 func mProf_Malloc(p unsafe.Pointer, size uintptr) {
-       var stk [maxStack]location
-       nstk := callers(4, stk[:])
+       var stk [maxStack]uintptr
+       nstk := callersRaw(1, stk[:])
        lock(&proflock)
        b := stkbucket(memProfile, size, stk[:nstk], true)
        c := mProf.cycle
@@ -414,13 +444,13 @@ func blocksampled(cycles int64) bool {
 func saveblockevent(cycles int64, skip int, which bucketType) {
        gp := getg()
        var nstk int
-       var stk [maxStack]location
+       var stk [maxStack]uintptr
        if gp.m.curg == nil || gp.m.curg == gp {
-               nstk = callers(skip, stk[:])
+               nstk = callersRaw(skip, stk[:])
        } else {
                // FIXME: This should get a traceback of gp.m.curg.
                // nstk = gcallers(gp.m.curg, skip, stk[:])
-               nstk = callers(skip, stk[:])
+               nstk = callersRaw(skip, stk[:])
        }
        lock(&proflock)
        b := stkbucket(which, 0, stk[:nstk], true)
@@ -521,6 +551,150 @@ func (r *MemProfileRecord) Stack() []uintptr {
        return r.Stack0[0:]
 }
 
+// reusebucket tries to pick a prunedProfile bucket off
+// the freebuckets list, returning it if one is available or nil
+// if the free list is empty.
+func reusebucket(nstk int) *bucket {
+       var b *bucket
+       if freebuckets != nil {
+               b = freebuckets
+               freebuckets = freebuckets.allnext
+               b.typ = prunedProfile
+               b.nstk = uintptr(nstk)
+               mp := b.mp()
+               // Hack: rely on the fact that memprofile records are
+               // larger than blockprofile records when clearing.
+               *mp = memRecord{}
+       }
+       return b
+}
+
+// freebucket appends the specified prunedProfile bucket
+// onto the free list, and removes references to it from the hash.
+func freebucket(tofree *bucket) *bucket {
+       // Thread this bucket into the free list.
+       ret := tofree.allnext
+       tofree.allnext = freebuckets
+       freebuckets = tofree
+
+       // Clean up the hash. The hash may point directly to this bucket...
+       i := int(tofree.hash % buckHashSize)
+       if buckhash[i] == tofree {
+               buckhash[i] = tofree.next
+       } else {
+               // ... or when this bucket was inserted by stkbucket, it may have been
+               // chained off some other unrelated bucket.
+               for b := buckhash[i]; b != nil; b = b.next {
+                       if b.next == tofree {
+                               b.next = tofree.next
+                               break
+                       }
+               }
+       }
+       return ret
+}
+
+// fixupStack takes a 'raw' stack trace (stack of PCs generated by
+// callersRaw) and performs pre-symbolization fixup on it, returning
+// the results in 'canonStack'. For each frame we look at the
+// file/func/line information, then use that info to decide whether to
+// include the frame in the final symbolized stack (removing frames
+// corresponding to 'morestack' routines, for example). We also expand
+// frames if the PC values to which they refer correponds to inlined
+// functions to allow for expanded symbolic info to be filled in
+// later. Note: there is code in go-callers.c's backtrace_full callback()
+// function that performs very similar fixups; these two code paths
+// should be kept in sync.
+func fixupStack(stk []uintptr, canonStack *[maxStack]uintptr, size uintptr) int {
+       var cidx int
+       var termTrace bool
+       for _, pc := range stk {
+               // Subtract 1 from PC to undo the 1 we added in callback in
+               // go-callers.c.
+               function, file, _, frames := funcfileline(pc-1, -1)
+
+               // Skip split-stack functions (match by function name)
+               skipFrame := false
+               if hasPrefix(function, "_____morestack_") || hasPrefix(function, "__morestack_") {
+                       skipFrame = true
+               }
+
+               // Skip split-stack functions (match by file)
+               if hasSuffix(file, "/morestack.S") {
+                       skipFrame = true
+               }
+
+               // Skip thunks and recover functions.  There is no equivalent to
+               // these functions in the gc toolchain.
+               fcn := function
+               if hasSuffix(fcn, "..r") {
+                       skipFrame = true
+               } else {
+                       for fcn != "" && (fcn[len(fcn)-1] >= '0' && fcn[len(fcn)-1] <= '9') {
+                               fcn = fcn[:len(fcn)-1]
+                       }
+                       if hasSuffix(fcn, "..stub") || hasSuffix(fcn, "..thunk") {
+                               skipFrame = true
+                       }
+               }
+               if skipFrame {
+                       continue
+               }
+
+               // Terminate the trace if we encounter a frame corresponding to
+               // runtime.main, runtime.kickoff, makecontext, etc. See the
+               // corresponding code in go-callers.c, callback function used
+               // with backtrace_full.
+               if function == "makecontext" {
+                       termTrace = true
+               }
+               if hasSuffix(file, "/proc.c") && function == "runtime_mstart" {
+                       termTrace = true
+               }
+               if hasSuffix(file, "/proc.go") &&
+                       (function == "runtime.main" || function == "runtime.kickoff") {
+                       termTrace = true
+               }
+
+               // Expand inline frames.
+               for i := 0; i < frames; i++ {
+                       (*canonStack)[cidx] = pc
+                       cidx++
+                       if cidx >= maxStack {
+                               termTrace = true
+                               break
+                       }
+               }
+               if termTrace {
+                       break
+               }
+       }
+       return cidx
+}
+
+// fixupBucket takes a raw memprofile bucket and creates a new bucket
+// in which the stack trace has been fixed up (inline frames expanded,
+// unwanted frames stripped out). Original bucket is left unmodified;
+// a new symbolizeProfile bucket may be generated as a side effect.
+// Payload information from the original bucket is incorporated into
+// the new bucket.
+func fixupBucket(b *bucket) {
+       var canonStack [maxStack]uintptr
+       frames := fixupStack(b.stk(), &canonStack, b.size)
+       cb := stkbucket(prunedProfile, b.size, canonStack[:frames], true)
+       switch b.typ {
+       default:
+               throw("invalid profile bucket type")
+       case memProfile:
+               rawrecord := b.mp()
+               cb.mp().active.add(&rawrecord.active)
+       case blockProfile, mutexProfile:
+               bpcount := b.bp().count
+               cb.bp().count += bpcount
+               cb.bp().cycles += bpcount
+       }
+}
+
 // MemProfile returns a profile of memory allocated and freed per allocation
 // site.
 //
@@ -576,15 +750,31 @@ func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) {
                }
        }
        if n <= len(p) {
-               ok = true
-               idx := 0
-               for b := mbuckets; b != nil; b = b.allnext {
+               var bnext *bucket
+
+               // Post-process raw buckets to fix up their stack traces
+               for b := mbuckets; b != nil; b = bnext {
+                       bnext = b.allnext
                        mp := b.mp()
                        if inuseZero || mp.active.alloc_bytes != mp.active.free_bytes {
-                               record(&p[idx], b)
-                               idx++
+                               fixupBucket(b)
                        }
                }
+
+               // Record pruned/fixed-up buckets
+               ok = true
+               idx := 0
+               for b := sbuckets; b != nil; b = b.allnext {
+                       record(&p[idx], b)
+                       idx++
+               }
+               n = idx
+
+               // Free up pruned buckets for use in next round
+               for b := sbuckets; b != nil; b = bnext {
+                       bnext = freebucket(b)
+               }
+               sbuckets = nil
        }
        unlock(&proflock)
        return
@@ -597,18 +787,18 @@ func record(r *MemProfileRecord, b *bucket) {
        r.FreeBytes = int64(mp.active.free_bytes)
        r.AllocObjects = int64(mp.active.allocs)
        r.FreeObjects = int64(mp.active.frees)
-       for i, loc := range b.stk() {
+       for i, pc := range b.stk() {
                if i >= len(r.Stack0) {
                        break
                }
-               r.Stack0[i] = loc.pc
+               r.Stack0[i] = pc
        }
        for i := int(b.nstk); i < len(r.Stack0); i++ {
                r.Stack0[i] = 0
        }
 }
 
-func iterate_memprof(fn func(*bucket, uintptr, *location, uintptr, uintptr, uintptr)) {
+func iterate_memprof(fn func(*bucket, uintptr, *uintptr, uintptr, uintptr, uintptr)) {
        lock(&proflock)
        for b := mbuckets; b != nil; b = b.allnext {
                mp := b.mp()
@@ -625,39 +815,59 @@ type BlockProfileRecord struct {
        StackRecord
 }
 
-// BlockProfile returns n, the number of records in the current blocking profile.
-// If len(p) >= n, BlockProfile copies the profile into p and returns n, true.
-// If len(p) < n, BlockProfile does not change p and returns n, false.
-//
-// Most clients should use the runtime/pprof package or
-// the testing package's -test.blockprofile flag instead
-// of calling BlockProfile directly.
-func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
-       lock(&proflock)
-       for b := bbuckets; b != nil; b = b.allnext {
+func harvestBlockMutexProfile(buckets *bucket, p []BlockProfileRecord) (n int, ok bool) {
+       for b := buckets; b != nil; b = b.allnext {
                n++
        }
        if n <= len(p) {
+               var bnext *bucket
+
+               // Post-process raw buckets to create pruned/fixed-up buckets
+               for b := buckets; b != nil; b = bnext {
+                       bnext = b.allnext
+                       fixupBucket(b)
+               }
+
+               // Record
                ok = true
-               for b := bbuckets; b != nil; b = b.allnext {
+               for b := sbuckets; b != nil; b = b.allnext {
                        bp := b.bp()
                        r := &p[0]
                        r.Count = bp.count
                        r.Cycles = bp.cycles
                        i := 0
-                       var loc location
-                       for i, loc = range b.stk() {
+                       var pc uintptr
+                       for i, pc = range b.stk() {
                                if i >= len(r.Stack0) {
                                        break
                                }
-                               r.Stack0[i] = loc.pc
+                               r.Stack0[i] = pc
                        }
                        for ; i < len(r.Stack0); i++ {
                                r.Stack0[i] = 0
                        }
                        p = p[1:]
                }
+
+               // Free up pruned buckets for use in next round.
+               for b := sbuckets; b != nil; b = bnext {
+                       bnext = freebucket(b)
+               }
+               sbuckets = nil
        }
+       return
+}
+
+// BlockProfile returns n, the number of records in the current blocking profile.
+// If len(p) >= n, BlockProfile copies the profile into p and returns n, true.
+// If len(p) < n, BlockProfile does not change p and returns n, false.
+//
+// Most clients should use the runtime/pprof package or
+// the testing package's -test.blockprofile flag instead
+// of calling BlockProfile directly.
+func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
+       lock(&proflock)
+       n, ok = harvestBlockMutexProfile(bbuckets, p)
        unlock(&proflock)
        return
 }
@@ -670,30 +880,7 @@ func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
 // instead of calling MutexProfile directly.
 func MutexProfile(p []BlockProfileRecord) (n int, ok bool) {
        lock(&proflock)
-       for b := xbuckets; b != nil; b = b.allnext {
-               n++
-       }
-       if n <= len(p) {
-               ok = true
-               for b := xbuckets; b != nil; b = b.allnext {
-                       bp := b.bp()
-                       r := &p[0]
-                       r.Count = int64(bp.count)
-                       r.Cycles = bp.cycles
-                       i := 0
-                       var loc location
-                       for i, loc = range b.stk() {
-                               if i >= len(r.Stack0) {
-                                       break
-                               }
-                               r.Stack0[i] = loc.pc
-                       }
-                       for ; i < len(r.Stack0); i++ {
-                               r.Stack0[i] = 0
-                       }
-                       p = p[1:]
-               }
-       }
+       n, ok = harvestBlockMutexProfile(xbuckets, p)
        unlock(&proflock)
        return
 }
index 9b8ffb91859d7585e632ec9a0077eaeb167285d7..264ad38cc1f26326d424d51463654f9848115b9d 100644 (file)
@@ -53,7 +53,7 @@ var indexError = error(errorString("index out of range"))
 // entire runtime stack for easier debugging.
 
 func panicindex() {
-       name, _, _ := funcfileline(getcallerpc()-1, -1)
+       name, _, _, _ := funcfileline(getcallerpc()-1, -1)
        if hasPrefix(name, "runtime.") {
                throw(string(indexError.(errorString)))
        }
@@ -64,7 +64,7 @@ func panicindex() {
 var sliceError = error(errorString("slice bounds out of range"))
 
 func panicslice() {
-       name, _, _ := funcfileline(getcallerpc()-1, -1)
+       name, _, _, _ := funcfileline(getcallerpc()-1, -1)
        if hasPrefix(name, "runtime.") {
                throw(string(sliceError.(errorString)))
        }
index 025ea7a3bd770c36c423f2919f67fc28dd44dac3..eac94bf6a8a90993ee946674a69ca31165977ad3 100644 (file)
@@ -360,6 +360,10 @@ func hasPrefix(s, prefix string) bool {
        return len(s) >= len(prefix) && s[:len(prefix)] == prefix
 }
 
+func hasSuffix(s, suffix string) bool {
+       return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
+}
+
 const (
        maxUint = ^uint(0)
        maxInt  = int(maxUint >> 1)
index d7e8c18b51740865b2eb0f157214eb7b456a55bf..8f3c843a78b1e6f0e5b52e37acd7a9d27e23a3bc 100644 (file)
@@ -79,7 +79,7 @@ func (ci *Frames) Next() (frame Frame, more bool) {
 
        // Subtract 1 from PC to undo the 1 we added in callback in
        // go-callers.c.
-       function, file, line := funcfileline(pc-1, int32(i))
+       function, file, line, _ := funcfileline(pc-1, int32(i))
        if function == "" && file == "" {
                return Frame{}, more
        }
@@ -158,7 +158,7 @@ const (
 // the a *Func describing the innermost function, but with an entry
 // of the outermost function.
 func FuncForPC(pc uintptr) *Func {
-       name, _, _ := funcfileline(pc, -1)
+       name, _, _, _ := funcfileline(pc, -1)
        if name == "" {
                return nil
        }
@@ -187,7 +187,7 @@ func (f *Func) Entry() uintptr {
 // The result will not be accurate if pc is not a program
 // counter within f.
 func (f *Func) FileLine(pc uintptr) (file string, line int) {
-       _, file, line = funcfileline(pc, -1)
+       _, file, line, _ = funcfileline(pc, -1)
        return file, line
 }
 
@@ -261,5 +261,5 @@ func demangleSymbol(s string) string {
 }
 
 // implemented in go-caller.c
-func funcfileline(uintptr, int32) (string, string, int)
+func funcfileline(uintptr, int32) (string, string, int, int)
 func funcentry(uintptr) uintptr
index 7581798e852b4cf6b2981aa3d8ad07a2723074f0..b0eecf2894e8e6614bb6131c0ad4f95fb47313b2 100644 (file)
@@ -20,7 +20,7 @@ func printcreatedby(gp *g) {
        if entry != 0 && tracepc > entry {
                tracepc -= sys.PCQuantum
        }
-       function, file, line := funcfileline(tracepc, -1)
+       function, file, line, _ := funcfileline(tracepc, -1)
        if function != "" && showframe(function, gp, false) && gp.goid != 1 {
                printcreatedby1(function, file, line, entry, pc)
        }
@@ -61,6 +61,16 @@ func callers(skip int, locbuf []location) int {
        return int(n)
 }
 
+//go:noescape
+//extern runtime_callersRaw
+func c_callersRaw(skip int32, pcs *uintptr, max int32) int32
+
+// callersRaw returns a raw (PCs only) stack trace of the current goroutine.
+func callersRaw(skip int, pcbuf []uintptr) int {
+       n := c_callersRaw(int32(skip)+1, &pcbuf[0], int32(len(pcbuf)))
+       return int(n)
+}
+
 // traceback prints a traceback of the current goroutine.
 // This differs from the gc version, which is given pc, sp, lr and g and
 // can print a traceback of any goroutine.
@@ -83,7 +93,7 @@ func traceback(skip int32) {
 func printAncestorTraceback(ancestor ancestorInfo) {
        print("[originating from goroutine ", ancestor.goid, "]:\n")
        for fidx, pc := range ancestor.pcs {
-               function, file, line := funcfileline(pc, -1)
+               function, file, line, _ := funcfileline(pc, -1)
                if showfuncinfo(function, fidx == 0) {
                        printAncestorTracebackFuncInfo(function, file, line, pc)
                }
@@ -92,7 +102,7 @@ func printAncestorTraceback(ancestor ancestorInfo) {
                print("...additional frames elided...\n")
        }
        // Show what created goroutine, except main goroutine (goid 1).
-       function, file, line := funcfileline(ancestor.gopc, -1)
+       function, file, line, _ := funcfileline(ancestor.gopc, -1)
        if function != "" && showfuncinfo(function, false) && ancestor.goid != 1 {
                printcreatedby1(function, file, line, funcentry(ancestor.gopc), ancestor.gopc)
        }
index 214344694e84c169ce09e278662678a1892701b3..5e31f912e0ae761ae3274f3a7e453f3284d14fff 100644 (file)
@@ -26,11 +26,13 @@ struct caller
   String file;
   intgo line;
   intgo index;
+  intgo frames;
 };
 
 /* Collect file/line information for a PC value.  If this is called
-   more than once, due to inlined functions, we use the last call, as
-   that is usually the most useful one.  */
+   more than once, due to inlined functions, we record the number of
+   inlined frames but return file/func/line for the last call, as
+   that is usually the most useful one.   */
 
 static int
 callback (void *data, uintptr_t pc __attribute__ ((unused)),
@@ -38,6 +40,8 @@ callback (void *data, uintptr_t pc __attribute__ ((unused)),
 {
   struct caller *c = (struct caller *) data;
 
+  c->frames++;
+
   /* The libbacktrace library says that these strings might disappear,
      but with the current implementation they won't.  We can't easily
      allocate memory here, so for now assume that we can save a
@@ -125,18 +129,19 @@ __go_get_backtrace_state ()
   return back_state;
 }
 
-/* Return function/file/line information for PC.  The index parameter
+/* Return function/file/line/nframes information for PC.  The index parameter
    is the entry on the stack of inlined functions; -1 means the last
-   one.  */
+   one, with *nframes set to the count of inlined frames for this PC.  */
 
 static _Bool
-__go_file_line (uintptr pc, int index, String *fn, String *file, intgo *line)
+__go_file_line (uintptr pc, int index, String *fn, String *file, intgo *line, intgo *nframes)
 {
   struct caller c;
   struct backtrace_state *state;
 
   runtime_memclr (&c, sizeof c);
   c.index = index;
+  c.frames = 0;
   runtime_xadd (&__go_runtime_in_callers, 1);
   state = __go_get_backtrace_state ();
   runtime_xadd (&__go_runtime_in_callers, -1);
@@ -144,6 +149,7 @@ __go_file_line (uintptr pc, int index, String *fn, String *file, intgo *line)
   *fn = c.fn;
   *file = c.file;
   *line = c.line;
+  *nframes = c.frames;
 
   // If backtrace_pcinfo didn't get the function name from the debug
   // info, try to get it from the symbol table.
@@ -222,7 +228,7 @@ runtime_funcfileline (uintptr targetpc, int32 index)
   struct funcfileline_return ret;
 
   if (!__go_file_line (targetpc, index, &ret.retfn, &ret.retfile,
-                      &ret.retline))
+                      &ret.retline, &ret.retframes))
     runtime_memclr (&ret, sizeof ret);
   return ret;
 }
index 31ff4747b98a467c8c3de089d7e50f62af6eda79..4a9c1a7b24d2091d7af58ccaca2c847229256983 100644 (file)
@@ -63,7 +63,9 @@ callback (void *data, uintptr_t pc, const char *filename, int lineno,
 
   /* Skip thunks and recover functions.  There is no equivalent to
      these functions in the gc toolchain, so returning them here means
-     significantly different results for runtime.Caller(N).  */
+     significantly different results for runtime.Caller(N). See also
+     similar code in runtime/mprof.go that strips out such functions
+     for block/mutex/memory profiles.  */
   if (function != NULL && !arg->keep_thunks)
     {
       const char *p;
@@ -262,3 +264,62 @@ Callers (intgo skip, struct __go_open_array pc)
 
   return ret;
 }
+
+struct callersRaw_data
+{
+  uintptr* pcbuf;
+  int skip;
+  int index;
+  int max;
+};
+
+// Callback function for backtrace_simple.  Just collect pc's.
+// Return zero to continue, non-zero to stop.
+
+static int callback_raw (void *data, uintptr_t pc)
+{
+  struct callersRaw_data *arg = (struct callersRaw_data *) data;
+
+  if (arg->skip > 0)
+    {
+      --arg->skip;
+      return 0;
+    }
+
+  /* On the call to backtrace_simple the pc value was most likely
+     decremented if there was a normal call, since the pc referred to
+     the instruction where the call returned and not the call itself.
+     This was done so that the line number referred to the call
+     instruction.  To make sure the actual pc from the call stack is
+     used, it is incremented here.
+
+     In the case of a signal, the pc was not decremented by
+     backtrace_full but still incremented here.  That doesn't really
+     hurt anything since the line number is right and the pc refers to
+     the same instruction.  */
+
+  arg->pcbuf[arg->index] = pc + 1;
+  arg->index++;
+  return arg->index >= arg->max;
+}
+
+/* runtime_callersRaw is similar to runtime_callers() above, but
+   it returns raw PC values as opposed to file/func/line locations. */
+int32
+runtime_callersRaw (int32 skip, uintptr *pcbuf, int32 m)
+{
+  struct callersRaw_data data;
+  struct backtrace_state* state;
+
+  data.pcbuf = pcbuf;
+  data.skip = skip + 1;
+  data.index = 0;
+  data.max = m;
+  runtime_xadd (&__go_runtime_in_callers, 1);
+  state = __go_get_backtrace_state ();
+  backtrace_simple (state, 0, callback_raw, error_callback, &data);
+  runtime_xadd (&__go_runtime_in_callers, -1);
+
+  return data.index;
+}
+
index 9506e3dec80dc1e63ccb6acbd44e0eeac9422eaf..71c1a3ee303bfabb0aaabe1eca6454740f37c795 100644 (file)
@@ -485,6 +485,7 @@ struct funcfileline_return
   String retfn;
   String retfile;
   intgo retline;
+  intgo retframes;
 };
 
 struct funcfileline_return