From 8116d34a608a439b75975ef811f383e41b74928d Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sat, 15 Feb 2025 00:17:01 +0800 Subject: [PATCH] runtime: runtime.Callers, runtime.CallersFrames --- _demo/runtest/main_test.go | 4 ++ runtime/internal/lib/runtime/extern.go | 18 ++++++- runtime/internal/lib/runtime/symtab.go | 66 +++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/_demo/runtest/main_test.go b/_demo/runtest/main_test.go index 3484e083..220a4a8e 100644 --- a/_demo/runtest/main_test.go +++ b/_demo/runtest/main_test.go @@ -7,3 +7,7 @@ func TestZoo(t *testing.T) { t.Fatal("Zoo() != 3") } } + +func TestFalse(t *testing.T) { + // t.Fatal("false") +} diff --git a/runtime/internal/lib/runtime/extern.go b/runtime/internal/lib/runtime/extern.go index 8f839a48..2f64a833 100644 --- a/runtime/internal/lib/runtime/extern.go +++ b/runtime/internal/lib/runtime/extern.go @@ -4,10 +4,26 @@ package runtime +import ( + "github.com/goplus/llgo/runtime/internal/clite/debug" +) + func Caller(skip int) (pc uintptr, file string, line int, ok bool) { panic("todo: runtime.Caller") } func Callers(skip int, pc []uintptr) int { - panic("todo: runtime.Callers") + if len(pc) == 0 { + return 0 + } + n := 0 + debug.StackTrace(skip, func(fr *debug.Frame) bool { + if n >= len(pc) { + return false + } + pc[n] = fr.PC + n++ + return true + }) + return n } diff --git a/runtime/internal/lib/runtime/symtab.go b/runtime/internal/lib/runtime/symtab.go index 5ace3344..04d236eb 100644 --- a/runtime/internal/lib/runtime/symtab.go +++ b/runtime/internal/lib/runtime/symtab.go @@ -4,12 +4,21 @@ package runtime +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + "github.com/goplus/llgo/runtime/internal/clite/debug" +) + // Frames may be used to get function/file/line information for a // slice of PC values returned by Callers. type Frames struct { // callers is a slice of PCs that have not yet been expanded to frames. callers []uintptr + nextPC uintptr + // frames is a slice of Frames that have yet to be returned. frames []Frame frameStore [2]Frame @@ -62,15 +71,68 @@ type Frame struct { funcInfo funcInfo } +func safeGoString(s *c.Char, defaultStr string) string { + if s == nil { + return defaultStr + } + return c.GoString(s) +} + func (ci *Frames) Next() (frame Frame, more bool) { - panic("todo: runtime.Frames.Next") + for len(ci.frames) < 2 { + // Find the next frame. + // We need to look for 2 frames so we know what + // to return for the "more" result. + if len(ci.callers) == 0 { + break + } + var pc uintptr + if ci.nextPC != 0 { + pc, ci.nextPC = ci.nextPC, 0 + } else { + pc, ci.callers = ci.callers[0], ci.callers[1:] + } + info := &debug.Info{} + if debug.Addrinfo(unsafe.Pointer(pc), info) == 0 { + break + } + ci.frames = append(ci.frames, Frame{ + PC: pc, + Function: safeGoString(info.Fname, ""), + File: safeGoString(info.Sname, ""), + Line: 0, + startLine: 0, + Entry: uintptr(info.Saddr), + }) + } + + // Pop one frame from the frame list. Keep the rest. + // Avoid allocation in the common case, which is 1 or 2 frames. + switch len(ci.frames) { + case 0: // In the rare case when there are no frames at all, we return Frame{}. + return + case 1: + frame = ci.frames[0] + ci.frames = ci.frameStore[:0] + case 2: + frame = ci.frames[0] + ci.frameStore[0] = ci.frames[1] + ci.frames = ci.frameStore[:1] + default: + frame = ci.frames[0] + ci.frames = ci.frames[1:] + } + more = len(ci.frames) > 0 + return } // CallersFrames takes a slice of PC values returned by Callers and // prepares to return function/file/line information. // Do not change the slice until you are done with the Frames. func CallersFrames(callers []uintptr) *Frames { - panic("todo: runtime.CallersFrames") + f := &Frames{callers: callers} + f.frames = f.frameStore[:0] + return f } // A Func represents a Go function in the running binary.