2018-10-13 13:45:53 +00:00
package line
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
2019-04-15 14:43:01 +00:00
"github.com/go-delve/delve/pkg/dwarf/util"
2018-10-13 13:45:53 +00:00
)
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
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
}
type opcodeKind uint8
const (
specialOpcode opcodeKind = iota
standardOpcode
extendedOpcode
)
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
)
// 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 ,
DW_LNS_prologue_end : prologueend ,
DW_LNS_epilogue_begin : epiloguebegin ,
}
var extendedopcodes = map [ byte ] opcodefn {
DW_LINE_end_sequence : endsequence ,
DW_LINE_set_address : setaddress ,
DW_LINE_define_file : definefile ,
}
func newStateMachine ( dbl * DebugLineInfo , instructions [ ] byte ) * StateMachine {
opcodes := make ( [ ] opcodefn , len ( standardopcodes ) + 1 )
opcodes [ 0 ] = execExtendedOpcode
for op := range standardopcodes {
opcodes [ op ] = standardopcodes [ op ]
}
sm := & StateMachine { dbl : dbl , file : dbl . FileNames [ 0 ] . Path , line : 1 , buf : bytes . NewBuffer ( instructions ) , opcodes : opcodes , isStmt : dbl . Prologue . InitialIsStmt == uint8 ( 1 ) , address : dbl . staticBase }
return sm
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
func ( lineInfo * DebugLineInfo ) AllPCsForFileLine ( f string , l int ) ( pcs [ ] uint64 ) {
if lineInfo == nil {
return nil
}
var (
lastAddr uint64
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
)
for {
if err := sm . next ( ) ; err != nil {
if lineInfo . Logf != nil {
lineInfo . Logf ( "AllPCsForFileLine error: %v" , err )
}
break
}
if sm . line == l && sm . file == f && sm . address != lastAddr && sm . isStmt && sm . valid {
pcs = append ( pcs , sm . address )
lastAddr = sm . address
}
}
return
}
var NoSourceError = 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 , NoSourceError
}
var (
pcs [ ] uint64
lastaddr uint64
sm = newStateMachine ( lineInfo , lineInfo . Instructions )
)
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 {
break
}
if ( sm . address >= begin && sm . address > lastaddr ) && sm . isStmt && ( ( 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 )
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 ) )
}
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 {
sm = lineInfo . stateMachineForEntry ( basePC )
lineInfo . lastMachineCache [ basePC ] = sm
}
}
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 {
if sm . dbl . Logf != nil {
sm . dbl . Logf ( "PCToLine error: %v" , err )
}
return "" , 0 , false
}
}
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 {
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
}
// LineToPC returns the first PC address associated with filename:lineno.
func ( lineInfo * DebugLineInfo ) LineToPC ( filename string , lineno int ) uint64 {
if lineInfo == nil {
return 0
}
sm := newStateMachine ( lineInfo , lineInfo . Instructions )
// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
var fallbackPC uint64
for {
if err := sm . next ( ) ; err != nil {
if lineInfo . Logf != nil && err != io . EOF {
lineInfo . Logf ( "LineToPC error: %v" , err )
}
break
}
if sm . line == lineno && sm . file == filename && sm . valid {
if sm . isStmt {
return sm . address
} else if fallbackPC == 0 {
fallbackPC = sm . address
}
}
}
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 . Logf != nil {
lineInfo . Logf ( "PrologueEnd error: %v" , err )
}
return 0 , "" , 0 , false
}
}
}
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 . isStmt = sm . dbl . Prologue . InitialIsStmt == uint8 ( 1 )
sm . basicBlock = false
}
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\n" )
}
} 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 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 = ""
}
}
}
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 = true
}
func setaddress ( sm * StateMachine , buf * bytes . Buffer ) {
var addr uint64
binary . Read ( buf , binary . LittleEndian , & addr )
sm . address = addr + sm . dbl . staticBase
}
func definefile ( sm * StateMachine , buf * bytes . Buffer ) {
entry := readFileEntry ( sm . dbl , sm . buf , false )
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
}