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