| | |
| | |
| | |
| |
|
| | package dwarf |
| |
|
| | import ( |
| | "errors" |
| | "fmt" |
| | "io" |
| | "path" |
| | "strings" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | type LineReader struct { |
| | buf buf |
| |
|
| | |
| | section []byte |
| |
|
| | str []byte |
| | lineStr []byte |
| |
|
| | |
| | version uint16 |
| | addrsize int |
| | segmentSelectorSize int |
| | minInstructionLength int |
| | maxOpsPerInstruction int |
| | defaultIsStmt bool |
| | lineBase int |
| | lineRange int |
| | opcodeBase int |
| | opcodeLengths []int |
| | directories []string |
| | fileEntries []*LineFile |
| |
|
| | programOffset Offset |
| | endOffset Offset |
| |
|
| | initialFileEntries int |
| |
|
| | |
| | state LineEntry |
| | fileIndex int |
| | } |
| |
|
| | |
| | type LineEntry struct { |
| | |
| | |
| | |
| | |
| | Address uint64 |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | OpIndex int |
| |
|
| | |
| | |
| | File *LineFile |
| |
|
| | |
| | |
| | |
| | |
| | Line int |
| |
|
| | |
| | |
| | |
| | Column int |
| |
|
| | |
| | |
| | |
| | IsStmt bool |
| |
|
| | |
| | |
| | BasicBlock bool |
| |
|
| | |
| | |
| | |
| | |
| | |
| | PrologueEnd bool |
| |
|
| | |
| | |
| | |
| | |
| | |
| | EpilogueBegin bool |
| |
|
| | |
| | |
| | |
| | |
| | |
| | ISA int |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | Discriminator int |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | EndSequence bool |
| | } |
| |
|
| | |
| | type LineFile struct { |
| | Name string |
| | Mtime uint64 |
| | Length int |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func (d *Data) LineReader(cu *Entry) (*LineReader, error) { |
| | if d.line == nil { |
| | |
| | return nil, nil |
| | } |
| |
|
| | |
| | off, ok := cu.Val(AttrStmtList).(int64) |
| | if !ok { |
| | |
| | return nil, nil |
| | } |
| | if off < 0 || off > int64(len(d.line)) { |
| | return nil, errors.New("AttrStmtList value out of range") |
| | } |
| | |
| | |
| | compDir, _ := cu.Val(AttrCompDir).(string) |
| |
|
| | |
| | u := &d.unit[d.offsetToUnit(cu.Offset)] |
| | buf := makeBuf(d, u, "line", Offset(off), d.line[off:]) |
| | |
| | r := LineReader{ |
| | buf: buf, |
| | section: d.line, |
| | str: d.str, |
| | lineStr: d.lineStr, |
| | } |
| |
|
| | |
| | if err := r.readHeader(compDir); err != nil { |
| | return nil, err |
| | } |
| |
|
| | |
| | r.Reset() |
| |
|
| | return &r, nil |
| | } |
| |
|
| | |
| | |
| | func (r *LineReader) readHeader(compDir string) error { |
| | buf := &r.buf |
| |
|
| | |
| | hdrOffset := buf.off |
| | unitLength, dwarf64 := buf.unitLength() |
| | r.endOffset = buf.off + unitLength |
| | if r.endOffset > buf.off+Offset(len(buf.data)) { |
| | return DecodeError{"line", hdrOffset, fmt.Sprintf("line table end %d exceeds section size %d", r.endOffset, buf.off+Offset(len(buf.data)))} |
| | } |
| | r.version = buf.uint16() |
| | if buf.err == nil && (r.version < 2 || r.version > 5) { |
| | |
| | |
| | |
| | |
| | |
| | return DecodeError{"line", hdrOffset, fmt.Sprintf("unknown line table version %d", r.version)} |
| | } |
| | if r.version >= 5 { |
| | r.addrsize = int(buf.uint8()) |
| | r.segmentSelectorSize = int(buf.uint8()) |
| | } else { |
| | r.addrsize = buf.format.addrsize() |
| | r.segmentSelectorSize = 0 |
| | } |
| | var headerLength Offset |
| | if dwarf64 { |
| | headerLength = Offset(buf.uint64()) |
| | } else { |
| | headerLength = Offset(buf.uint32()) |
| | } |
| | programOffset := buf.off + headerLength |
| | if programOffset > r.endOffset { |
| | return DecodeError{"line", hdrOffset, fmt.Sprintf("malformed line table: program offset %d exceeds end offset %d", programOffset, r.endOffset)} |
| | } |
| | r.programOffset = programOffset |
| | r.minInstructionLength = int(buf.uint8()) |
| | if r.version >= 4 { |
| | |
| | r.maxOpsPerInstruction = int(buf.uint8()) |
| | } else { |
| | r.maxOpsPerInstruction = 1 |
| | } |
| | r.defaultIsStmt = buf.uint8() != 0 |
| | r.lineBase = int(int8(buf.uint8())) |
| | r.lineRange = int(buf.uint8()) |
| |
|
| | |
| | if buf.err != nil { |
| | return buf.err |
| | } |
| | if r.maxOpsPerInstruction == 0 { |
| | return DecodeError{"line", hdrOffset, "invalid maximum operations per instruction: 0"} |
| | } |
| | if r.lineRange == 0 { |
| | return DecodeError{"line", hdrOffset, "invalid line range: 0"} |
| | } |
| |
|
| | |
| | r.opcodeBase = int(buf.uint8()) |
| | r.opcodeLengths = make([]int, r.opcodeBase) |
| | for i := 1; i < r.opcodeBase; i++ { |
| | r.opcodeLengths[i] = int(buf.uint8()) |
| | } |
| |
|
| | |
| | if buf.err != nil { |
| | return buf.err |
| | } |
| | for i, length := range r.opcodeLengths { |
| | if known, ok := knownOpcodeLengths[i]; ok && known != length { |
| | return DecodeError{"line", hdrOffset, fmt.Sprintf("opcode %d expected to have length %d, but has length %d", i, known, length)} |
| | } |
| | } |
| |
|
| | if r.version < 5 { |
| | |
| | r.directories = []string{compDir} |
| | for { |
| | directory := buf.string() |
| | if buf.err != nil { |
| | return buf.err |
| | } |
| | if len(directory) == 0 { |
| | break |
| | } |
| | if !pathIsAbs(directory) { |
| | |
| | |
| | directory = pathJoin(compDir, directory) |
| | } |
| | r.directories = append(r.directories, directory) |
| | } |
| |
|
| | |
| | |
| | r.fileEntries = make([]*LineFile, 1) |
| | for { |
| | if done, err := r.readFileEntry(); err != nil { |
| | return err |
| | } else if done { |
| | break |
| | } |
| | } |
| | } else { |
| | dirFormat := r.readLNCTFormat() |
| | c := buf.uint() |
| | r.directories = make([]string, c) |
| | for i := range r.directories { |
| | dir, _, _, err := r.readLNCT(dirFormat, dwarf64) |
| | if err != nil { |
| | return err |
| | } |
| | r.directories[i] = dir |
| | } |
| | fileFormat := r.readLNCTFormat() |
| | c = buf.uint() |
| | r.fileEntries = make([]*LineFile, c) |
| | for i := range r.fileEntries { |
| | name, mtime, size, err := r.readLNCT(fileFormat, dwarf64) |
| | if err != nil { |
| | return err |
| | } |
| | r.fileEntries[i] = &LineFile{name, mtime, int(size)} |
| | } |
| | } |
| |
|
| | r.initialFileEntries = len(r.fileEntries) |
| |
|
| | return buf.err |
| | } |
| |
|
| | |
| | |
| | |
| | type lnctForm struct { |
| | lnct int |
| | form format |
| | } |
| |
|
| | |
| | func (r *LineReader) readLNCTFormat() []lnctForm { |
| | c := r.buf.uint8() |
| | ret := make([]lnctForm, c) |
| | for i := range ret { |
| | ret[i].lnct = int(r.buf.uint()) |
| | ret[i].form = format(r.buf.uint()) |
| | } |
| | return ret |
| | } |
| |
|
| | |
| | func (r *LineReader) readLNCT(s []lnctForm, dwarf64 bool) (path string, mtime uint64, size uint64, err error) { |
| | var dir string |
| | for _, lf := range s { |
| | var str string |
| | var val uint64 |
| | switch lf.form { |
| | case formString: |
| | str = r.buf.string() |
| | case formStrp, formLineStrp: |
| | var off uint64 |
| | if dwarf64 { |
| | off = r.buf.uint64() |
| | } else { |
| | off = uint64(r.buf.uint32()) |
| | } |
| | if uint64(int(off)) != off { |
| | return "", 0, 0, DecodeError{"line", r.buf.off, "strp/line_strp offset out of range"} |
| | } |
| | var b1 buf |
| | if lf.form == formStrp { |
| | b1 = makeBuf(r.buf.dwarf, r.buf.format, "str", 0, r.str) |
| | } else { |
| | b1 = makeBuf(r.buf.dwarf, r.buf.format, "line_str", 0, r.lineStr) |
| | } |
| | b1.skip(int(off)) |
| | str = b1.string() |
| | if b1.err != nil { |
| | return "", 0, 0, DecodeError{"line", r.buf.off, b1.err.Error()} |
| | } |
| | case formStrpSup: |
| | |
| | if dwarf64 { |
| | r.buf.uint64() |
| | } else { |
| | r.buf.uint32() |
| | } |
| | case formStrx: |
| | |
| | r.buf.uint() |
| | case formStrx1: |
| | r.buf.uint8() |
| | case formStrx2: |
| | r.buf.uint16() |
| | case formStrx3: |
| | r.buf.uint24() |
| | case formStrx4: |
| | r.buf.uint32() |
| | case formData1: |
| | val = uint64(r.buf.uint8()) |
| | case formData2: |
| | val = uint64(r.buf.uint16()) |
| | case formData4: |
| | val = uint64(r.buf.uint32()) |
| | case formData8: |
| | val = r.buf.uint64() |
| | case formData16: |
| | r.buf.bytes(16) |
| | case formDwarfBlock: |
| | r.buf.bytes(int(r.buf.uint())) |
| | case formUdata: |
| | val = r.buf.uint() |
| | } |
| |
|
| | switch lf.lnct { |
| | case lnctPath: |
| | path = str |
| | case lnctDirectoryIndex: |
| | if val >= uint64(len(r.directories)) { |
| | return "", 0, 0, DecodeError{"line", r.buf.off, "directory index out of range"} |
| | } |
| | dir = r.directories[val] |
| | case lnctTimestamp: |
| | mtime = val |
| | case lnctSize: |
| | size = val |
| | case lnctMD5: |
| | |
| | } |
| | } |
| |
|
| | if dir != "" && path != "" { |
| | path = pathJoin(dir, path) |
| | } |
| |
|
| | return path, mtime, size, nil |
| | } |
| |
|
| | |
| | |
| | |
| | func (r *LineReader) readFileEntry() (bool, error) { |
| | name := r.buf.string() |
| | if r.buf.err != nil { |
| | return false, r.buf.err |
| | } |
| | if len(name) == 0 { |
| | return true, nil |
| | } |
| | off := r.buf.off |
| | dirIndex := int(r.buf.uint()) |
| | if !pathIsAbs(name) { |
| | if dirIndex >= len(r.directories) { |
| | return false, DecodeError{"line", off, "directory index too large"} |
| | } |
| | name = pathJoin(r.directories[dirIndex], name) |
| | } |
| | mtime := r.buf.uint() |
| | length := int(r.buf.uint()) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if len(r.fileEntries) < cap(r.fileEntries) { |
| | fe := r.fileEntries[:len(r.fileEntries)+1] |
| | if fe[len(fe)-1] != nil { |
| | |
| | r.fileEntries = fe |
| | return false, nil |
| | } |
| | } |
| | r.fileEntries = append(r.fileEntries, &LineFile{name, mtime, length}) |
| | return false, nil |
| | } |
| |
|
| | |
| | |
| | func (r *LineReader) updateFile() { |
| | if r.fileIndex < len(r.fileEntries) { |
| | r.state.File = r.fileEntries[r.fileIndex] |
| | } else { |
| | r.state.File = nil |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | func (r *LineReader) Next(entry *LineEntry) error { |
| | if r.buf.err != nil { |
| | return r.buf.err |
| | } |
| |
|
| | |
| | |
| | for { |
| | if len(r.buf.data) == 0 { |
| | return io.EOF |
| | } |
| | emit := r.step(entry) |
| | if r.buf.err != nil { |
| | return r.buf.err |
| | } |
| | if emit { |
| | return nil |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | var knownOpcodeLengths = map[int]int{ |
| | lnsCopy: 0, |
| | lnsAdvancePC: 1, |
| | lnsAdvanceLine: 1, |
| | lnsSetFile: 1, |
| | lnsNegateStmt: 0, |
| | lnsSetBasicBlock: 0, |
| | lnsConstAddPC: 0, |
| | lnsSetPrologueEnd: 0, |
| | lnsSetEpilogueBegin: 0, |
| | lnsSetISA: 1, |
| | |
| | |
| | |
| | } |
| |
|
| | |
| | |
| | |
| | func (r *LineReader) step(entry *LineEntry) bool { |
| | opcode := int(r.buf.uint8()) |
| |
|
| | if opcode >= r.opcodeBase { |
| | |
| | adjustedOpcode := opcode - r.opcodeBase |
| | r.advancePC(adjustedOpcode / r.lineRange) |
| | lineDelta := r.lineBase + adjustedOpcode%r.lineRange |
| | r.state.Line += lineDelta |
| | goto emit |
| | } |
| |
|
| | switch opcode { |
| | case 0: |
| | |
| | length := Offset(r.buf.uint()) |
| | startOff := r.buf.off |
| | opcode := r.buf.uint8() |
| |
|
| | switch opcode { |
| | case lneEndSequence: |
| | r.state.EndSequence = true |
| | *entry = r.state |
| | r.resetState() |
| |
|
| | case lneSetAddress: |
| | switch r.addrsize { |
| | case 1: |
| | r.state.Address = uint64(r.buf.uint8()) |
| | case 2: |
| | r.state.Address = uint64(r.buf.uint16()) |
| | case 4: |
| | r.state.Address = uint64(r.buf.uint32()) |
| | case 8: |
| | r.state.Address = r.buf.uint64() |
| | default: |
| | r.buf.error("unknown address size") |
| | } |
| |
|
| | case lneDefineFile: |
| | if done, err := r.readFileEntry(); err != nil { |
| | r.buf.err = err |
| | return false |
| | } else if done { |
| | r.buf.err = DecodeError{"line", startOff, "malformed DW_LNE_define_file operation"} |
| | return false |
| | } |
| | r.updateFile() |
| |
|
| | case lneSetDiscriminator: |
| | |
| | r.state.Discriminator = int(r.buf.uint()) |
| | } |
| |
|
| | r.buf.skip(int(startOff + length - r.buf.off)) |
| |
|
| | if opcode == lneEndSequence { |
| | return true |
| | } |
| |
|
| | |
| | case lnsCopy: |
| | goto emit |
| |
|
| | case lnsAdvancePC: |
| | r.advancePC(int(r.buf.uint())) |
| |
|
| | case lnsAdvanceLine: |
| | r.state.Line += int(r.buf.int()) |
| |
|
| | case lnsSetFile: |
| | r.fileIndex = int(r.buf.uint()) |
| | r.updateFile() |
| |
|
| | case lnsSetColumn: |
| | r.state.Column = int(r.buf.uint()) |
| |
|
| | case lnsNegateStmt: |
| | r.state.IsStmt = !r.state.IsStmt |
| |
|
| | case lnsSetBasicBlock: |
| | r.state.BasicBlock = true |
| |
|
| | case lnsConstAddPC: |
| | r.advancePC((255 - r.opcodeBase) / r.lineRange) |
| |
|
| | case lnsFixedAdvancePC: |
| | r.state.Address += uint64(r.buf.uint16()) |
| |
|
| | |
| | case lnsSetPrologueEnd: |
| | r.state.PrologueEnd = true |
| |
|
| | case lnsSetEpilogueBegin: |
| | r.state.EpilogueBegin = true |
| |
|
| | case lnsSetISA: |
| | r.state.ISA = int(r.buf.uint()) |
| |
|
| | default: |
| | |
| | |
| | for i := 0; i < r.opcodeLengths[opcode]; i++ { |
| | r.buf.uint() |
| | } |
| | } |
| | return false |
| |
|
| | emit: |
| | *entry = r.state |
| | r.state.BasicBlock = false |
| | r.state.PrologueEnd = false |
| | r.state.EpilogueBegin = false |
| | r.state.Discriminator = 0 |
| | return true |
| | } |
| |
|
| | |
| | |
| | func (r *LineReader) advancePC(opAdvance int) { |
| | opIndex := r.state.OpIndex + opAdvance |
| | r.state.Address += uint64(r.minInstructionLength * (opIndex / r.maxOpsPerInstruction)) |
| | r.state.OpIndex = opIndex % r.maxOpsPerInstruction |
| | } |
| |
|
| | |
| | type LineReaderPos struct { |
| | |
| | off Offset |
| | |
| | numFileEntries int |
| | |
| | |
| | state LineEntry |
| | fileIndex int |
| | } |
| |
|
| | |
| | func (r *LineReader) Tell() LineReaderPos { |
| | return LineReaderPos{r.buf.off, len(r.fileEntries), r.state, r.fileIndex} |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func (r *LineReader) Seek(pos LineReaderPos) { |
| | r.buf.off = pos.off |
| | r.buf.data = r.section[r.buf.off:r.endOffset] |
| | r.fileEntries = r.fileEntries[:pos.numFileEntries] |
| | r.state = pos.state |
| | r.fileIndex = pos.fileIndex |
| | } |
| |
|
| | |
| | |
| | func (r *LineReader) Reset() { |
| | |
| | r.buf.off = r.programOffset |
| | r.buf.data = r.section[r.buf.off:r.endOffset] |
| |
|
| | |
| | r.fileEntries = r.fileEntries[:r.initialFileEntries] |
| |
|
| | |
| | r.resetState() |
| | } |
| |
|
| | |
| | func (r *LineReader) resetState() { |
| | |
| | |
| | r.state = LineEntry{ |
| | Address: 0, |
| | OpIndex: 0, |
| | File: nil, |
| | Line: 1, |
| | Column: 0, |
| | IsStmt: r.defaultIsStmt, |
| | BasicBlock: false, |
| | PrologueEnd: false, |
| | EpilogueBegin: false, |
| | ISA: 0, |
| | Discriminator: 0, |
| | } |
| | r.fileIndex = 1 |
| | r.updateFile() |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (r *LineReader) Files() []*LineFile { |
| | return r.fileEntries |
| | } |
| |
|
| | |
| | |
| | var ErrUnknownPC = errors.New("ErrUnknownPC") |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (r *LineReader) SeekPC(pc uint64, entry *LineEntry) error { |
| | if err := r.Next(entry); err != nil { |
| | return err |
| | } |
| | if entry.Address > pc { |
| | |
| | r.Reset() |
| | if err := r.Next(entry); err != nil { |
| | return err |
| | } |
| | if entry.Address > pc { |
| | |
| | r.Reset() |
| | return ErrUnknownPC |
| | } |
| | } |
| |
|
| | |
| | for { |
| | var next LineEntry |
| | pos := r.Tell() |
| | if err := r.Next(&next); err != nil { |
| | if err == io.EOF { |
| | return ErrUnknownPC |
| | } |
| | return err |
| | } |
| | if next.Address > pc { |
| | if entry.EndSequence { |
| | |
| | return ErrUnknownPC |
| | } |
| | |
| | |
| | r.Seek(pos) |
| | return nil |
| | } |
| | *entry = next |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | func pathIsAbs(path string) bool { |
| | _, path = splitDrive(path) |
| | return len(path) > 0 && (path[0] == '/' || path[0] == '\\') |
| | } |
| |
|
| | |
| | |
| | func pathJoin(dirname, filename string) string { |
| | if len(dirname) == 0 { |
| | return filename |
| | } |
| | |
| | |
| | |
| | drive, dirname := splitDrive(dirname) |
| | if drive == "" { |
| | |
| | return path.Join(dirname, filename) |
| | } |
| | |
| | drive2, filename := splitDrive(filename) |
| | if drive2 != "" { |
| | if !strings.EqualFold(drive, drive2) { |
| | |
| | |
| | return drive2 + filename |
| | } |
| | |
| | } |
| | if !(strings.HasSuffix(dirname, "/") || strings.HasSuffix(dirname, `\`)) && dirname != "" { |
| | sep := `\` |
| | if strings.HasPrefix(dirname, "/") { |
| | sep = `/` |
| | } |
| | dirname += sep |
| | } |
| | return drive + dirname + filename |
| | } |
| |
|
| | |
| | |
| | func splitDrive(path string) (drive, rest string) { |
| | if len(path) >= 2 && path[1] == ':' { |
| | if c := path[0]; 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' { |
| | return path[:2], path[2:] |
| | } |
| | } |
| | if len(path) > 3 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/') { |
| | |
| | npath := strings.ReplaceAll(path, "/", `\`) |
| | |
| | slash1 := strings.IndexByte(npath[2:], '\\') + 2 |
| | if slash1 > 2 { |
| | |
| | slash2 := strings.IndexByte(npath[slash1+1:], '\\') + slash1 + 1 |
| | if slash2 > slash1 { |
| | return path[:slash2], path[slash2:] |
| | } |
| | } |
| | } |
| | return "", path |
| | } |
| |
|