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:
Derek Parker 2021-06-22 04:37:46 -07:00 committed by GitHub
parent 42ecbd4413
commit 544a803a80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 49 additions and 28 deletions

@ -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
}