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 }