diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index 09dd6de7..e330ae4c 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -39,6 +39,9 @@ type DebugLineInfo struct { // lastMachineCache[pc] is a state machine stopped at an address after pc lastMachineCache map[uint64]*StateMachine + // debugLineStr is the contents of the .debug_line_str section. + debugLineStr []byte + // staticBase is the address at which the executable is loaded, 0 for non-PIEs staticBase uint64 @@ -59,7 +62,7 @@ type FileEntry struct { type DebugLines []*DebugLineInfo // ParseAll parses all debug_line segments found in data -func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines { +func ParseAll(data []byte, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines { var ( lines = make(DebugLines, 0) buf = bytes.NewBuffer(data) @@ -67,7 +70,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64 // We have to parse multiple file name tables here. for buf.Len() > 0 { - lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize)) + lines = append(lines, Parse("", buf, debugLineStr, logfn, staticBase, normalizeBackslash, ptrSize)) } return lines @@ -75,9 +78,12 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64 // Parse parses a single debug_line segment from buf. Compdir is the // DW_AT_comp_dir attribute of the associated compile unit. -func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo { +func Parse(compdir string, buf *bytes.Buffer, debugLineStr []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo { dbl := new(DebugLineInfo) dbl.Logf = logfn + if logfn == nil { + dbl.Logf = func(string, ...interface{}) {} + } dbl.staticBase = staticBase dbl.ptrSize = ptrSize dbl.Lookup = make(map[string]*FileEntry) @@ -86,6 +92,7 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}) dbl.stateMachineCache = make(map[uint64]*StateMachine) dbl.lastMachineCache = make(map[uint64]*StateMachine) dbl.normalizeBackslash = normalizeBackslash + dbl.debugLineStr = debugLineStr parseDebugLinePrologue(dbl, buf) if dbl.Prologue.Version >= 5 { @@ -123,10 +130,9 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { dbl.ptrSize += int(buf.Next(1)[0]) // segment_selector_size } - // Version 4 or earlier p.Length = binary.LittleEndian.Uint32(buf.Next(4)) p.MinInstrLength = uint8(buf.Next(1)[0]) - if p.Version == 4 { + if p.Version >= 4 { p.MaxOpPerInstr = uint8(buf.Next(1)[0]) } else { p.MaxOpPerInstr = 1 @@ -174,10 +180,14 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool { for dirEntryFormReader.next(buf) { switch dirEntryFormReader.contentType { case _DW_LNCT_path: - if dirEntryFormReader.formCode != _DW_FORM_string { + switch dirEntryFormReader.formCode { + case _DW_FORM_string: info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str) - } else { - //TODO(aarzilli): support debug_string, debug_line_str + case _DW_FORM_line_strp: + buf := bytes.NewBuffer(info.debugLineStr[dirEntryFormReader.u64:]) + dir, _ := util.ParseString(buf) + info.IncludeDirs = append(info.IncludeDirs, dir) + default: info.Logf("unsupported string form %#x", dirEntryFormReader.formCode) } case _DW_LNCT_directory_index: @@ -277,10 +287,13 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool { switch fileEntryFormReader.contentType { case _DW_LNCT_path: - if fileEntryFormReader.formCode != _DW_FORM_string { + switch fileEntryFormReader.formCode { + case _DW_FORM_string: p = fileEntryFormReader.str - } else { - //TODO(aarzilli): support debug_string, debug_line_str + case _DW_FORM_line_strp: + buf := bytes.NewBuffer(info.debugLineStr[fileEntryFormReader.u64:]) + p, _ = util.ParseString(buf) + default: info.Logf("unsupported string form %#x", fileEntryFormReader.formCode) } case _DW_LNCT_directory_index: diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index cbb5b71c..043a16bf 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -74,7 +74,7 @@ func ptrSizeByRuntimeArch() int { func testDebugLinePrologueParser(p string, t *testing.T) { data := grabDebugLineSection(p, t) - debugLines := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch()) + debugLines := ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch()) mainFileFound := false for _, dbl := range debugLines { @@ -181,7 +181,7 @@ func BenchmarkLineParser(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _ = ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch()) + _ = ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch()) } } @@ -196,7 +196,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines { tb.Fatal("Could not read test data", err) } - return ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch()) + return ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch()) } func BenchmarkStateMachine(b *testing.B) { @@ -316,7 +316,7 @@ func TestDebugLineC(t *testing.T) { t.Fatal("Could not read test data", err) } - parsed := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch()) + parsed := ParseAll(data, nil, nil, 0, true, ptrSizeByRuntimeArch()) if len(parsed) == 0 { t.Fatal("Parser result is empty") @@ -365,7 +365,7 @@ func TestDebugLineDwarf4(t *testing.T) { t.Fatal("Could not read test data", err) } - debugLines := ParseAll(data, nil, 0, true, 8) + debugLines := ParseAll(data, nil, nil, 0, true, 8) for _, dbl := range debugLines { if dbl.Prologue.Version == 4 { diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go index 6f96569e..867d9b7b 100644 --- a/pkg/dwarf/line/parse_util.go +++ b/pkg/dwarf/line/parse_util.go @@ -165,13 +165,14 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { } rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0))) + case ^uint64(0): + // do nothing + default: if rdr.logf != nil { rdr.logf("unknown form code %#x", rdr.formCode) } rdr.formCodes[rdr.nexti] = ^uint64(0) // only print error once - case ^uint64(0): - // do nothing } rdr.nexti++ diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index d7e11c26..04501a97 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -103,9 +103,13 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *Stat for op := range standardopcodes { opcodes[op] = standardopcodes[op] } + var file string + if len(dbl.FileNames) > 0 { + file = dbl.FileNames[0].Path + } sm := &StateMachine{ dbl: dbl, - file: dbl.FileNames[0].Path, + file: file, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index 24e62762..b7281151 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -74,7 +74,7 @@ func TestGrafana(t *testing.T) { } cuname, _ := e.Val(dwarf.AttrName).(string) - lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false, 8) + lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, nil, t.Logf, 0, false, 8) lineInfo.endSeqIsValid = true sm := newStateMachine(lineInfo, lineInfo.Instructions, 8) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index b8af4e91..a7e0ea69 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -645,11 +645,12 @@ type Image struct { closer io.Closer sepDebugCloser io.Closer - dwarf *dwarf.Data - dwarfReader *dwarf.Reader - loclist2 *loclist.Dwarf2Reader - loclist5 *loclist.Dwarf5Reader - debugAddr *godwarf.DebugAddrSection + dwarf *dwarf.Data + dwarfReader *dwarf.Reader + loclist2 *loclist.Dwarf2Reader + loclist5 *loclist.Dwarf5Reader + debugAddr *godwarf.DebugAddrSection + debugLineStr []byte typeCache map[dwarf.Offset]godwarf.Type @@ -1202,6 +1203,8 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w image.loclist5 = loclist.NewDwarf5Reader(debugLoclistBytes) debugAddrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "addr") image.debugAddr = godwarf.ParseAddr(debugAddrBytes) + debugLineStrBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "line_str") + image.debugLineStr = debugLineStrBytes wg.Add(3) go bi.parseDebugFrameElf(image, dwarfFile, debugInfoBytes, wg) @@ -1723,7 +1726,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB logger.Printf(fmt, args) } } - cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize()) + cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize()) } cu.producer, _ = entry.Val(dwarf.AttrProducer).(string) if cu.isgo && cu.producer != "" { diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 194f552b..a455a445 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -3200,7 +3200,7 @@ func stacktraceCheck(t *testing.T, tc []string, frames []proc.Stackframe) []int func frameInFile(frame proc.Stackframe, file string) bool { for _, loc := range []proc.Location{frame.Current, frame.Call} { - if !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) { + if !strings.HasSuffix(loc.File, file) && !strings.HasSuffix(loc.File, "/"+file) && !strings.HasSuffix(loc.File, "\\"+file) { return false } if loc.Line <= 0 { @@ -3338,7 +3338,7 @@ func TestCgoSources(t *testing.T) { for _, needle := range []string{"main.go", "hello.c"} { found := false for _, k := range sources { - if strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) { + if strings.HasSuffix(k, needle) || strings.HasSuffix(k, "/"+needle) || strings.HasSuffix(k, "\\"+needle) { found = true break }