github.com/stealthrocket/wzprof@v0.2.1-0.20230830205924-5fa86be5e5b3/traceback.go (about)

     1  package wzprof
     2  
     3  import (
     4  	"github.com/stealthrocket/wzprof/internal/goruntime"
     5  )
     6  
     7  // An adaptation of the unwinder from go/src/runtime/traceback. It is modified
     8  // to work on a virtual memory object instead of the current program's memory,
     9  // and simplified for cases that don't concern GOARCH=wasm. uintptr has been
    10  // replaced to ptr, and architecture-dependent values replaced for wasm. It
    11  // still contains code to deal with race conditions because its was little work
    12  // to keep around, only involves pointer nil checks to execute, and may be
    13  // useful if wazero adds more concurrency when wasm threads support lands. Cgo
    14  // has been eliminated.
    15  
    16  // Copyright (c) 2009 The Go Authors. All rights reserved.
    17  //
    18  // Redistribution and use in source and binary forms, with or without
    19  // modification, are permitted provided that the following conditions are met:
    20  //
    21  //    * Redistributions of source code must retain the above copyright notice,
    22  // this list of conditions and the following disclaimer.
    23  //    * Redistributions in binary form must reproduce the above copyright
    24  // notice, this list of conditions and the following disclaimer in the
    25  // documentation and/or other materials provided with the distribution.
    26  //    * Neither the name of Google Inc. nor the names of its contributors may be
    27  // used to endorse or promote products derived from this software without
    28  // specific prior written permission.
    29  //
    30  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    31  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    32  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    33  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    34  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    35  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    36  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    37  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    38  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    39  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    40  // POSSIBILITY OF SUCH DAMAGE.
    41  
    42  // A stkframe holds information about a single physical stack frame. Adapted
    43  // from runtime/stkframe.go.
    44  type stkframe struct {
    45  	// fn is the function being run in this frame. If there is
    46  	// inlining, this is the outermost function.
    47  	fn funcInfo
    48  
    49  	// pc is the program counter within fn.
    50  	//
    51  	// The meaning of this is subtle:
    52  	//
    53  	// - Typically, this frame performed a regular function call
    54  	//   and this is the return PC (just after the CALL
    55  	//   instruction). In this case, pc-1 reflects the CALL
    56  	//   instruction itself and is the correct source of symbolic
    57  	//   information.
    58  	//
    59  	// - If this frame "called" sigpanic, then pc is the
    60  	//   instruction that panicked, and pc is the correct address
    61  	//   to use for symbolic information.
    62  	//
    63  	// - If this is the innermost frame, then PC is where
    64  	//   execution will continue, but it may not be the
    65  	//   instruction following a CALL. This may be from
    66  	//   cooperative preemption, in which case this is the
    67  	//   instruction after the call to morestack. Or this may be
    68  	//   from a signal or an un-started goroutine, in which case
    69  	//   PC could be any instruction, including the first
    70  	//   instruction in a function. Conventionally, we use pc-1
    71  	//   for symbolic information, unless pc == fn.entry(), in
    72  	//   which case we use pc.
    73  	pc   ptr64
    74  	lr   ptr64 // program counter at caller aka link register
    75  	sp   ptr64 // stack pointer at pc
    76  	fp   ptr64 // stack pointer at caller aka frame pointer
    77  	varp ptr64 // top of local variables
    78  }
    79  
    80  // unwindFlags control the behavior of various unwinders.
    81  type unwindFlags uint8
    82  
    83  const (
    84  	// unwindPrintErrors indicates that if unwinding encounters an error, it
    85  	// should print a message and stop without throwing. This is used for things
    86  	// like stack printing, where it's better to get incomplete information than
    87  	// to crash. This is also used in situations where everything may not be
    88  	// stopped nicely and the stack walk may not be able to complete, such as
    89  	// during profiling signals or during a crash.
    90  	//
    91  	// If neither unwindPrintErrors or unwindSilentErrors are set, unwinding
    92  	// performs extra consistency checks and throws on any error.
    93  	//
    94  	// Note that there are a small number of fatal situations that will throw
    95  	// regardless of unwindPrintErrors or unwindSilentErrors.
    96  	unwindPrintErrors unwindFlags = 1 << iota
    97  
    98  	// unwindSilentErrors silently ignores errors during unwinding.
    99  	unwindSilentErrors
   100  
   101  	// unwindTrap indicates that the initial PC and SP are from a trap, not a
   102  	// return PC from a call.
   103  	//
   104  	// The unwindTrap flag is updated during unwinding. If set, frame.pc is the
   105  	// address of a faulting instruction instead of the return address of a
   106  	// call. It also means the liveness at pc may not be known.
   107  	//
   108  	// TODO: Distinguish frame.continpc, which is really the stack map PC, from
   109  	// the actual continuation PC, which is computed differently depending on
   110  	// this flag and a few other things.
   111  	unwindTrap
   112  
   113  	// unwindJumpStack indicates that, if the traceback is on a system stack, it
   114  	// should resume tracing at the user stack when the system stack is
   115  	// exhausted.
   116  	unwindJumpStack
   117  )
   118  
   119  // An unwinder iterates the physical stack frames of a Go sack.
   120  //
   121  // Typical use of an unwinder looks like:
   122  //
   123  //	var u unwinder
   124  //	for u.init(gp, 0); u.valid(); u.next() {
   125  //		// ... use frame info in u ...
   126  //	}
   127  type unwinder struct {
   128  	mem     vmem
   129  	symbols *pclntab
   130  
   131  	// frame is the current physical stack frame, or all 0s if
   132  	// there is no frame.
   133  	frame stkframe
   134  
   135  	// g is the G who's stack is being unwound. If the
   136  	// unwindJumpStack flag is set and the unwinder jumps stacks,
   137  	// this will be different from the initial G.
   138  	g gptr
   139  
   140  	// cgoCtxt is the index into g.cgoCtxt of the next frame on the cgo stack.
   141  	// The cgo stack is unwound in tandem with the Go stack as we find marker frames.
   142  	// cgoCtxt int
   143  
   144  	// calleeFuncID is the function ID of the caller of the current
   145  	// frame.
   146  	calleeFuncID goruntime.FuncID
   147  
   148  	// flags are the flags to this unwind. Some of these are updated as we
   149  	// unwind (see the flags documentation).
   150  	flags unwindFlags
   151  }
   152  
   153  const (
   154  	goarchPtrSize = 8 // https://github.com/golang/go/blob/bd3f44e4ffe54e9cf841ebc8356e403bb38436bd/src/internal/goarch/goarch.go#L33
   155  	sysPCQuantum  = 1 // https://github.com/golang/go/blob/49ad23a6d23d6cc1666c22e4bc215f25f717b569/src/internal/goarch/goarch_wasm.go
   156  )
   157  
   158  func (u *unwinder) initAt(pc0, sp0, lr0 ptr64, gp gptr, flags unwindFlags) {
   159  	if pc0 == ptr64(^uint64(0)) && sp0 == ptr64(^uint64(0)) {
   160  		panic("should have been initialized")
   161  	}
   162  
   163  	var frame stkframe
   164  	frame.pc = pc0
   165  	frame.sp = sp0
   166  
   167  	// If the PC is zero, it's likely a nil function call.
   168  	// Start in the caller's frame.
   169  	if frame.pc == 0 {
   170  		frame.pc = deref[ptr64](u.mem, frame.sp)
   171  		frame.sp += goarchPtrSize
   172  	}
   173  
   174  	f := u.symbols.FindFunc(frame.pc)
   175  	if !f.valid() {
   176  		u.finishInternal()
   177  		return
   178  	}
   179  	frame.fn = f
   180  
   181  	// Populate the unwinder.
   182  	u.frame = frame
   183  	u.flags = flags
   184  	u.g = gp
   185  	u.calleeFuncID = goruntime.FuncIDNormal
   186  
   187  	u.resolveInternal(true)
   188  }
   189  
   190  func (u *unwinder) valid() bool {
   191  	return u.frame.pc != 0
   192  }
   193  
   194  // resolveInternal fills in u.frame based on u.frame.fn, pc, and sp.
   195  //
   196  // innermost indicates that this is the first resolve on this stack. If
   197  // innermost is set.
   198  //
   199  // On entry, u.frame contains:
   200  //   - fn is the running function.
   201  //   - pc is the PC in the running function.
   202  //   - sp is the stack pointer at that program counter.
   203  //   - For the innermost frame on LR machines, lr is the program counter that called fn.
   204  //
   205  // On return, u.frame contains:
   206  //   - fp is the stack pointer of the caller.
   207  //   - lr is the program counter that called fn.
   208  //   - varp, argp, and continpc are populated for the current frame.
   209  //
   210  // If fn is a stack-jumping function, resolveInternal can change the entire
   211  // frame state to follow that stack jump.
   212  //
   213  // This is internal to unwinder.
   214  func (u *unwinder) resolveInternal(innermost bool) {
   215  	frame := &u.frame
   216  	gp := u.g
   217  
   218  	f := frame.fn
   219  	if f.Pcsp == 0 {
   220  		// No frame information, must be external function, like race support.
   221  		// See golang.org/issue/13568.
   222  		u.finishInternal()
   223  		return
   224  	}
   225  
   226  	// Compute function info flags.
   227  	flag := f.Flag
   228  
   229  	// Found an actual function.
   230  	// Derive frame pointer.
   231  	if frame.fp == 0 {
   232  		// Jump over system stack transitions. If we're on g0 and there's a user
   233  		// goroutine, try to jump. Otherwise this is a regular call.
   234  		// We also defensively check that this won't switch M's on us,
   235  		// which could happen at critical points in the scheduler.
   236  		// This ensures gp.m doesn't change from a stack jump.
   237  		if u.flags&unwindJumpStack != 0 && gp == gMG0(u.mem, gp) && gMCurg(u.mem, gp) != 0 && ptr64(gMCurg(u.mem, gp)) == gM(u.mem, gp) {
   238  			switch f.FuncID {
   239  			case goruntime.FuncID_morestack:
   240  				// morestack does not return normally -- newstack()
   241  				// gogo's to curg.sched. Match that.
   242  				// This keeps morestack() from showing up in the backtrace,
   243  				// but that makes some sense since it'll never be returned
   244  				// to.
   245  				gp = gMCurg(u.mem, gp)
   246  				u.g = gp
   247  				frame.pc = gSchedPc(u.mem, gp)
   248  				frame.fn = u.symbols.FindFunc(frame.pc)
   249  				f = frame.fn
   250  				flag = f.Flag
   251  				frame.lr = gSchedLr(u.mem, gp)
   252  				frame.sp = gSchedSp(u.mem, gp)
   253  			case goruntime.FuncID_systemstack:
   254  				// systemstack returns normally, so just follow the
   255  				// stack transition.
   256  				gp = gMCurg(u.mem, gp)
   257  				u.g = gp
   258  				frame.sp = gSchedSp(u.mem, gp)
   259  				flag &^= goruntime.FuncFlagSPWrite
   260  			}
   261  		}
   262  		frame.fp = frame.sp + ptr64(funcspdelta(f, frame.pc))
   263  		frame.fp += goarchPtrSize
   264  	}
   265  
   266  	// Derive link register.
   267  	if flag&goruntime.FuncFlagTopFrame != 0 {
   268  		// This function marks the top of the stack. Stop the traceback.
   269  		frame.lr = 0
   270  	} else if flag&goruntime.FuncFlagSPWrite != 0 {
   271  		// The function we are in does a write to SP that we don't know
   272  		// how to encode in the spdelta table. Examples include context
   273  		// switch routines like runtime.gogo but also any code that switches
   274  		// to the g0 stack to run host C code.
   275  		if u.flags&(unwindPrintErrors|unwindSilentErrors) != 0 {
   276  			// We can't reliably unwind the SP (we might
   277  			// not even be on the stack we think we are),
   278  			// so stop the traceback here.
   279  			frame.lr = 0
   280  		} else {
   281  			// For a GC stack traversal, we should only see
   282  			// an SPWRITE function when it has voluntarily preempted itself on entry
   283  			// during the stack growth check. In that case, the function has
   284  			// not yet had a chance to do any writes to SP and is safe to unwind.
   285  			// isAsyncSafePoint does not allow assembly functions to be async preempted,
   286  			// and preemptPark double-checks that SPWRITE functions are not async preempted.
   287  			// So for GC stack traversal, we can safely ignore SPWRITE for the innermost frame,
   288  			// but farther up the stack we'd better not find any.
   289  			if !innermost {
   290  				panic("traceback: unexpected SPWRITE function")
   291  			}
   292  		}
   293  	} else {
   294  		var lrPtr ptr64
   295  		if frame.lr == 0 {
   296  			lrPtr = frame.fp - goarchPtrSize
   297  			frame.lr = deref[ptr64](u.mem, lrPtr)
   298  		}
   299  	}
   300  
   301  	frame.varp = frame.fp
   302  	// On [wasm], call instruction pushes return PC before entering new function.
   303  	frame.varp -= goarchPtrSize
   304  }
   305  
   306  func (u *unwinder) next() {
   307  	frame := &u.frame
   308  	f := frame.fn
   309  
   310  	// Do not unwind past the bottom of the stack.
   311  	if frame.lr == 0 {
   312  		u.finishInternal()
   313  		return
   314  	}
   315  	flr := u.symbols.FindFunc(frame.lr)
   316  	if !flr.valid() {
   317  		frame.lr = 0
   318  		u.finishInternal()
   319  		return
   320  	}
   321  
   322  	if frame.pc == frame.lr && frame.sp == frame.fp {
   323  		// If the next frame is identical to the current frame, we cannot make progress.
   324  		// print("runtime: traceback stuck. pc=", hex(frame.pc), " sp=", hex(frame.sp), "\n")
   325  		// tracebackHexdump(gp.stack, frame, frame.sp)
   326  		panic("traceback stuck")
   327  	}
   328  
   329  	injectedCall := f.FuncID == goruntime.FuncID_sigpanic || f.FuncID == goruntime.FuncID_asyncPreempt || f.FuncID == goruntime.FuncID_debugCallV2
   330  	if injectedCall {
   331  		u.flags |= unwindTrap
   332  	} else {
   333  		u.flags &^= unwindTrap
   334  	}
   335  
   336  	// Unwind to next frame.
   337  	u.calleeFuncID = f.FuncID
   338  	frame.fn = flr
   339  	frame.pc = frame.lr
   340  	frame.lr = 0
   341  	frame.sp = frame.fp
   342  	frame.fp = 0
   343  
   344  	u.resolveInternal(false)
   345  }
   346  
   347  // finishInternal is an unwinder-internal helper called after the stack has been
   348  // exhausted. It sets the unwinder to an invalid state.
   349  func (u *unwinder) finishInternal() {
   350  	u.frame.pc = 0
   351  }
   352  
   353  func funcspdelta(f funcInfo, targetpc ptr64) int32 {
   354  	x, _ := pcvalue(f, f.Pcsp, targetpc)
   355  	return x
   356  }
   357  
   358  // Returns the PCData value, and the PC where this value starts.
   359  func pcvalue(f funcInfo, off uint32, targetpc ptr64) (int32, ptr64) {
   360  	if off == 0 {
   361  		return -1, 0
   362  	}
   363  
   364  	if !f.valid() {
   365  		panic("no module data")
   366  	}
   367  	p := f.md.pctab[off:]
   368  	pc := f.entry()
   369  	prevpc := pc
   370  	val := int32(-1)
   371  	for {
   372  		var ok bool
   373  		p, ok = step(p, &pc, &val, pc == f.entry())
   374  		if !ok {
   375  			break
   376  		}
   377  		if targetpc < pc {
   378  			// Replace a random entry in the cache. Random
   379  			// replacement prevents a performance cliff if
   380  			// a recursive stack's cycle is slightly
   381  			// larger than the cache.
   382  			// Put the new element at the beginning,
   383  			// since it is the most likely to be newly used.
   384  			// if cache != nil {
   385  			// 	x := pcvalueCacheKey(targetpc)
   386  			// 	e := &cache.entries[x]
   387  			// 	ci := fastrandn(uint32(len(cache.entries[x])))
   388  			// 	e[ci] = e[0]
   389  			// 	e[0] = pcvalueCacheEnt{
   390  			// 		targetpc: targetpc,
   391  			// 		off:      off,
   392  			// 		val:      val,
   393  			// 	}
   394  			// }
   395  
   396  			return val, prevpc
   397  		}
   398  		prevpc = pc
   399  	}
   400  
   401  	panic("invalid pc-encoded table")
   402  }
   403  
   404  // step advances to the next pc, value pair in the encoded table.
   405  func step(p []byte, pc *ptr64, val *int32, first bool) (newp []byte, ok bool) {
   406  	// For both uvdelta and pcdelta, the common case (~70%)
   407  	// is that they are a single byte. If so, avoid calling readvarint.
   408  	uvdelta := uint32(p[0])
   409  	if uvdelta == 0 && !first {
   410  		return nil, false
   411  	}
   412  	n := uint32(1)
   413  	if uvdelta&0x80 != 0 {
   414  		n, uvdelta = readvarint(p)
   415  	}
   416  	*val += int32(-(uvdelta & 1) ^ (uvdelta >> 1))
   417  	p = p[n:]
   418  
   419  	pcdelta := uint32(p[0])
   420  	n = 1
   421  	if pcdelta&0x80 != 0 {
   422  		n, pcdelta = readvarint(p)
   423  	}
   424  	p = p[n:]
   425  	*pc += ptr64(pcdelta * sysPCQuantum)
   426  	return p, true
   427  }
   428  
   429  // readvarint reads a varint from p.
   430  func readvarint(p []byte) (read uint32, val uint32) {
   431  	var v, shift, n uint32
   432  	for {
   433  		b := p[n]
   434  		n++
   435  		v |= uint32(b&0x7F) << (shift & 31)
   436  		if b&0x80 == 0 {
   437  			break
   438  		}
   439  		shift += 7
   440  	}
   441  	return n, v
   442  }
   443  
   444  // inlinedCall is the encoding of entries in the FUNCDATA_InlTree table.
   445  type inlinedCall struct {
   446  	funcID    goruntime.FuncID // type of the called function
   447  	_         [3]byte
   448  	nameOff   int32 // offset into pclntab for name of called function
   449  	parentPc  int32 // position of an instruction whose source position is the call site (offset from entry)
   450  	startLine int32 // line number of start of function (func keyword/TEXT directive)
   451  }
   452  
   453  type inlineUnwinder struct {
   454  	symbols *pclntab
   455  	mem     vmem
   456  	f       funcInfo
   457  	inlTree ptr64 // Address of the array of inlinedCall entries
   458  }
   459  
   460  // next returns the frame representing uf's logical caller.
   461  func (u *inlineUnwinder) next(uf inlineFrame) inlineFrame {
   462  	if uf.index < 0 {
   463  		uf.pc = 0
   464  		return uf
   465  	}
   466  	c := derefArrayIndex[inlinedCall](u.mem, u.inlTree, uf.index)
   467  	return u.resolveInternal(u.f.entry() + ptr64(c.parentPc))
   468  }
   469  
   470  // srcFunc returns the srcFunc representing the given frame.
   471  func (u *inlineUnwinder) srcFunc(uf inlineFrame) srcFunc {
   472  	if uf.index < 0 {
   473  		return u.f.srcFunc()
   474  	}
   475  	t := derefArrayIndex[inlinedCall](u.mem, u.inlTree, uf.index)
   476  	return srcFunc{
   477  		datap:     u.f.md,
   478  		nameOff:   t.nameOff,
   479  		startLine: t.startLine,
   480  		funcID:    t.funcID,
   481  	}
   482  }
   483  
   484  func (u *inlineUnwinder) resolveInternal(pc ptr64) inlineFrame {
   485  	return inlineFrame{
   486  		pc: pc,
   487  		// Conveniently, this returns -1 if there's an error, which is the same
   488  		// value we use for the outermost frame.
   489  		index: pcdatavalue1(u.f, goruntime.PCDATA_InlTreeIndex, pc),
   490  	}
   491  }
   492  
   493  // An inlineFrame is a position in an inlineUnwinder.
   494  type inlineFrame struct {
   495  	// pc is the PC giving the file/line metadata of the current frame. This is
   496  	// always a "call PC" (not a "return PC"). This is 0 when the iterator is
   497  	// exhausted.
   498  	pc ptr64
   499  
   500  	// index is the index of the current record in inlTree, or -1 if we are in
   501  	// the outermost function.
   502  	index int32
   503  }
   504  
   505  func (uf inlineFrame) valid() bool {
   506  	return uf.pc != 0
   507  }
   508  
   509  func newInlineUnwinder(symbols *pclntab, mem vmem, f funcInfo, pc ptr64) (inlineUnwinder, inlineFrame) {
   510  	inldataAddr := funcdata(symbols, f, goruntime.FUNCDATA_InlTree)
   511  	if inldataAddr == 0 {
   512  		return inlineUnwinder{symbols: symbols, mem: mem, f: f}, inlineFrame{pc: pc, index: -1}
   513  	}
   514  	u := inlineUnwinder{symbols: symbols, mem: mem, f: f, inlTree: inldataAddr}
   515  	return u, u.resolveInternal(pc)
   516  }
   517  
   518  // funcdata returns a pointer to the ith funcdata for f.
   519  // funcdata should be kept in sync with cmd/link:writeFuncs.
   520  func funcdata(symbols *pclntab, f funcInfo, i uint8) ptr64 {
   521  	if i >= f.Nfuncdata {
   522  		return 0
   523  	}
   524  	base := symbols.md.gofunc
   525  	off := funcdataoffset(f, i)
   526  
   527  	// Return off == ^uint32(0) ? 0 : f.datap.gofunc + uintptr(off), but without branches.
   528  	// The compiler calculates mask on most architectures using conditional assignment.
   529  	var mask ptr64
   530  	if off == ^uint32(0) {
   531  		mask = 1
   532  	}
   533  	mask--
   534  	raw := base + ptr64(off)
   535  	return raw & mask
   536  }