dwarf: ignores tombstone entries in inlined information (#1488)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
@@ -45,6 +45,35 @@ func TestWithDebugInfo(t *testing.T) {
|
||||
bin []byte
|
||||
exp string
|
||||
}{
|
||||
{
|
||||
name: "tinygo",
|
||||
bin: dwarftestdata.TinyGoWasm,
|
||||
exp: `module[] function[_start] failed: wasm error: unreachable
|
||||
wasm stack trace:
|
||||
.runtime._panic(i32)
|
||||
0x18f3: /runtime_tinygowasm.go:70:6
|
||||
.main.c()
|
||||
0x2ff9: /main.go:16:7
|
||||
.main.b()
|
||||
0x2f97: /main.go:12:3
|
||||
.main.a()
|
||||
0x2f39: /main.go:8:3
|
||||
.main.main()
|
||||
0x2149: /main.go:4:3
|
||||
.runtime.run$1()
|
||||
0x1fcb: /scheduler_any.go:25:11
|
||||
.runtime.run$1$gowrapper(i32)
|
||||
0x6f0: /scheduler_any.go:23:2
|
||||
.tinygo_launch(i32)
|
||||
0x23: /task_asyncify_wasm.S:59
|
||||
.runtime.scheduler()
|
||||
0x1ec4: /task_asyncify.go:109:17 (inlined)
|
||||
/scheduler.go:236:11
|
||||
.runtime.run()
|
||||
0x1d92: /scheduler_any.go:28:11
|
||||
._start()
|
||||
0x1d12: /runtime_wasm_wasi.go:21:5`,
|
||||
},
|
||||
{
|
||||
name: "zig",
|
||||
bin: dwarftestdata.ZigWasm,
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
// line number, source code file, etc, can change. Therefore,
|
||||
// even though these binaries are huge, we check them in to the repositories.
|
||||
|
||||
//go:embed testdata/tinygo/main.wasm
|
||||
var TinyGoWasm []byte
|
||||
|
||||
//go:embed testdata/zig/main.wasm
|
||||
var ZigWasm []byte
|
||||
|
||||
|
||||
17
internal/testing/dwarftestdata/testdata/tinygo/main.go
vendored
Normal file
17
internal/testing/dwarftestdata/testdata/tinygo/main.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
a()
|
||||
}
|
||||
|
||||
func a() {
|
||||
b()
|
||||
}
|
||||
|
||||
func b() {
|
||||
c()
|
||||
}
|
||||
|
||||
func c() {
|
||||
panic("NOOOOOOOOOOOOOOO")
|
||||
}
|
||||
BIN
internal/testing/dwarftestdata/testdata/tinygo/main.wasm
vendored
Executable file
BIN
internal/testing/dwarftestdata/testdata/tinygo/main.wasm
vendored
Executable file
Binary file not shown.
@@ -33,6 +33,19 @@ func NewDWARFLines(d *dwarf.Data) *DWARFLines {
|
||||
return &DWARFLines{d: d, linesPerEntry: map[dwarf.Offset][]line{}}
|
||||
}
|
||||
|
||||
// isTombstoneAddr returns true if the given address is invalid a.k.a tombstone address which was made no longer valid
|
||||
// by linker. According to the DWARF spec[1], the value is encoded as 0xffffffff for Wasm (as 32-bit target),
|
||||
// but some tools encode it either in -1, -2 [2] or 1<<32 (This might not be by tools, but by debug/dwarf package's bug).
|
||||
//
|
||||
// [1] https://dwarfstd.org/issues/200609.1.html
|
||||
// [2] https://github.com/WebAssembly/binaryen/blob/97178d08d4a20d2a5e3a6be813fc6a7079ef86e1/src/wasm/wasm-debug.cpp#L651-L660
|
||||
// [3] https://reviews.llvm.org/D81784
|
||||
func isTombstoneAddr(addr uint64) bool {
|
||||
addr32 := int32(addr)
|
||||
return addr32 == -1 || addr32 == -2 ||
|
||||
addr32 == 0 // This covers 1 <<32.
|
||||
}
|
||||
|
||||
// Line returns the line information for the given instructionOffset which is an offset in
|
||||
// the code section of the original Wasm binary. Returns empty string if the info is not found.
|
||||
func (d *DWARFLines) Line(instructionOffset uint64) (ret []string) {
|
||||
@@ -76,7 +89,11 @@ entry:
|
||||
continue
|
||||
}
|
||||
for _, pcs := range ranges {
|
||||
if pcs[0] <= instructionOffset && instructionOffset < pcs[1] {
|
||||
start, end := pcs[0], pcs[1]
|
||||
if isTombstoneAddr(start) || isTombstoneAddr(end) {
|
||||
continue
|
||||
}
|
||||
if start <= instructionOffset && instructionOffset < end {
|
||||
switch ent.Tag {
|
||||
case dwarf.TagCompileUnit:
|
||||
cu = ent
|
||||
@@ -122,6 +139,8 @@ entry:
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
// TODO: Maybe we should ignore tombstone addresses by using isTombstoneAddr,
|
||||
// but not sure if that would be an issue in practice.
|
||||
lines = append(lines, line{addr: le.Address, pos: pos})
|
||||
}
|
||||
sort.Slice(lines, func(i, j int) bool { return lines[i].addr < lines[j].addr })
|
||||
|
||||
@@ -2,6 +2,7 @@ package wasmdebug_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
@@ -195,3 +196,78 @@ func TestDWARFLines_Line_Rust(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDWARFLines_Line_TinyGo(t *testing.T) {
|
||||
mod, err := binary.DecodeModule(dwarftestdata.TinyGoWasm, api.CoreFeaturesV2, wasm.MemoryLimitPages, false, true, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, mod.DWARFLines)
|
||||
|
||||
// codeSecStart is the beginning of the code section in the Wasm binary.
|
||||
// If dwarftestdata.TinyGoWasm has been changed, we need to inspect by `wasm-tools objdump`.
|
||||
const codeSecStart = 0x16f
|
||||
|
||||
// These cases are crafted by matching the stack trace result from wasmtime. To verify, run:
|
||||
//
|
||||
// WASMTIME_BACKTRACE_DETAILS=1 wasmtime run internal/testing/dwarftestdata/testdata/tinygo/main.wasm
|
||||
//
|
||||
// And this should produce the output as:
|
||||
//
|
||||
// Caused by:
|
||||
// 0: failed to invoke command default
|
||||
// 1: error while executing at wasm backtrace:
|
||||
// 0: 0x1a62 - runtime.abort
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/runtime_tinygowasm.go:70:6 - runtime._panic
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/panic.go:52:7
|
||||
// 1: 0x3168 - main.c
|
||||
// at /Users/mathetake/Downloads/tinygo/tmo/main.go:16:7
|
||||
// 2: 0x3106 - main.b
|
||||
// at /Users/mathetake/Downloads/tinygo/tmo/main.go:12:3
|
||||
// 3: 0x30a8 - main.a
|
||||
// at /Users/mathetake/Downloads/tinygo/tmo/main.go:8:3
|
||||
// 4: 0x22b8 - main.main
|
||||
// at /Users/mathetake/Downloads/tinygo/tmo/main.go:4:3
|
||||
// 5: 0x213a - runtime.run$1
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/scheduler_any.go:25:11
|
||||
// 6: 0x85f - <goroutine wrapper>
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/scheduler_any.go:23:2
|
||||
// 7: 0x192 - tinygo_launch
|
||||
// at /Users/mathetake/Downloads/tinygo/src/internal/task/task_asyncify_wasm.S:59
|
||||
// 8: 0x2033 - (*internal/task.Task).Resume
|
||||
// at /Users/mathetake/Downloads/tinygo/src/internal/task/task_asyncify.go:109:17 - runtime.scheduler
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/scheduler.go:236:11
|
||||
// 9: 0x1f01 - runtime.run
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/scheduler_any.go:28:11
|
||||
// 10: 0x1e81 - _start
|
||||
// at /Users/mathetake/Downloads/tinygo/src/runtime/runtime_wasm_wasi.go:21:5
|
||||
for _, tc := range []struct {
|
||||
offset uint64
|
||||
exp []string
|
||||
}{
|
||||
{offset: 0x1e81 - codeSecStart, exp: []string{"runtime/runtime_wasm_wasi.go:21:5"}},
|
||||
{offset: 0x1f01 - codeSecStart, exp: []string{"runtime/scheduler_any.go:28:11"}},
|
||||
{offset: 0x2033 - codeSecStart, exp: []string{
|
||||
"internal/task/task_asyncify.go:109:17",
|
||||
"runtime/scheduler.go:236:11",
|
||||
}},
|
||||
{offset: 0x192 - codeSecStart, exp: []string{"internal/task/task_asyncify_wasm.S:59"}},
|
||||
{offset: 0x85f - codeSecStart, exp: []string{"runtime/scheduler_any.go:23:2"}},
|
||||
{offset: 0x213a - codeSecStart, exp: []string{"runtime/scheduler_any.go:25:11"}},
|
||||
{offset: 0x22b8 - codeSecStart, exp: []string{"main.go:4:3"}},
|
||||
{offset: 0x30a8 - codeSecStart, exp: []string{"main.go:8:3"}},
|
||||
{offset: 0x3106 - codeSecStart, exp: []string{"main.go:12:3"}},
|
||||
{offset: 0x3168 - codeSecStart, exp: []string{"main.go:16:7"}},
|
||||
// Note(important): this case is different from the output of Wasmtime, which produces the incorrect inline info (panic.go:52:7).
|
||||
// Actually, "runtime_tinygowasm.go:70:6" invokes trap() which is translated as "unreachable" instruction by LLVM, so there won't be
|
||||
// any inlined function invocation here.
|
||||
{offset: 0x1a62 - codeSecStart, exp: []string{"runtime/runtime_tinygowasm.go:70:6"}},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("%#x/%s", tc.offset, tc.exp), func(t *testing.T) {
|
||||
actual := mod.DWARFLines.Line(tc.offset)
|
||||
require.Equal(t, len(tc.exp), len(actual), "\nexp: %s\ngot: %s", strings.Join(tc.exp, "\n"), strings.Join(actual, "\n"))
|
||||
for i := range tc.exp {
|
||||
require.Contains(t, actual[i], tc.exp[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user