From 4e834731546cecf62030f8ddf4bc37e330e74439 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 21 Jul 2020 22:39:09 +0200 Subject: [PATCH] dwarf/line: support DWARF version 5 (#2090) DWARFv5 has a new format for the header, directory table and line table of the debug_line section. --- pkg/dwarf/line/line_parser.go | 86 ++++++++++++++++++- pkg/dwarf/line/parse_util.go | 144 ++++++++++++++++++++++++++++++++ pkg/dwarf/line/state_machine.go | 10 ++- 3 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 pkg/dwarf/line/parse_util.go diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index 71dac59f..8bf58b44 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -84,8 +84,13 @@ func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}) dbl.normalizeBackslash = normalizeBackslash parseDebugLinePrologue(dbl, buf) - parseIncludeDirs(dbl, buf) - parseFileEntries(dbl, buf) + if dbl.Prologue.Version >= 5 { + parseIncludeDirs5(dbl, buf) + parseFileEntries5(dbl, buf) + } else { + parseIncludeDirs2(dbl, buf) + parseFileEntries2(dbl, buf) + } // Instructions size calculation breakdown: // - dbl.Prologue.UnitLength is the length of the entire unit, not including the 4 bytes to represent that length. @@ -101,6 +106,12 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4)) p.Version = binary.LittleEndian.Uint16(buf.Next(2)) + if p.Version >= 5 { + dbl.ptrSize = int(buf.Next(1)[0]) // address_size + 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 { @@ -119,7 +130,8 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { dbl.Prologue = p } -func parseIncludeDirs(info *DebugLineInfo, buf *bytes.Buffer) { +// parseIncludeDirs2 parses the directory table for DWARF version 2 through 4. +func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) { for { str, _ := util.ParseString(buf) if str == "" { @@ -130,7 +142,33 @@ func parseIncludeDirs(info *DebugLineInfo, buf *bytes.Buffer) { } } -func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) { +// parseIncludeDirs5 parses the directory table for DWARF version 5. +func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) { + dirEntryFormReader := readEntryFormat(buf, info.Logf) + dirCount, _ := util.DecodeULEB128(buf) + info.IncludeDirs = make([]string, 0, dirCount) + for i := uint64(0); i < dirCount; i++ { + dirEntryFormReader.reset() + for dirEntryFormReader.next(buf) { + switch dirEntryFormReader.contentType { + case _DW_LNCT_path: + if dirEntryFormReader.formCode != _DW_FORM_string { + info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str) + } else { + //TODO(aarzilli): support debug_string, debug_line_str + info.Logf("unsupported string form %#x", dirEntryFormReader.formCode) + } + case _DW_LNCT_directory_index: + case _DW_LNCT_timestamp: + case _DW_LNCT_size: + case _DW_LNCT_MD5: + } + } + } +} + +// parseFileEntries2 parses the file table for DWARF 2 through 4 +func parseFileEntries2(info *DebugLineInfo, buf *bytes.Buffer) { for { entry := readFileEntry(info, buf, true) if entry.Path == "" { @@ -165,3 +203,43 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) return entry } + +// parseFileEntries5 parses the file table for DWARF 5 +func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) { + fileEntryFormReader := readEntryFormat(buf, info.Logf) + fileCount, _ := util.DecodeULEB128(buf) + info.FileNames = make([]*FileEntry, 0, fileCount) + for i := 0; i < int(fileCount); i++ { + fileEntryFormReader.reset() + for fileEntryFormReader.next(buf) { + entry := new(FileEntry) + var path string + var diridx int = -1 + + switch fileEntryFormReader.contentType { + case _DW_LNCT_path: + if fileEntryFormReader.formCode != _DW_FORM_string { + path = fileEntryFormReader.str + } else { + //TODO(aarzilli): support debug_string, debug_line_str + info.Logf("unsupported string form %#x", fileEntryFormReader.formCode) + } + case _DW_LNCT_directory_index: + diridx = int(fileEntryFormReader.u64) + case _DW_LNCT_timestamp: + entry.LastModTime = fileEntryFormReader.u64 + case _DW_LNCT_size: + entry.Length = fileEntryFormReader.u64 + case _DW_LNCT_MD5: + // not implemented + } + + if diridx >= 0 && !filepath.IsAbs(path) && diridx < len(info.IncludeDirs) { + path = filepath.Join(info.IncludeDirs[diridx], path) + } + entry.Path = path + info.FileNames = append(info.FileNames, entry) + info.Lookup[entry.Path] = entry + } + } +} diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go new file mode 100644 index 00000000..10dd9633 --- /dev/null +++ b/pkg/dwarf/line/parse_util.go @@ -0,0 +1,144 @@ +package line + +import ( + "bytes" + "encoding/binary" + + "github.com/go-delve/delve/pkg/dwarf/util" +) + +const ( + _DW_FORM_block = 0x09 + _DW_FORM_block1 = 0x0a + _DW_FORM_block2 = 0x03 + _DW_FORM_block4 = 0x04 + _DW_FORM_data1 = 0x0b + _DW_FORM_data2 = 0x05 + _DW_FORM_data4 = 0x06 + _DW_FORM_data8 = 0x07 + _DW_FORM_data16 = 0x1e + _DW_FORM_flag = 0x0c + _DW_FORM_line_strp = 0x1f + _DW_FORM_sdata = 0x0d + _DW_FORM_sec_offset = 0x17 + _DW_FORM_string = 0x08 + _DW_FORM_strp = 0x0e + _DW_FORM_strx = 0x1a + _DW_FORM_strx1 = 0x25 + _DW_FORM_strx2 = 0x26 + _DW_FORM_strx3 = 0x27 + _DW_FORM_strx4 = 0x28 + _DW_FORM_udata = 0x0f +) + +const ( + _DW_LNCT_path = 0x1 + iota + _DW_LNCT_directory_index + _DW_LNCT_timestamp + _DW_LNCT_size + _DW_LNCT_MD5 +) + +type formReader struct { + logf func(string, ...interface{}) + contentTypes []uint64 + formCodes []uint64 + + contentType uint64 + formCode uint64 + + block []byte + u64 uint64 + i64 int64 + str string + + nexti int +} + +func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *formReader { + count := buf.Next(1)[0] + r := &formReader{ + logf: logf, + contentTypes: make([]uint64, count), + formCodes: make([]uint64, count), + } + for i := range r.contentTypes { + r.contentTypes[i], _ = util.DecodeULEB128(buf) + r.formCodes[i], _ = util.DecodeULEB128(buf) + } + return r +} + +func (rdr *formReader) reset() { + rdr.nexti = 0 +} + +func (rdr *formReader) next(buf *bytes.Buffer) bool { + if rdr.nexti >= len(rdr.contentTypes) { + return false + } + + rdr.contentType = rdr.contentTypes[rdr.nexti] + rdr.formCode = rdr.formCodes[rdr.nexti] + + switch rdr.formCode { + case _DW_FORM_block: + n, _ := util.DecodeULEB128(buf) + rdr.readBlock(buf, n) + + case _DW_FORM_block1: + rdr.readBlock(buf, uint64(buf.Next(1)[0])) + + case _DW_FORM_block2: + rdr.readBlock(buf, uint64(binary.LittleEndian.Uint16(buf.Next(2)))) + + case _DW_FORM_block4: + rdr.readBlock(buf, uint64(binary.LittleEndian.Uint32(buf.Next(4)))) + + case _DW_FORM_data1, _DW_FORM_flag, _DW_FORM_strx1: + rdr.u64 = uint64(buf.Next(1)[0]) + + case _DW_FORM_data2, _DW_FORM_strx2: + rdr.u64 = uint64(binary.LittleEndian.Uint16(buf.Next(2))) + + case _DW_FORM_data4, _DW_FORM_line_strp, _DW_FORM_sec_offset, _DW_FORM_strp, _DW_FORM_strx4: + rdr.u64 = uint64(binary.LittleEndian.Uint32(buf.Next(4))) + + case _DW_FORM_data8: + rdr.u64 = binary.LittleEndian.Uint64(buf.Next(8)) + + case _DW_FORM_data16: + rdr.readBlock(buf, 16) + + case _DW_FORM_sdata: + rdr.i64, _ = util.DecodeSLEB128(buf) + + case _DW_FORM_udata, _DW_FORM_strx: + rdr.u64, _ = util.DecodeULEB128(buf) + + case _DW_FORM_string: + rdr.str, _ = util.ParseString(buf) + + case _DW_FORM_strx3: + rdr.u64 = uint64(binary.LittleEndian.Uint32(append(buf.Next(3), 0x0))) + + 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++ + return true +} + +func (rdr *formReader) readBlock(buf *bytes.Buffer, n uint64) { + if cap(rdr.block) < int(n) { + rdr.block = make([]byte, 0, n) + } + rdr.block = rdr.block[:n] + buf.Read(rdr.block) +} diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index c307a106..b5c8abd2 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -501,10 +501,14 @@ func advanceline(sm *StateMachine, buf *bytes.Buffer) { func setfile(sm *StateMachine, buf *bytes.Buffer) { i, _ := util.DecodeULEB128(buf) - if i-1 < uint64(len(sm.dbl.FileNames)) { - sm.file = sm.dbl.FileNames[i-1].Path + if sm.dbl.Prologue.Version < 5 { + // in DWARF v5 files are indexed starting from 0, in v4 and prior the index starts at 1 + i-- + } + if i < uint64(len(sm.dbl.FileNames)) { + sm.file = sm.dbl.FileNames[i].Path } else { - j := (i - 1) - uint64(len(sm.dbl.FileNames)) + j := i - uint64(len(sm.dbl.FileNames)) if j < uint64(len(sm.definedFiles)) { sm.file = sm.definedFiles[j].Path } else {