From e6e7aeb667057ad6a98120ebf3591fbafdedb19d Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 3 Mar 2025 17:42:44 +0100 Subject: [PATCH] dwarf,proc: various fixes to support DWARFv5 (#3893) Miscellaneous fixes to our DWARFv5 implementation, several contributed by @thanm. --- pkg/dwarf/line/line_parser.go | 6 +++++- pkg/dwarf/line/line_parser_test.go | 3 ++- pkg/dwarf/line/state_machine.go | 20 ++++++++++++++++++-- pkg/dwarf/parseutil.go | 2 +- pkg/proc/bininfo.go | 2 +- pkg/proc/proc_general_test.go | 2 +- pkg/proc/proc_test.go | 11 ++++++----- 7 files changed, 34 insertions(+), 12 deletions(-) diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index 259bdc1e..16bdb8dd 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -116,7 +116,11 @@ func Parse(compdir string, buf *bytes.Buffer, debugLineStr []byte, logfn func(st // - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length. // - dbl.Prologue.Length is the length of the prologue not including unit length, version or prologue length itself. // - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)). - dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6)) + verDelta := uint32(6) + if dbl.Prologue.Version >= 5 { + verDelta = uint32(8) + } + dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - verDelta)) return dbl } diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index d5a8a782..6f5bfd7b 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -63,6 +63,7 @@ const ( lineRangeGo18 uint8 = 10 versionGo14 uint16 = 2 versionGo111 uint16 = 3 + versionGo125 uint16 = 5 opcodeBaseGo14 uint8 = 10 opcodeBaseGo111 uint8 = 11 ) @@ -79,7 +80,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) { for _, dbl := range debugLines { prologue := dbl.Prologue - if prologue.Version != versionGo14 && prologue.Version != versionGo111 { + if prologue.Version != versionGo14 && prologue.Version != versionGo111 && prologue.Version != versionGo125 { t.Fatal("Version not parsed correctly", prologue.Version) } diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index 22149ff9..fadf1578 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -106,7 +106,7 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *Stat } var file string if len(dbl.FileNames) > 0 { - file = dbl.FileNames[0].Path + file = dbl.defaultFile() } dbl.endSeqIsValid = true sm := &StateMachine{ @@ -123,6 +123,22 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *Stat return sm } +func (dbl *DebugLineInfo) defaultFile() string { + // The default file is always file 1, however DWARFv5 starts numbering the + // entries of the files table from 0 but prior versions started with 1 + // which means that for DWARFv4 and earlier the default file is the first + // entry of the table where in DWARFv5 the default is the second entry. + // See DWARFv4 and DWARFv5 section 6.2.4 at the end. + if dbl.Prologue.Version < 5 { + return dbl.FileNames[0].Path + } + if len(dbl.FileNames) == 1 { + // DWARFv5 doesn't say what should happen in this case. + return dbl.FileNames[0].Path + } + return dbl.FileNames[1].Path +} + // AllPCsForFileLines Adds all PCs for a given file and set (domain of map) of lines // to the map value corresponding to each line. func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64) { @@ -390,7 +406,7 @@ func (sm *StateMachine) next() error { } if sm.endSeq { sm.endSeq = false - sm.file = sm.dbl.FileNames[0].Path + sm.file = sm.dbl.defaultFile() sm.line = 1 sm.column = 0 sm.isa = 0 diff --git a/pkg/dwarf/parseutil.go b/pkg/dwarf/parseutil.go index d8ed2b0e..a25f47f9 100644 --- a/pkg/dwarf/parseutil.go +++ b/pkg/dwarf/parseutil.go @@ -131,7 +131,7 @@ func ReadUnitVersions(data []byte) map[dwarf.Offset]uint8 { switch unitType { case _DW_UT_compile, _DW_UT_partial: - headerSize = 5 + secoffsz + headerSize = 4 + secoffsz case _DW_UT_skeleton, _DW_UT_split_compile: headerSize = 4 + secoffsz + 8 diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index da709006..085ff114 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -42,7 +42,7 @@ import ( const ( dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231) - dwarfAttrAddrBase = 0x74 // debug/dwarf.AttrAddrBase in Go 1.14, defined here for compatibility with Go < 1.14 + dwarfAttrAddrBase = 0x73 // debug/dwarf.AttrAddrBase in Go 1.14, defined here for compatibility with Go < 1.14 dwarfTreeCacheSize = 512 // size of the dwarfTree cache of each image ) diff --git a/pkg/proc/proc_general_test.go b/pkg/proc/proc_general_test.go index 56600aa1..dfa040a9 100644 --- a/pkg/proc/proc_general_test.go +++ b/pkg/proc/proc_general_test.go @@ -136,7 +136,7 @@ func TestDwarfVersion(t *testing.T) { const fakeEntryPoint = 1 assertNoError(bi.LoadBinaryInfo(fixture.Path, fakeEntryPoint, nil), t, "LoadBinaryInfo") for _, cu := range bi.Images[0].compileUnits { - if cu.Version != 4 { + if cu.Version != 4 && cu.Version != 5 { t.Errorf("compile unit %q at %#x has bad version %d", cu.name, cu.entry.Offset, cu.Version) } } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 27ac1c31..4f4ae52a 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -720,13 +720,13 @@ func TestStacktrace(t *testing.T) { locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") - if len(locations) != len(stacks[i])+2 { - t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2) - } - t.Logf("Stacktrace %d:\n", i) for i := range locations { - t.Logf("\t%s:%d\n", locations[i].Call.File, locations[i].Call.Line) + t.Logf("\t%s (%#x) %s:%d\n", locations[i].Call.Fn.Name, locations[i].Call.PC, locations[i].Call.File, locations[i].Call.Line) + } + + if len(locations) != len(stacks[i])+2 { + t.Fatalf("Wrong stack trace size %d %d\n", len(locations), len(stacks[i])+2) } for j := range stacks[i] { @@ -3193,6 +3193,7 @@ func testDeclLineCount(t *testing.T, p *proc.Target, lineno int, tgtvars []strin assertLineNumber(p, t, lineno, "Program did not continue to correct next location") scope, err := proc.GoroutineScope(p, p.CurrentThread()) + t.Logf("scope PC: %#x\n", scope.PC) assertNoError(err, t, fmt.Sprintf("GoroutineScope (:%d)", lineno)) vars, err := scope.Locals(0, "") assertNoError(err, t, fmt.Sprintf("Locals (:%d)", lineno))