be47d6ecef
From-SVN: r200974
482 lines
12 KiB
Go
482 lines
12 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// DWARF line number information.
|
|
|
|
package dwarf
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
)
|
|
|
|
// A Line holds all the available information about the source code
|
|
// corresponding to a specific program counter address.
|
|
type Line struct {
|
|
Filename string // source file name
|
|
OpIndex int // index of operation in VLIW instruction
|
|
Line int // line number
|
|
Column int // column number
|
|
ISA int // instruction set code
|
|
Discriminator int // block discriminator
|
|
Stmt bool // instruction starts statement
|
|
Block bool // instruction starts basic block
|
|
EndPrologue bool // instruction ends function prologue
|
|
BeginEpilogue bool // instruction begins function epilogue
|
|
}
|
|
|
|
// LineForPc returns the line number information for a program counter
|
|
// address, if any. When this returns multiple Line structures in a
|
|
// context where only one can be used, the last one is the best.
|
|
func (d *Data) LineForPC(pc uint64) ([]*Line, error) {
|
|
for i := range d.unit {
|
|
u := &d.unit[i]
|
|
if u.pc == nil {
|
|
if err := d.readUnitLine(i, u); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
for _, ar := range u.pc {
|
|
if pc >= ar.low && pc < ar.high {
|
|
return d.findLine(u, pc)
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// readUnitLine reads in the line number information for a compilation
|
|
// unit.
|
|
func (d *Data) readUnitLine(i int, u *unit) error {
|
|
r := d.unitReader(i)
|
|
setLineOff := false
|
|
for {
|
|
e, err := r.Next()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e == nil {
|
|
break
|
|
}
|
|
if r.unit != i {
|
|
break
|
|
}
|
|
switch e.Tag {
|
|
case TagCompileUnit, TagSubprogram, TagEntryPoint, TagInlinedSubroutine:
|
|
low, lowok := e.Val(AttrLowpc).(uint64)
|
|
var high uint64
|
|
var highok bool
|
|
switch v := e.Val(AttrHighpc).(type) {
|
|
case uint64:
|
|
high = v
|
|
highok = true
|
|
case int64:
|
|
high = low + uint64(v)
|
|
highok = true
|
|
}
|
|
if lowok && highok {
|
|
u.pc = append(u.pc, addrRange{low, high})
|
|
} else if off, ok := e.Val(AttrRanges).(Offset); ok {
|
|
if err := d.readAddressRanges(off, low, u); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
val := e.Val(AttrStmtList)
|
|
if val != nil {
|
|
if off, ok := val.(int64); ok {
|
|
u.lineoff = Offset(off)
|
|
setLineOff = true
|
|
} else if off, ok := val.(Offset); ok {
|
|
u.lineoff = off
|
|
setLineOff = true
|
|
} else {
|
|
return errors.New("unrecognized format for DW_ATTR_stmt_list")
|
|
}
|
|
}
|
|
if dir, ok := e.Val(AttrCompDir).(string); ok {
|
|
u.dir = dir
|
|
}
|
|
}
|
|
}
|
|
if !setLineOff {
|
|
u.lineoff = Offset(0)
|
|
u.lineoff--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readAddressRanges adds address ranges to a unit.
|
|
func (d *Data) readAddressRanges(off Offset, base uint64, u *unit) error {
|
|
b := makeBuf(d, u, "ranges", off, d.ranges[off:])
|
|
var highest uint64
|
|
switch u.addrsize() {
|
|
case 1:
|
|
highest = 0xff
|
|
case 2:
|
|
highest = 0xffff
|
|
case 4:
|
|
highest = 0xffffffff
|
|
case 8:
|
|
highest = 0xffffffffffffffff
|
|
default:
|
|
return errors.New("unknown address size")
|
|
}
|
|
for {
|
|
if b.err != nil {
|
|
return b.err
|
|
}
|
|
low := b.addr()
|
|
high := b.addr()
|
|
if low == 0 && high == 0 {
|
|
return b.err
|
|
} else if low == highest {
|
|
base = high
|
|
} else {
|
|
u.pc = append(u.pc, addrRange{low + base, high + base})
|
|
}
|
|
}
|
|
}
|
|
|
|
// findLine finds the line information for a PC value, given the unit
|
|
// containing the information.
|
|
func (d *Data) findLine(u *unit, pc uint64) ([]*Line, error) {
|
|
if u.lines == nil {
|
|
if err := d.parseLine(u); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for _, ln := range u.lines {
|
|
if pc < ln.addrs[0].pc || pc > ln.addrs[len(ln.addrs)-1].pc {
|
|
continue
|
|
}
|
|
i := sort.Search(len(ln.addrs),
|
|
func(i int) bool { return ln.addrs[i].pc > pc })
|
|
i--
|
|
p := new(Line)
|
|
*p = ln.line
|
|
p.Line = ln.addrs[i].line
|
|
ret := []*Line{p}
|
|
for i++; i < len(ln.addrs) && ln.addrs[i].pc == pc; i++ {
|
|
p = new(Line)
|
|
*p = ln.line
|
|
p.Line = ln.addrs[i].line
|
|
ret = append(ret, p)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// FileLine returns the file name and line number for a program
|
|
// counter address, or "", 0 if unknown.
|
|
func (d *Data) FileLine(pc uint64) (string, int, error) {
|
|
r, err := d.LineForPC(pc)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if r == nil {
|
|
return "", 0, nil
|
|
}
|
|
ln := r[len(r)-1]
|
|
return ln.Filename, ln.Line, nil
|
|
}
|
|
|
|
// A mapLineInfo holds the PC values and line numbers associated with
|
|
// a single Line structure. This representation is chosen to reduce
|
|
// memory usage based on typical debug info.
|
|
type mapLineInfo struct {
|
|
line Line // line.Line will be zero
|
|
addrs lineAddrs // sorted by PC
|
|
}
|
|
|
|
// A list of lines. This will be sorted by PC.
|
|
type lineAddrs []oneLineInfo
|
|
|
|
func (p lineAddrs) Len() int { return len(p) }
|
|
func (p lineAddrs) Less(i, j int) bool { return p[i].pc < p[j].pc }
|
|
func (p lineAddrs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
// A oneLineInfo is a single PC and line number.
|
|
type oneLineInfo struct {
|
|
pc uint64
|
|
line int
|
|
}
|
|
|
|
// A lineHdr holds the relevant information from a line number
|
|
// program header.
|
|
type lineHdr struct {
|
|
version uint16 // version of line number encoding
|
|
minInsnLen uint8 // minimum instruction length
|
|
maxOpsPerInsn uint8 // maximum number of ops per instruction
|
|
defStmt bool // initial value of stmt register
|
|
lineBase int8 // line adjustment base
|
|
lineRange uint8 // line adjustment step
|
|
opBase uint8 // base of special opcode values
|
|
opLen []uint8 // lengths of standard opcodes
|
|
dirs []string // directories
|
|
files []string // file names
|
|
}
|
|
|
|
// parseLine parses the line number information for a compilation unit
|
|
func (d *Data) parseLine(u *unit) error {
|
|
if u.lineoff+1 == 0 {
|
|
return errors.New("unknown line offset")
|
|
}
|
|
b := makeBuf(d, u, "line", u.lineoff, d.line[u.lineoff:])
|
|
len := uint64(b.uint32())
|
|
dwarf64 := false
|
|
if len == 0xffffffff {
|
|
len = b.uint64()
|
|
dwarf64 = true
|
|
}
|
|
end := b.off + Offset(len)
|
|
hdr := d.parseLineHdr(u, &b, dwarf64)
|
|
if b.err == nil {
|
|
d.parseLineProgram(u, &b, hdr, end)
|
|
}
|
|
return b.err
|
|
}
|
|
|
|
// parseLineHdr parses a line number program header.
|
|
func (d *Data) parseLineHdr(u *unit, b *buf, dwarf64 bool) (hdr lineHdr) {
|
|
hdr.version = b.uint16()
|
|
if hdr.version < 2 || hdr.version > 4 {
|
|
b.error("unsupported DWARF version " + strconv.Itoa(int(hdr.version)))
|
|
return
|
|
}
|
|
|
|
var hlen Offset
|
|
if dwarf64 {
|
|
hlen = Offset(b.uint64())
|
|
} else {
|
|
hlen = Offset(b.uint32())
|
|
}
|
|
end := b.off + hlen
|
|
|
|
hdr.minInsnLen = b.uint8()
|
|
if hdr.version < 4 {
|
|
hdr.maxOpsPerInsn = 1
|
|
} else {
|
|
hdr.maxOpsPerInsn = b.uint8()
|
|
}
|
|
|
|
if b.uint8() == 0 {
|
|
hdr.defStmt = false
|
|
} else {
|
|
hdr.defStmt = true
|
|
}
|
|
hdr.lineBase = int8(b.uint8())
|
|
hdr.lineRange = b.uint8()
|
|
hdr.opBase = b.uint8()
|
|
hdr.opLen = b.bytes(int(hdr.opBase - 1))
|
|
|
|
for d := b.string(); len(d) > 0; d = b.string() {
|
|
hdr.dirs = append(hdr.dirs, d)
|
|
}
|
|
|
|
for f := b.string(); len(f) > 0; f = b.string() {
|
|
d := b.uint()
|
|
if !filepath.IsAbs(f) {
|
|
if d > 0 {
|
|
if d > uint64(len(hdr.dirs)) {
|
|
b.error("DWARF directory index out of range")
|
|
return
|
|
}
|
|
f = filepath.Join(hdr.dirs[d-1], f)
|
|
} else if u.dir != "" {
|
|
f = filepath.Join(u.dir, f)
|
|
}
|
|
}
|
|
b.uint() // file's last mtime
|
|
b.uint() // file length
|
|
hdr.files = append(hdr.files, f)
|
|
}
|
|
|
|
if end > b.off {
|
|
b.bytes(int(end - b.off))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// parseLineProgram parses a line program, adding information to
|
|
// d.lineInfo as it goes.
|
|
func (d *Data) parseLineProgram(u *unit, b *buf, hdr lineHdr, end Offset) {
|
|
address := uint64(0)
|
|
line := 1
|
|
resetLineInfo := Line{
|
|
Filename: "",
|
|
OpIndex: 0,
|
|
Line: 0,
|
|
Column: 0,
|
|
ISA: 0,
|
|
Discriminator: 0,
|
|
Stmt: hdr.defStmt,
|
|
Block: false,
|
|
EndPrologue: false,
|
|
BeginEpilogue: false,
|
|
}
|
|
if len(hdr.files) > 0 {
|
|
resetLineInfo.Filename = hdr.files[0]
|
|
}
|
|
lineInfo := resetLineInfo
|
|
|
|
var lines []mapLineInfo
|
|
|
|
minInsnLen := uint64(hdr.minInsnLen)
|
|
maxOpsPerInsn := uint64(hdr.maxOpsPerInsn)
|
|
lineBase := int(hdr.lineBase)
|
|
lineRange := hdr.lineRange
|
|
newLineInfo := true
|
|
for b.off < end && b.err == nil {
|
|
op := b.uint8()
|
|
if op >= hdr.opBase {
|
|
// This is a special opcode.
|
|
op -= hdr.opBase
|
|
advance := uint64(op / hdr.lineRange)
|
|
opIndex := uint64(lineInfo.OpIndex)
|
|
address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn)
|
|
newOpIndex := int((opIndex + advance) % maxOpsPerInsn)
|
|
line += lineBase + int(op%lineRange)
|
|
if newOpIndex != lineInfo.OpIndex {
|
|
lineInfo.OpIndex = newOpIndex
|
|
newLineInfo = true
|
|
}
|
|
lines, lineInfo, newLineInfo = d.addLine(lines, lineInfo, address, line, newLineInfo)
|
|
} else if op == LineExtendedOp {
|
|
c := b.uint()
|
|
op = b.uint8()
|
|
switch op {
|
|
case LineExtEndSequence:
|
|
u.lines = append(u.lines, lines...)
|
|
lineInfo = resetLineInfo
|
|
lines = nil
|
|
newLineInfo = true
|
|
case LineExtSetAddress:
|
|
address = b.addr()
|
|
case LineExtDefineFile:
|
|
f := b.string()
|
|
d := b.uint()
|
|
b.uint() // mtime
|
|
b.uint() // length
|
|
if d > 0 && !filepath.IsAbs(f) {
|
|
if d >= uint64(len(hdr.dirs)) {
|
|
b.error("DWARF directory index out of range")
|
|
return
|
|
}
|
|
f = filepath.Join(hdr.dirs[d-1], f)
|
|
}
|
|
hdr.files = append(hdr.files, f)
|
|
case LineExtSetDiscriminator:
|
|
lineInfo.Discriminator = int(b.uint())
|
|
newLineInfo = true
|
|
default:
|
|
if c > 0 {
|
|
b.bytes(int(c) - 1)
|
|
}
|
|
}
|
|
} else {
|
|
switch op {
|
|
case LineCopy:
|
|
lines, lineInfo, newLineInfo = d.addLine(lines, lineInfo, address, line, newLineInfo)
|
|
case LineAdvancePC:
|
|
advance := b.uint()
|
|
opIndex := uint64(lineInfo.OpIndex)
|
|
address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn)
|
|
newOpIndex := int((opIndex + advance) % maxOpsPerInsn)
|
|
if newOpIndex != lineInfo.OpIndex {
|
|
lineInfo.OpIndex = newOpIndex
|
|
newLineInfo = true
|
|
}
|
|
case LineAdvanceLine:
|
|
line += int(b.int())
|
|
case LineSetFile:
|
|
i := b.uint()
|
|
if i > uint64(len(hdr.files)) {
|
|
b.error("DWARF file number out of range")
|
|
return
|
|
}
|
|
lineInfo.Filename = hdr.files[i-1]
|
|
newLineInfo = true
|
|
case LineSetColumn:
|
|
lineInfo.Column = int(b.uint())
|
|
newLineInfo = true
|
|
case LineNegateStmt:
|
|
lineInfo.Stmt = !lineInfo.Stmt
|
|
newLineInfo = true
|
|
case LineSetBasicBlock:
|
|
lineInfo.Block = true
|
|
newLineInfo = true
|
|
case LineConstAddPC:
|
|
op = 255 - hdr.opBase
|
|
advance := uint64(op / hdr.lineRange)
|
|
opIndex := uint64(lineInfo.OpIndex)
|
|
address += minInsnLen * ((opIndex + advance) / maxOpsPerInsn)
|
|
newOpIndex := int((opIndex + advance) % maxOpsPerInsn)
|
|
if newOpIndex != lineInfo.OpIndex {
|
|
lineInfo.OpIndex = newOpIndex
|
|
newLineInfo = true
|
|
}
|
|
case LineFixedAdvancePC:
|
|
address += uint64(b.uint16())
|
|
if lineInfo.OpIndex != 0 {
|
|
lineInfo.OpIndex = 0
|
|
newLineInfo = true
|
|
}
|
|
case LineSetPrologueEnd:
|
|
lineInfo.EndPrologue = true
|
|
newLineInfo = true
|
|
case LineSetEpilogueBegin:
|
|
lineInfo.BeginEpilogue = true
|
|
newLineInfo = true
|
|
case LineSetISA:
|
|
lineInfo.ISA = int(b.uint())
|
|
newLineInfo = true
|
|
default:
|
|
if int(op) >= len(hdr.opLen) {
|
|
b.error("DWARF line opcode has unknown length")
|
|
return
|
|
}
|
|
for i := hdr.opLen[op-1]; i > 0; i-- {
|
|
b.int()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// addLine adds the current address and line to lines using lineInfo.
|
|
// If newLineInfo is true this is a new lineInfo. This returns the
|
|
// updated lines, lineInfo, and newLineInfo.
|
|
func (d *Data) addLine(lines []mapLineInfo, lineInfo Line, address uint64, line int, newLineInfo bool) ([]mapLineInfo, Line, bool) {
|
|
if newLineInfo {
|
|
if len(lines) > 0 {
|
|
sort.Sort(lines[len(lines)-1].addrs)
|
|
p := &lines[len(lines)-1]
|
|
if len(p.addrs) > 0 && address > p.addrs[len(p.addrs)-1].pc {
|
|
p.addrs = append(p.addrs, oneLineInfo{address, p.addrs[len(p.addrs)-1].line})
|
|
}
|
|
}
|
|
lines = append(lines, mapLineInfo{line: lineInfo})
|
|
}
|
|
p := &lines[len(lines)-1]
|
|
p.addrs = append(p.addrs, oneLineInfo{address, line})
|
|
|
|
if lineInfo.Block || lineInfo.EndPrologue || lineInfo.BeginEpilogue || lineInfo.Discriminator != 0 {
|
|
lineInfo.Block = false
|
|
lineInfo.EndPrologue = false
|
|
lineInfo.BeginEpilogue = false
|
|
lineInfo.Discriminator = 0
|
|
newLineInfo = true
|
|
} else {
|
|
newLineInfo = false
|
|
}
|
|
|
|
return lines, lineInfo, newLineInfo
|
|
}
|