package line import ( "bytes" "encoding/binary" "errors" "fmt" "io" "github.com/go-delve/delve/pkg/dwarf/util" ) type Location struct { File string Line int Address uint64 Delta int } type StateMachine struct { dbl *DebugLineInfo file string line int address uint64 column uint isStmt bool isa uint64 // instruction set architecture register (DWARFv4) basicBlock bool endSeq bool lastDelta int prologueEnd bool epilogueBegin bool // valid is true if the current value of the state machine is the address of // an instruction (using the terminology used by DWARF spec the current // value of the state machine should be appended to the matrix representing // the compilation unit) valid bool started bool buf *bytes.Buffer // remaining instructions opcodes []opcodefn definedFiles []*FileEntry // files defined with DW_LINE_define_file lastAddress uint64 lastFile string lastLine int ptrSize int } type opcodefn func(*StateMachine, *bytes.Buffer) // Special opcodes const ( DW_LNS_copy = 1 DW_LNS_advance_pc = 2 DW_LNS_advance_line = 3 DW_LNS_set_file = 4 DW_LNS_set_column = 5 DW_LNS_negate_stmt = 6 DW_LNS_set_basic_block = 7 DW_LNS_const_add_pc = 8 DW_LNS_fixed_advance_pc = 9 DW_LNS_prologue_end = 10 DW_LNS_epilogue_begin = 11 DW_LNS_set_isa = 12 ) // Extended opcodes const ( DW_LINE_end_sequence = 1 DW_LINE_set_address = 2 DW_LINE_define_file = 3 DW_LINE_set_discriminator = 4 ) var standardopcodes = map[byte]opcodefn{ DW_LNS_copy: copyfn, DW_LNS_advance_pc: advancepc, DW_LNS_advance_line: advanceline, DW_LNS_set_file: setfile, DW_LNS_set_column: setcolumn, DW_LNS_negate_stmt: negatestmt, DW_LNS_set_basic_block: setbasicblock, DW_LNS_const_add_pc: constaddpc, DW_LNS_fixed_advance_pc: fixedadvancepc, DW_LNS_prologue_end: prologueend, DW_LNS_epilogue_begin: epiloguebegin, DW_LNS_set_isa: setisa, } var extendedopcodes = map[byte]opcodefn{ DW_LINE_end_sequence: endsequence, DW_LINE_set_address: setaddress, DW_LINE_define_file: definefile, DW_LINE_set_discriminator: setdiscriminator, } func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *StateMachine { opcodes := make([]opcodefn, len(standardopcodes)+1) opcodes[0] = execExtendedOpcode for op := range standardopcodes { opcodes[op] = standardopcodes[op] } var file string if len(dbl.FileNames) > 0 { file = dbl.FileNames[0].Path } sm := &StateMachine{ dbl: dbl, file: file, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase, lastAddress: ^uint64(0), ptrSize: ptrSize, } return sm } // AllPCsForFileLines Adds all PCs for a given file and set (domain of map) of lines // to the map value corresponding to each line. func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64) { if lineInfo == nil { return } var ( lastAddr uint64 sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) ) for { if err := sm.next(); err != nil { if lineInfo.Logf != nil { lineInfo.Logf("AllPCsForFileLine error: %v", err) } break } if sm.address != lastAddr && sm.isStmt && sm.valid && sm.file == f { if pcs, ok := m[sm.line]; ok { pcs = append(pcs, sm.address) m[sm.line] = pcs lastAddr = sm.address } } } } var ErrNoSource = errors.New("no source available") // AllPCsBetween returns all PC addresses between begin and end (including both begin and end) // that have the is_stmt flag set and do not belong to excludeFile:excludeLine. func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile string, excludeLine int) ([]uint64, error) { if lineInfo == nil { return nil, ErrNoSource } var ( pcs []uint64 lastaddr uint64 sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) ) for { if err := sm.next(); err != nil { if lineInfo.Logf != nil { lineInfo.Logf("AllPCsBetween error: %v", err) } break } if !sm.valid { continue } if (sm.address > end) && (end >= sm.lastAddress) { break } if sm.address >= begin && sm.address <= end && sm.address > lastaddr && sm.isStmt && !sm.endSeq && ((sm.file != excludeFile) || (sm.line != excludeLine)) { lastaddr = sm.address pcs = append(pcs, sm.address) } } return pcs, nil } // copy returns a copy of this state machine, running the returned state // machine will not affect sm. func (sm *StateMachine) copy() *StateMachine { var r StateMachine r = *sm r.buf = bytes.NewBuffer(sm.buf.Bytes()) return &r } func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) { sm = lineInfo.stateMachineCache[basePC] if sm == nil { sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) sm.PCToLine(basePC) lineInfo.stateMachineCache[basePC] = sm } sm = sm.copy() return } // PCToLine returns the filename and line number associated with pc. // If pc isn't found inside lineInfo's table it will return the filename and // line number associated with the closest PC address preceding pc. // basePC will be used for caching, it's normally the entry point for the // function containing pc. func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) { if lineInfo == nil { return "", 0 } if basePC > pc { panic(fmt.Errorf("basePC after pc %#x %#x", basePC, pc)) } sm := lineInfo.stateMachineFor(basePC, pc) file, line, _ := sm.PCToLine(pc) return file, line } func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine { var sm *StateMachine if basePC == 0 { sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) } else { // Try to use the last state machine that we used for this function, if // there isn't one or it's already past pc try to clone the cached state // machine stopped at the entry point of the function. // As a last resort start from the start of the debug_line section. sm = lineInfo.lastMachineCache[basePC] if sm == nil || sm.lastAddress >= pc { sm = lineInfo.stateMachineForEntry(basePC) lineInfo.lastMachineCache[basePC] = sm } } return sm } func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { if !sm.started { if err := sm.next(); err != nil { if sm.dbl.Logf != nil { sm.dbl.Logf("PCToLine error: %v", err) } return "", 0, false } } if sm.lastAddress > pc && sm.lastAddress != ^uint64(0) { return "", 0, false } for { if sm.valid { if (sm.address > pc) && (pc >= sm.lastAddress) { return sm.lastFile, sm.lastLine, true } if sm.address == pc { return sm.file, sm.line, true } } if err := sm.next(); err != nil { if sm.dbl.Logf != nil { sm.dbl.Logf("PCToLine error: %v", err) } break } } if sm.valid { return sm.file, sm.line, true } return "", 0, false } // PCStmt is a PC address with its is_stmt flag type PCStmt struct { PC uint64 Stmt bool } // LineToPCs returns all PCs associated with filename:lineno func (lineInfo *DebugLineInfo) LineToPCs(filename string, lineno int) []PCStmt { if lineInfo == nil { return nil } sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) pcstmts := []PCStmt{} for { if err := sm.next(); err != nil { if lineInfo.Logf != nil && err != io.EOF { lineInfo.Logf("LineToPCs error: %v", err) } break } if sm.line == lineno && sm.file == filename && sm.valid { pcstmts = append(pcstmts, PCStmt{sm.address, sm.isStmt}) } } return pcstmts } // PrologueEndPC returns the first PC address marked as prologue_end in the half open interval [start, end) func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file string, line int, ok bool) { if lineInfo == nil { return 0, "", 0, false } sm := lineInfo.stateMachineForEntry(start) for { if sm.valid { if sm.address >= end { return 0, "", 0, false } if sm.prologueEnd { return sm.address, sm.file, sm.line, true } } if err := sm.next(); err != nil { if lineInfo.Logf != nil { lineInfo.Logf("PrologueEnd error: %v", err) } return 0, "", 0, false } } } // FirstStmtForLine looks in the half open interval [start, end) for the // first PC address marked as stmt for the line at address 'start'. func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, file string, line int, ok bool) { first := true sm := lineInfo.stateMachineForEntry(start) for { if sm.valid { if sm.address >= end { return 0, "", 0, false } if first { first = false file, line = sm.file, sm.line } if sm.isStmt && sm.file == file && sm.line == line { return sm.address, sm.file, sm.line, true } } if err := sm.next(); err != nil { if lineInfo.Logf != nil { lineInfo.Logf("FirstStmtForLine error: %v", err) } return 0, "", 0, false } } } func (lineInfo *DebugLineInfo) FirstFile() string { sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize) for { if sm.valid { return sm.file } if err := sm.next(); err != nil { if lineInfo.Logf != nil { lineInfo.Logf("FirstFile error: %v", err) } return "" } } } func (sm *StateMachine) next() error { sm.started = true if sm.valid { sm.lastAddress, sm.lastFile, sm.lastLine = sm.address, sm.file, sm.line // valid is set by either a special opcode or a DW_LNS_copy, in both cases // we need to reset basic_block, prologue_end and epilogue_begin sm.basicBlock = false sm.prologueEnd = false sm.epilogueBegin = false } if sm.endSeq { sm.endSeq = false sm.file = sm.dbl.FileNames[0].Path sm.line = 1 sm.column = 0 sm.isa = 0 sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1) sm.basicBlock = false sm.lastAddress = ^uint64(0) } b, err := sm.buf.ReadByte() if err != nil { return err } if b < sm.dbl.Prologue.OpcodeBase { if int(b) < len(sm.opcodes) { sm.valid = false sm.opcodes[b](sm, sm.buf) } else { // unimplemented standard opcode, read the number of arguments specified // in the prologue and do nothing with them opnum := sm.dbl.Prologue.StdOpLengths[b-1] for i := 0; i < int(opnum); i++ { util.DecodeSLEB128(sm.buf) } fmt.Printf("unknown opcode %d(0x%x), %d arguments, file %s, line %d, address 0x%x\n", b, b, opnum, sm.file, sm.line, sm.address) } } else { execSpecialOpcode(sm, b) } return nil } func execSpecialOpcode(sm *StateMachine, instr byte) { var ( opcode = uint8(instr) decoded = opcode - sm.dbl.Prologue.OpcodeBase ) sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange)) sm.line += sm.lastDelta sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength) sm.valid = true } func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) { _, _ = util.DecodeULEB128(buf) b, _ := buf.ReadByte() if fn, ok := extendedopcodes[b]; ok { fn(sm, buf) } } func copyfn(sm *StateMachine, buf *bytes.Buffer) { sm.valid = true } func advancepc(sm *StateMachine, buf *bytes.Buffer) { addr, _ := util.DecodeULEB128(buf) sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength) } func advanceline(sm *StateMachine, buf *bytes.Buffer) { line, _ := util.DecodeSLEB128(buf) sm.line += int(line) sm.lastDelta = int(line) } func setfile(sm *StateMachine, buf *bytes.Buffer) { i, _ := util.DecodeULEB128(buf) if sm.dbl.Prologue.Version < 5 { // in DWARF v5 files are indexed starting from 0, in v4 and prior the index starts at 1 i-- } if i < uint64(len(sm.dbl.FileNames)) { sm.file = sm.dbl.FileNames[i].Path } else { j := i - uint64(len(sm.dbl.FileNames)) if j < uint64(len(sm.definedFiles)) { sm.file = sm.definedFiles[j].Path } else { sm.file = "" } } } func setcolumn(sm *StateMachine, buf *bytes.Buffer) { c, _ := util.DecodeULEB128(buf) sm.column = uint(c) } func negatestmt(sm *StateMachine, buf *bytes.Buffer) { sm.isStmt = !sm.isStmt } func setbasicblock(sm *StateMachine, buf *bytes.Buffer) { sm.basicBlock = true } func constaddpc(sm *StateMachine, buf *bytes.Buffer) { sm.address += uint64((255-sm.dbl.Prologue.OpcodeBase)/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength) } func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) { var operand uint16 binary.Read(buf, binary.LittleEndian, &operand) sm.address += uint64(operand) } func endsequence(sm *StateMachine, buf *bytes.Buffer) { sm.endSeq = true sm.valid = sm.dbl.endSeqIsValid } func setaddress(sm *StateMachine, buf *bytes.Buffer) { addr, err := util.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize) if err != nil { panic(err) } sm.address = addr + sm.dbl.staticBase } func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) { _, _ = util.DecodeULEB128(buf) } func definefile(sm *StateMachine, buf *bytes.Buffer) { if entry := readFileEntry(sm.dbl, sm.buf, false); entry != nil { sm.definedFiles = append(sm.definedFiles, entry) } } func prologueend(sm *StateMachine, buf *bytes.Buffer) { sm.prologueEnd = true } func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) { sm.epilogueBegin = true } func setisa(sm *StateMachine, buf *bytes.Buffer) { c, _ := util.DecodeULEB128(buf) sm.isa = c }