package wasmdebugimport ()// DWARFLines is used to retrieve source code line information from the DWARF data.typeDWARFLinesstruct {// d is created by DWARF custom sections. d *dwarf.Data// linesPerEntry maps dwarf.Offset for dwarf.Entry to the list of lines contained by the entry. // The value is sorted in the increasing order by the address. linesPerEntry map[dwarf.Offset][]line mux sync.Mutex}type line struct { addr uint64 pos dwarf.LineReaderPos}// NewDWARFLines returns DWARFLines for the given *dwarf.Data.func ( *dwarf.Data) *DWARFLines {if == nil {returnnil }return &DWARFLines{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/D81784func isTombstoneAddr( uint64) bool { := int32()return == -1 || == -2 || == 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 ( *DWARFLines) ( uint64) ( []string) {if == nil {return }// DWARFLines is created per Wasm binary, so there's a possibility that multiple instances // created from a same binary face runtime error at the same time, and that results in // concurrent access to this function. .mux.Lock()defer .mux.Unlock() := .d.Reader()var []*dwarf.Entryvar *dwarf.Entryvarbool:for { , := .Next()if != nil || == nil {break }// If we already found the compilation unit and relevant inlined routines, we can stop searching entries.if != nil && {break }switch .Tag {casedwarf.TagCompileUnit, dwarf.TagInlinedSubroutine:default:// Only CompileUnit and InlinedSubroutines are relevant.continue }// Check if the entry spans the range which contains the target instruction. , := .d.Ranges()if != nil {continue }for , := range { , := [0], [1]ifisTombstoneAddr() || isTombstoneAddr() {continue }if <= && < {switch .Tag {casedwarf.TagCompileUnit: = casedwarf.TagInlinedSubroutine: = append(, )// Search inlined subroutines until all the children. = !.Children// Not that "children" in the DWARF spec is defined as the next entry to this entry. // See "2.3 Relationship of Debugging Information Entries" in https://dwarfstd.org/doc/DWARF4.pdf }continue } } }// If the relevant compilation unit is not found, nothing we can do with this DWARF info.if == nil {return } , := .d.LineReader()if != nil || == nil {return }var []linevarboolvardwarf.LineEntry// Get the lines inside the entry.if , = .linesPerEntry[.Offset]; ! {// If not found, we create the list of lines by reading all the LineEntries in the Entry. // // Note that the dwarf.LineEntry.SeekPC API shouldn't be used because the Go's dwarf package assumes that // all the line entries in an Entry are sorted in increasing order which *might not* be true // for some languages. Such order requirement is not a part of DWARF specification, // and in fact Zig language tends to emit interleaved line information. // // Thus, here we read all line entries here, and sort them in the increasing order wrt addresses.for { := .Tell() = .Next(&)iferrors.Is(, io.EOF) {break } elseif != nil {return }// TODO: Maybe we should ignore tombstone addresses by using isTombstoneAddr, // but not sure if that would be an issue in practice. = append(, line{addr: .Address, pos: }) }sort.Slice(, func(, int) bool { return [].addr < [].addr }) .linesPerEntry[.Offset] = // Caches for the future inquiries for the same Entry. }// Now we have the lines for this entry. We can find the corresponding source line for instructionOffset // via binary search on the list. := len() := sort.Search(, func( int) bool { return [].addr >= })if == { // This case the address is not found. See the doc sort.Search.return } := []if .addr != {// If the address doesn't match exactly, the previous entry is the one that contains the instruction. // That can happen anytime as the DWARF spec allows it, and other tools can handle it in this way conventionally // https://github.com/gimli-rs/addr2line/blob/3a2dbaf84551a06a429f26e9c96071bb409b371f/src/lib.rs#L236-L242 // https://github.com/kateinoigakukun/wasminspect/blob/f29f052f1b03104da9f702508ac0c1bbc3530ae4/crates/debugger/src/dwarf/mod.rs#L453-L459if -1 < 0 {return } = [-1] }// Advance the line reader for the found position. .Seek(.pos) = .Next(&)if != nil {// If we reach this block, that means there's a bug in the []line creation logic above.panic("BUG: stored dwarf.LineReaderPos is invalid") }// In the inlined case, the line info is the innermost inlined function call. := len() != 0 := fmt.Sprintf("%#x: ", ) = append(, formatLine(, .File.Name, int64(.Line), int64(.Column), ))if { = strings.Repeat(" ", len()) := .Files()// inlinedRoutines contain the inlined call information in the reverse order (children is higher than parent), // so we traverse the reverse order and emit the inlined calls.for := len() - 1; >= 0; -- { := [] , := .Val(dwarf.AttrCallFile).(int64)if ! {return } elseif >= int64(len()) {// This in theory shouldn't happen according to the spec, but guard against ill-formed DWARF info.return } := [] , := .Val(dwarf.AttrCallLine).(int64) , := .Val(dwarf.AttrCallColumn).(int64) = append(, formatLine(, .Name, , ,// Last one is the origin of the inlined function calls. != 0)) } }return}func formatLine(, string, , int64, bool) string { := strings.Builder{} .WriteString() .WriteString()if != 0 { .WriteString(fmt.Sprintf(":%d", ))if != 0 { .WriteString(fmt.Sprintf(":%d", )) } }if { .WriteString(" (inlined)") }return .String()}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.