proc,dwarf: Improve DWARF v5 support (#2544)
While Go still mostly uses DWARF v4, newer versions of GCC will emit DWARF v5 by default. This patch improves support for DWARF v5 by parsing the .debug_line_str section and using that during file:line lookups. This patch only includes support for files, not directories. Co-authored-by: Derek Parker <deparker@redhat.com>
This commit is contained in:
parent
42ecbd4413
commit
544a803a80
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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++
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 != "" {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user