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