2015-03-28 01:12:07 +00:00
|
|
|
package line
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2017-09-01 13:30:45 +00:00
|
|
|
"path/filepath"
|
2020-02-11 01:31:54 +00:00
|
|
|
"strings"
|
2015-03-28 01:12:07 +00:00
|
|
|
|
2019-01-04 18:39:25 +00:00
|
|
|
"github.com/go-delve/delve/pkg/dwarf/util"
|
2015-03-28 01:12:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type DebugLinePrologue struct {
|
2015-08-28 08:06:22 +00:00
|
|
|
UnitLength uint32
|
2015-03-28 01:12:07 +00:00
|
|
|
Version uint16
|
2015-08-28 08:06:22 +00:00
|
|
|
Length uint32
|
2015-03-28 01:12:07 +00:00
|
|
|
MinInstrLength uint8
|
2020-04-09 20:57:44 +00:00
|
|
|
MaxOpPerInstr uint8
|
2015-03-28 01:12:07 +00:00
|
|
|
InitialIsStmt uint8
|
|
|
|
LineBase int8
|
|
|
|
LineRange uint8
|
|
|
|
OpcodeBase uint8
|
|
|
|
StdOpLengths []uint8
|
|
|
|
}
|
|
|
|
|
|
|
|
type DebugLineInfo struct {
|
|
|
|
Prologue *DebugLinePrologue
|
|
|
|
IncludeDirs []string
|
|
|
|
FileNames []*FileEntry
|
|
|
|
Instructions []byte
|
2015-08-28 08:06:22 +00:00
|
|
|
Lookup map[string]*FileEntry
|
2017-08-18 17:49:29 +00:00
|
|
|
|
2018-06-14 18:12:11 +00:00
|
|
|
Logf func(string, ...interface{})
|
|
|
|
|
2017-08-18 17:49:29 +00:00
|
|
|
// stateMachineCache[pc] is a state machine stopped at pc
|
|
|
|
stateMachineCache map[uint64]*StateMachine
|
|
|
|
|
|
|
|
// lastMachineCache[pc] is a state machine stopped at an address after pc
|
|
|
|
lastMachineCache map[uint64]*StateMachine
|
2020-02-11 01:31:54 +00:00
|
|
|
|
2018-05-29 15:01:51 +00:00
|
|
|
// staticBase is the address at which the executable is loaded, 0 for non-PIEs
|
|
|
|
staticBase uint64
|
2020-02-11 01:31:54 +00:00
|
|
|
|
|
|
|
// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
|
|
|
|
normalizeBackslash bool
|
2020-04-09 20:57:44 +00:00
|
|
|
ptrSize int
|
2020-12-04 17:35:57 +00:00
|
|
|
endSeqIsValid bool
|
2015-03-28 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type FileEntry struct {
|
2017-09-01 13:30:45 +00:00
|
|
|
Path string
|
2015-03-28 01:12:07 +00:00
|
|
|
DirIdx uint64
|
|
|
|
LastModTime uint64
|
|
|
|
Length uint64
|
|
|
|
}
|
|
|
|
|
2015-08-28 08:06:22 +00:00
|
|
|
type DebugLines []*DebugLineInfo
|
|
|
|
|
2017-09-01 13:30:45 +00:00
|
|
|
// ParseAll parses all debug_line segments found in data
|
2020-03-10 16:34:40 +00:00
|
|
|
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
|
2015-03-28 01:12:07 +00:00
|
|
|
var (
|
2015-08-28 08:06:22 +00:00
|
|
|
lines = make(DebugLines, 0)
|
|
|
|
buf = bytes.NewBuffer(data)
|
2015-03-28 01:12:07 +00:00
|
|
|
)
|
|
|
|
|
2015-08-28 08:06:22 +00:00
|
|
|
// We have to parse multiple file name tables here.
|
|
|
|
for buf.Len() > 0 {
|
2020-03-10 16:34:40 +00:00
|
|
|
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize))
|
2017-09-01 13:30:45 +00:00
|
|
|
}
|
2015-08-28 08:06:22 +00:00
|
|
|
|
2017-09-01 13:30:45 +00:00
|
|
|
return lines
|
|
|
|
}
|
2015-08-28 08:06:22 +00:00
|
|
|
|
2017-09-01 13:30:45 +00:00
|
|
|
// Parse parses a single debug_line segment from buf. Compdir is the
|
|
|
|
// DW_AT_comp_dir attribute of the associated compile unit.
|
2020-03-10 16:34:40 +00:00
|
|
|
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
|
2017-09-01 13:30:45 +00:00
|
|
|
dbl := new(DebugLineInfo)
|
2018-06-14 18:12:11 +00:00
|
|
|
dbl.Logf = logfn
|
2018-05-29 15:01:51 +00:00
|
|
|
dbl.staticBase = staticBase
|
2020-03-10 16:34:40 +00:00
|
|
|
dbl.ptrSize = ptrSize
|
2017-09-01 13:30:45 +00:00
|
|
|
dbl.Lookup = make(map[string]*FileEntry)
|
2020-02-21 17:00:34 +00:00
|
|
|
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
|
2015-08-28 08:06:22 +00:00
|
|
|
|
2017-08-18 17:49:29 +00:00
|
|
|
dbl.stateMachineCache = make(map[uint64]*StateMachine)
|
|
|
|
dbl.lastMachineCache = make(map[uint64]*StateMachine)
|
2020-02-11 01:31:54 +00:00
|
|
|
dbl.normalizeBackslash = normalizeBackslash
|
2017-08-18 17:49:29 +00:00
|
|
|
|
2017-09-01 13:30:45 +00:00
|
|
|
parseDebugLinePrologue(dbl, buf)
|
2020-07-21 20:39:09 +00:00
|
|
|
if dbl.Prologue.Version >= 5 {
|
|
|
|
parseIncludeDirs5(dbl, buf)
|
|
|
|
parseFileEntries5(dbl, buf)
|
|
|
|
} else {
|
|
|
|
parseIncludeDirs2(dbl, buf)
|
|
|
|
parseFileEntries2(dbl, buf)
|
|
|
|
}
|
2017-09-01 13:30:45 +00:00
|
|
|
|
|
|
|
// Instructions size calculation breakdown:
|
|
|
|
// - 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))
|
|
|
|
|
|
|
|
return dbl
|
2015-03-28 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
|
|
|
|
p := new(DebugLinePrologue)
|
|
|
|
|
2015-08-28 08:06:22 +00:00
|
|
|
p.UnitLength = binary.LittleEndian.Uint32(buf.Next(4))
|
2015-03-28 01:12:07 +00:00
|
|
|
p.Version = binary.LittleEndian.Uint16(buf.Next(2))
|
2020-07-21 20:39:09 +00:00
|
|
|
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
|
2015-08-28 08:06:22 +00:00
|
|
|
p.Length = binary.LittleEndian.Uint32(buf.Next(4))
|
2015-03-28 01:12:07 +00:00
|
|
|
p.MinInstrLength = uint8(buf.Next(1)[0])
|
2020-04-09 20:57:44 +00:00
|
|
|
if p.Version == 4 {
|
|
|
|
p.MaxOpPerInstr = uint8(buf.Next(1)[0])
|
|
|
|
} else {
|
|
|
|
p.MaxOpPerInstr = 1
|
|
|
|
}
|
2015-03-28 01:12:07 +00:00
|
|
|
p.InitialIsStmt = uint8(buf.Next(1)[0])
|
|
|
|
p.LineBase = int8(buf.Next(1)[0])
|
|
|
|
p.LineRange = uint8(buf.Next(1)[0])
|
|
|
|
p.OpcodeBase = uint8(buf.Next(1)[0])
|
|
|
|
|
|
|
|
p.StdOpLengths = make([]uint8, p.OpcodeBase-1)
|
|
|
|
binary.Read(buf, binary.LittleEndian, &p.StdOpLengths)
|
|
|
|
|
|
|
|
dbl.Prologue = p
|
|
|
|
}
|
|
|
|
|
2020-07-21 20:39:09 +00:00
|
|
|
// parseIncludeDirs2 parses the directory table for DWARF version 2 through 4.
|
|
|
|
func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) {
|
2015-03-28 01:12:07 +00:00
|
|
|
for {
|
|
|
|
str, _ := util.ParseString(buf)
|
|
|
|
if str == "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
info.IncludeDirs = append(info.IncludeDirs, str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-21 20:39:09 +00:00
|
|
|
// 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) {
|
2015-03-28 01:12:07 +00:00
|
|
|
for {
|
2017-08-19 11:06:20 +00:00
|
|
|
entry := readFileEntry(info, buf, true)
|
2017-09-01 13:30:45 +00:00
|
|
|
if entry.Path == "" {
|
2015-03-28 01:12:07 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
info.FileNames = append(info.FileNames, entry)
|
2017-09-01 13:30:45 +00:00
|
|
|
info.Lookup[entry.Path] = entry
|
2015-03-28 01:12:07 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-19 11:06:20 +00:00
|
|
|
|
|
|
|
func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) *FileEntry {
|
|
|
|
entry := new(FileEntry)
|
|
|
|
|
|
|
|
entry.Path, _ = util.ParseString(buf)
|
|
|
|
if entry.Path == "" && exitOnEmptyPath {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
|
2020-02-11 01:31:54 +00:00
|
|
|
if info.normalizeBackslash {
|
|
|
|
entry.Path = strings.Replace(entry.Path, "\\", "/", -1)
|
|
|
|
}
|
|
|
|
|
2017-08-19 11:06:20 +00:00
|
|
|
entry.DirIdx, _ = util.DecodeULEB128(buf)
|
|
|
|
entry.LastModTime, _ = util.DecodeULEB128(buf)
|
|
|
|
entry.Length, _ = util.DecodeULEB128(buf)
|
|
|
|
if !filepath.IsAbs(entry.Path) {
|
|
|
|
if entry.DirIdx >= 0 && entry.DirIdx < uint64(len(info.IncludeDirs)) {
|
|
|
|
entry.Path = filepath.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return entry
|
|
|
|
}
|
2020-07-21 20:39:09 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|