pkg/proc: add support for additional stack-switching functions on loong64 (#4100)

Extend loong64SwitchStack to handle:
- cgo-related functions (Fixes #4099)
- runtime.mstart, runtime.newstack, runtime.systemstack

Co-authored-by: Huang Qiqi <huangqiqi@loongson.cn>
This commit is contained in:
yelvens
2025-08-22 00:42:44 +08:00
committed by GitHub
parent 44123aeea6
commit fbcae21681
3 changed files with 114 additions and 20 deletions

View File

@ -23,8 +23,7 @@ Tests skipped by each supported backend:
* 2 not working on linux/386
* linux/386/pie skipped = 1
* 1 broken
* linux/loong64 skipped = 2
* 1 broken - cgo stacktraces
* linux/loong64 skipped = 1
* 1 not working on linux/loong64
* linux/ppc64le skipped = 3
* 1 broken - cgo stacktraces
@ -36,8 +35,8 @@ Tests skipped by each supported backend:
* linux/riscv64 skipped = 2
* 1 broken - cgo stacktraces
* 1 not working on linux/riscv64
* loong64 skipped = 9
* 2 broken
* loong64 skipped = 8
* 1 broken
* 1 broken - global variable symbolication
* 6 not implemented
* pie skipped = 2

View File

@ -89,6 +89,8 @@ func loong64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Bina
return fctxt
}
const loong64cgocallSPOffsetSaveSlot = 0x8
func loong64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool {
if it.frame.Current.Fn == nil {
if it.systemstack && it.g != nil && it.top {
@ -101,26 +103,121 @@ func loong64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) boo
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
case "runtime.goexit", "runtime.rt0_go":
// Look for "top of stack" functions.
it.atend = true
return true
default:
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.throw" && it.frame.Current.Fn.Name != "runtime.fatalthrow" {
// The runtime switches to the system stack in multiple places.
// This usually happens through a call to runtime.systemstack but there
// are functions that switch to the system stack manually (for example
// runtime.morestack).
// Since we are only interested in printing the system stack for cgo
// calls we switch directly to the goroutine stack if we detect that the
// function at the top of the stack is a runtime function.
if err := it.switchToGoroutineStack(); err != nil {
it.err = err
return false
}
case "runtime.mcall":
if it.systemstack && it.g != nil {
it.switchToGoroutineStack()
return true
}
it.atend = true
return true
case "runtime.asmcgocall":
if it.top || !it.systemstack {
return false
}
// This function is called by a goroutine to execute a C function and
// switches from the goroutine stack to the system stack.
// Since we are unwinding the stack from callee to caller we have to switch
// from the system stack to the goroutine stack.
oldsp := it.regs.SP()
off, _ := readIntRaw(it.mem, oldsp+loong64cgocallSPOffsetSaveSlot, int64(it.bi.Arch.PtrSize()))
newsp := uint64(int64(it.stackhi) - off)
// The runtime.asmcgocall prologue contains: addi.d $sp, $sp, -8,
// hence we require newsp + 8 at this point
it.regs.Reg(it.regs.SPRegNum).Uint64Val = newsp + 8
// runtime.asmcgocall can also be called from inside the system stack,
// in that case no stack switch actually happens
if it.regs.SP() == oldsp {
return false
}
it.top = false
it.systemstack = false
// The return value is stored in the LR register which is saved at -8(SP).
addrret := uint64(int64(it.regs.SP()) - int64(it.bi.Arch.PtrSize()))
it.frame.Ret, _ = readUintRaw(it.mem, addrret, int64(it.bi.Arch.PtrSize()))
it.pc = it.frame.Ret
return true
case "runtime.cgocallback_gofunc", "runtime.cgocallback":
// For a detailed description of how this works read the long comment at
// the start of $GOROOT/src/runtime/cgocall.go and the source code of
// runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_loong64.s
//
// When a C functions calls back into go it will eventually call into
// runtime.cgocallback_gofunc which is the function that does the stack
// switch from the system stack back into the goroutine stack
// Since we are going backwards on the stack here we see the transition
// as goroutine stack -> system stack.
if it.top || it.systemstack {
return false
}
it.loadG0SchedSP()
if it.g0_sched_sp <= 0 {
return false
}
// entering the system stack
it.regs.Reg(it.regs.SPRegNum).Uint64Val = it.g0_sched_sp
// reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack
it.g0_sched_sp, _ = readUintRaw(it.mem, it.regs.SP()+loong64cgocallSPOffsetSaveSlot, int64(it.bi.Arch.PtrSize()))
it.top = false
callFrameRegs, ret, retaddr := it.advanceRegs()
frameOnSystemStack := it.newStackframe(ret, retaddr)
it.pc = frameOnSystemStack.Ret
it.regs = callFrameRegs
it.systemstack = true
return true
case "crosscall2":
// The offsets get from runtime/cgo/asm_loong64.s:25
newsp, _ := readUintRaw(it.mem, it.regs.SP()+8*23, int64(it.bi.Arch.PtrSize()))
newbp, _ := readUintRaw(it.mem, it.regs.SP()+8*4, int64(it.bi.Arch.PtrSize()))
newlr, _ := readUintRaw(it.mem, it.regs.SP()+8*22, int64(it.bi.Arch.PtrSize()))
if it.regs.Reg(it.regs.BPRegNum) != nil {
it.regs.Reg(it.regs.BPRegNum).Uint64Val = newbp
} else {
reg, _ := it.readRegisterAt(it.regs.BPRegNum, it.regs.SP()+8*4)
it.regs.AddReg(it.regs.BPRegNum, reg)
}
it.regs.Reg(it.regs.LRRegNum).Uint64Val = newlr
it.regs.Reg(it.regs.SPRegNum).Uint64Val = newsp
it.pc = newlr
return true
case "runtime.mstart":
// Calls to runtime.systemstack will switch to the systemstack then:
// 1. alter the goroutine stack so that it looks like systemstack_switch
// was called
// 2. alter the system stack so that it looks like the bottom-most frame
// belongs to runtime.mstart
// If we find a runtime.mstart frame on the system stack of a goroutine
// parked on runtime.systemstack_switch we assume runtime.systemstack was
// called and continue tracing from the parked position.
if it.top || !it.systemstack || it.g == nil {
return false
}
if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
return false
}
it.switchToGoroutineStack()
return true
case "runtime.newstack", "runtime.systemstack":
if it.systemstack && it.g != nil {
it.switchToGoroutineStack()
return true
}
return false
}
return false

View File

@ -2961,7 +2961,6 @@ func TestCgoStacktrace(t *testing.T) {
skipOn(t, "broken - cgo stacktraces", "windows", "arm64")
skipOn(t, "broken - cgo stacktraces", "linux", "ppc64le")
skipOn(t, "broken - cgo stacktraces", "linux", "riscv64")
skipOn(t, "broken - cgo stacktraces", "linux", "loong64")
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
skipOn(t, "broken - cgo stacktraces", "windows", "arm64")
}
@ -4200,7 +4199,6 @@ func TestCgoStacktrace2(t *testing.T) {
skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
skipOn(t, "broken", "ppc64le")
skipOn(t, "broken", "riscv64")
skipOn(t, "broken", "loong64")
protest.MustHaveCgo(t)
// If a panic happens during cgo execution the stacktrace should show the C
// function that caused the problem.