2015-03-28 01:12:07 +00:00
package line
import (
"bytes"
"encoding/binary"
2017-01-09 23:21:54 +00:00
"errors"
2015-03-28 01:12:07 +00:00
"fmt"
2018-03-25 08:04:32 +00:00
"log"
2015-03-28 01:12:07 +00:00
2017-02-08 16:00:44 +00:00
"github.com/derekparker/delve/pkg/dwarf/util"
2015-03-28 01:12:07 +00:00
)
type Location struct {
File string
Line int
Address uint64
Delta int
}
type StateMachine struct {
2018-03-25 08:04:32 +00:00
dbl * DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastDelta int
prologueEnd bool
epilogueBegin bool
2017-02-07 21:07:18 +00:00
// 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
2017-08-18 17:49:29 +00:00
2018-03-25 08:04:32 +00:00
lastOpcodeKind opcodeKind
2017-08-18 17:49:29 +00:00
started bool
buf * bytes . Buffer // remaining instructions
opcodes [ ] opcodefn
2017-08-19 11:06:20 +00:00
definedFiles [ ] * FileEntry // files defined with DW_LINE_define_file
2017-08-18 17:49:29 +00:00
lastAddress uint64
lastFile string
lastLine int
2015-03-28 01:12:07 +00:00
}
2018-03-25 08:04:32 +00:00
type opcodeKind uint8
const (
specialOpcode opcodeKind = iota
standardOpcode
extendedOpcode
)
2015-03-28 01:12:07 +00:00
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
2018-03-25 08:04:32 +00:00
DW_LNS_prologue_end = 10
DW_LNS_epilogue_begin = 11
2015-03-28 01:12:07 +00:00
)
// Extended opcodes
const (
DW_LINE_end_sequence = 1
DW_LINE_set_address = 2
DW_LINE_define_file = 3
)
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 ,
2018-03-25 08:04:32 +00:00
DW_LNS_prologue_end : prologueend ,
DW_LNS_epilogue_begin : epiloguebegin ,
2015-03-28 01:12:07 +00:00
}
var extendedopcodes = map [ byte ] opcodefn {
DW_LINE_end_sequence : endsequence ,
DW_LINE_set_address : setaddress ,
DW_LINE_define_file : definefile ,
}
2017-08-18 17:49:29 +00:00
func newStateMachine ( dbl * DebugLineInfo , instructions [ ] byte ) * StateMachine {
opcodes := make ( [ ] opcodefn , len ( standardopcodes ) + 1 )
opcodes [ 0 ] = execExtendedOpcode
for op := range standardopcodes {
opcodes [ op ] = standardopcodes [ op ]
}
2018-03-25 08:04:32 +00:00
sm := & StateMachine { dbl : dbl , file : dbl . FileNames [ 0 ] . Path , line : 1 , buf : bytes . NewBuffer ( instructions ) , opcodes : opcodes , isStmt : dbl . Prologue . InitialIsStmt == uint8 ( 1 ) }
return sm
2015-03-28 01:12:07 +00:00
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
2017-09-01 13:30:45 +00:00
func ( lineInfo * DebugLineInfo ) AllPCsForFileLine ( f string , l int ) ( pcs [ ] uint64 ) {
if lineInfo == nil {
return nil
}
2015-03-28 01:12:07 +00:00
var (
2018-03-25 08:04:32 +00:00
lastAddr uint64
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
2015-03-28 01:12:07 +00:00
)
2017-08-18 17:49:29 +00:00
for {
if err := sm . next ( ) ; err != nil {
2018-03-25 08:04:32 +00:00
if lineInfo . logSuppressedErrors {
log . Printf ( "AllPCsForFileLine error: %v" , err )
}
2017-08-18 17:49:29 +00:00
break
}
2018-03-25 08:04:32 +00:00
if sm . line == l && sm . file == f && sm . address != lastAddr && sm . isStmt && sm . valid {
pcs = append ( pcs , sm . address )
lastAddr = sm . address
2015-03-28 01:12:07 +00:00
}
}
return
}
2017-01-09 23:21:54 +00:00
var NoSourceError = errors . New ( "no source available" )
2018-03-25 08:04:32 +00:00
// 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 ) {
2017-01-09 23:21:54 +00:00
if lineInfo == nil {
2017-08-22 14:40:01 +00:00
return nil , NoSourceError
2017-01-09 23:21:54 +00:00
}
2017-09-01 13:30:45 +00:00
2015-04-20 18:03:22 +00:00
var (
2015-10-22 17:07:24 +00:00
pcs [ ] uint64
lastaddr uint64
2017-08-18 17:49:29 +00:00
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
2015-04-20 18:03:22 +00:00
)
2017-08-18 17:49:29 +00:00
for {
if err := sm . next ( ) ; err != nil {
2018-03-25 08:04:32 +00:00
if lineInfo . logSuppressedErrors {
log . Printf ( "AllPCsBetween error: %v" , err )
}
2017-08-18 17:49:29 +00:00
break
}
2017-02-07 21:07:18 +00:00
if ! sm . valid {
continue
}
2015-04-20 18:03:22 +00:00
if sm . address > end {
break
}
2018-03-25 08:04:32 +00:00
if ( sm . address >= begin && sm . address > lastaddr ) && sm . isStmt && ( ( sm . file != excludeFile ) || ( sm . line != excludeLine ) ) {
2015-10-22 17:07:24 +00:00
lastaddr = sm . address
2015-04-20 18:03:22 +00:00
pcs = append ( pcs , sm . address )
}
}
2017-01-09 23:21:54 +00:00
return pcs , nil
2015-04-20 18:03:22 +00:00
}
2017-08-18 17:49:29 +00:00
// 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
}
2018-03-25 08:04:32 +00:00
func ( lineInfo * DebugLineInfo ) stateMachineForEntry ( basePC uint64 ) ( sm * StateMachine ) {
sm = lineInfo . stateMachineCache [ basePC ]
if sm == nil {
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
sm . PCToLine ( basePC )
lineInfo . stateMachineCache [ basePC ] = sm
}
sm = sm . copy ( )
return
}
2017-09-01 13:30:45 +00:00
// 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.
2017-08-18 17:49:29 +00:00
// 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 ) {
2017-09-01 13:30:45 +00:00
if lineInfo == nil {
return "" , 0
}
2017-08-18 17:49:29 +00:00
if basePC > pc {
panic ( fmt . Errorf ( "basePC after pc %#x %#x" , basePC , pc ) )
}
2017-09-01 13:30:45 +00:00
2017-08-18 17:49:29 +00:00
var sm * StateMachine
if basePC == 0 {
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
} 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 {
2018-03-25 08:04:32 +00:00
sm = lineInfo . stateMachineForEntry ( basePC )
2017-08-18 17:49:29 +00:00
lineInfo . lastMachineCache [ basePC ] = sm
2017-09-01 13:30:45 +00:00
}
2017-08-18 17:49:29 +00:00
}
file , line , _ := sm . PCToLine ( pc )
return file , line
}
func ( sm * StateMachine ) PCToLine ( pc uint64 ) ( string , int , bool ) {
if ! sm . started {
if err := sm . next ( ) ; err != nil {
2018-03-25 08:04:32 +00:00
if sm . dbl . logSuppressedErrors {
log . Printf ( "PCToLine error: %v" , err )
}
2017-08-18 17:49:29 +00:00
return "" , 0 , false
2017-09-01 13:30:45 +00:00
}
2017-08-18 17:49:29 +00:00
}
if sm . lastAddress > pc {
return "" , 0 , false
}
for {
if sm . valid {
if sm . address > pc {
return sm . lastFile , sm . lastLine , true
}
if sm . address == pc {
return sm . file , sm . line , true
}
}
if err := sm . next ( ) ; err != nil {
2018-03-25 08:04:32 +00:00
if sm . dbl . logSuppressedErrors {
log . Printf ( "PCToLine error: %v" , err )
}
2017-08-18 17:49:29 +00:00
break
2017-09-01 13:30:45 +00:00
}
}
2017-11-28 14:42:50 +00:00
if sm . valid {
return sm . file , sm . line , true
}
2017-08-18 17:49:29 +00:00
return "" , 0 , false
2017-09-01 13:30:45 +00:00
}
// LineToPC returns the first PC address associated with filename:lineno.
func ( lineInfo * DebugLineInfo ) LineToPC ( filename string , lineno int ) uint64 {
if lineInfo == nil {
return 0
}
var (
foundFile bool
2017-08-18 17:49:29 +00:00
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
2017-09-01 13:30:45 +00:00
)
2018-03-25 08:04:32 +00:00
// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
var fallbackPC uint64
2017-08-18 17:49:29 +00:00
for {
if err := sm . next ( ) ; err != nil {
2018-03-25 08:04:32 +00:00
if lineInfo . logSuppressedErrors {
log . Printf ( "LineToPC error: %v" , err )
}
2017-08-18 17:49:29 +00:00
break
}
2017-09-01 13:30:45 +00:00
if foundFile && sm . file != filename {
break
}
if sm . line == lineno && sm . file == filename {
foundFile = true
if sm . valid {
2018-03-25 08:04:32 +00:00
if sm . isStmt {
return sm . address
} else if fallbackPC == 0 {
fallbackPC = sm . address
}
2017-09-01 13:30:45 +00:00
}
}
}
2018-03-25 08:04:32 +00:00
return fallbackPC
}
// 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 ) {
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 . logSuppressedErrors {
log . Printf ( "PrologueEnd error: %v" , err )
}
return 0 , "" , 0 , false
}
}
2017-09-01 13:30:45 +00:00
}
2017-08-18 17:49:29 +00:00
func ( sm * StateMachine ) next ( ) error {
sm . started = true
if sm . valid {
sm . lastAddress , sm . lastFile , sm . lastLine = sm . address , sm . file , sm . line
}
2017-12-04 10:03:17 +00:00
if sm . endSeq {
sm . endSeq = false
sm . file = sm . dbl . FileNames [ 0 ] . Path
sm . line = 1
sm . column = 0
sm . isStmt = false
sm . basicBlock = false
}
2018-03-25 08:04:32 +00:00
if sm . lastOpcodeKind == specialOpcode {
sm . basicBlock = false
sm . prologueEnd = false
sm . epilogueBegin = false
}
2017-08-18 17:49:29 +00:00
b , err := sm . buf . ReadByte ( )
if err != nil {
return err
}
if int ( b ) < len ( sm . opcodes ) {
2018-03-25 08:04:32 +00:00
if b == 0 {
sm . lastOpcodeKind = extendedOpcode
} else {
sm . lastOpcodeKind = standardOpcode
}
2017-08-18 17:49:29 +00:00
sm . valid = false
sm . opcodes [ b ] ( sm , sm . buf )
} else if b < sm . dbl . Prologue . OpcodeBase {
// 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 )
}
} else {
2015-03-28 01:12:07 +00:00
execSpecialOpcode ( sm , b )
}
2017-08-18 17:49:29 +00:00
return nil
2015-03-28 01:12:07 +00:00
}
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
2017-02-07 21:07:18 +00:00
sm . address += uint64 ( decoded / sm . dbl . Prologue . LineRange ) * uint64 ( sm . dbl . Prologue . MinInstrLength )
2015-03-28 01:12:07 +00:00
sm . basicBlock = false
2018-03-25 08:04:32 +00:00
sm . lastOpcodeKind = specialOpcode
2017-02-07 21:07:18 +00:00
sm . valid = true
2015-03-28 01:12:07 +00:00
}
2017-08-18 17:49:29 +00:00
func execExtendedOpcode ( sm * StateMachine , buf * bytes . Buffer ) {
2015-03-28 01:12:07 +00:00
_ , _ = util . DecodeULEB128 ( buf )
b , _ := buf . ReadByte ( )
2017-08-18 17:49:29 +00:00
if fn , ok := extendedopcodes [ b ] ; ok {
fn ( sm , buf )
2015-03-28 01:12:07 +00:00
}
}
func copyfn ( sm * StateMachine , buf * bytes . Buffer ) {
sm . basicBlock = false
2017-02-07 21:07:18 +00:00
sm . valid = true
2015-03-28 01:12:07 +00:00
}
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 )
2017-08-19 11:06:20 +00:00
if i - 1 < uint64 ( len ( sm . dbl . FileNames ) ) {
sm . file = sm . dbl . FileNames [ i - 1 ] . Path
} else {
j := ( i - 1 ) - uint64 ( len ( sm . dbl . FileNames ) )
if j < uint64 ( len ( sm . definedFiles ) ) {
sm . file = sm . definedFiles [ j ] . Path
} else {
sm . file = ""
}
}
2015-03-28 01:12:07 +00:00
}
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 ) {
2017-02-07 21:07:18 +00:00
sm . address += uint64 ( ( 255 - sm . dbl . Prologue . OpcodeBase ) / sm . dbl . Prologue . LineRange ) * uint64 ( sm . dbl . Prologue . MinInstrLength )
2015-03-28 01:12:07 +00:00
}
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
2017-02-07 21:07:18 +00:00
sm . valid = true
2015-03-28 01:12:07 +00:00
}
func setaddress ( sm * StateMachine , buf * bytes . Buffer ) {
var addr uint64
binary . Read ( buf , binary . LittleEndian , & addr )
sm . address = addr
}
func definefile ( sm * StateMachine , buf * bytes . Buffer ) {
2017-08-19 11:06:20 +00:00
entry := readFileEntry ( sm . dbl , sm . buf , false )
sm . definedFiles = append ( sm . definedFiles , entry )
2015-03-28 01:12:07 +00:00
}
2018-03-25 08:04:32 +00:00
func prologueend ( sm * StateMachine , buf * bytes . Buffer ) {
sm . prologueEnd = true
}
func epiloguebegin ( sm * StateMachine , buf * bytes . Buffer ) {
sm . epilogueBegin = true
}