dwarf: ignores tombstone entries in inlined information (#1488)

Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
Takeshi Yoneda
2023-05-22 15:56:33 +10:00
committed by GitHub
parent 714368bcea
commit 256b7a4bf9
6 changed files with 145 additions and 1 deletions

View File

@@ -45,6 +45,35 @@ func TestWithDebugInfo(t *testing.T) {
bin []byte bin []byte
exp string 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", name: "zig",
bin: dwarftestdata.ZigWasm, bin: dwarftestdata.ZigWasm,

View File

@@ -13,6 +13,9 @@ import (
// line number, source code file, etc, can change. Therefore, // line number, source code file, etc, can change. Therefore,
// even though these binaries are huge, we check them in to the repositories. // 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 //go:embed testdata/zig/main.wasm
var ZigWasm []byte var ZigWasm []byte

View File

@@ -0,0 +1,17 @@
package main
func main() {
a()
}
func a() {
b()
}
func b() {
c()
}
func c() {
panic("NOOOOOOOOOOOOOOO")
}

Binary file not shown.

View File

@@ -33,6 +33,19 @@ func NewDWARFLines(d *dwarf.Data) *DWARFLines {
return &DWARFLines{d: d, linesPerEntry: map[dwarf.Offset][]line{}} 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 // 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. // 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) { func (d *DWARFLines) Line(instructionOffset uint64) (ret []string) {
@@ -76,7 +89,11 @@ entry:
continue continue
} }
for _, pcs := range ranges { 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 { switch ent.Tag {
case dwarf.TagCompileUnit: case dwarf.TagCompileUnit:
cu = ent cu = ent
@@ -122,6 +139,8 @@ entry:
} else if err != nil { } else if err != nil {
return 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}) lines = append(lines, line{addr: le.Address, pos: pos})
} }
sort.Slice(lines, func(i, j int) bool { return lines[i].addr < lines[j].addr }) sort.Slice(lines, func(i, j int) bool { return lines[i].addr < lines[j].addr })

View File

@@ -2,6 +2,7 @@ package wasmdebug_test
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/tetratelabs/wazero/api" "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])
}
})
}
}