gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/dwarf/line/state_machine.go (about)

     1  package line
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf"
    11  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf/leb128"
    12  )
    13  
    14  type Location struct {
    15  	File    string
    16  	Line    int
    17  	Address uint64
    18  	Delta   int
    19  }
    20  
    21  type StateMachine struct {
    22  	dbl           *DebugLineInfo
    23  	file          string
    24  	line          int
    25  	address       uint64
    26  	column        uint
    27  	isStmt        bool
    28  	isa           uint64 // instruction set architecture register (DWARFv4)
    29  	basicBlock    bool
    30  	endSeq        bool
    31  	lastDelta     int
    32  	prologueEnd   bool
    33  	epilogueBegin bool
    34  	// valid is true if the current value of the state machine is the address of
    35  	// an instruction (using the terminology used by DWARF spec the current
    36  	// value of the state machine should be appended to the matrix representing
    37  	// the compilation unit)
    38  	valid bool
    39  
    40  	started bool
    41  
    42  	buf     *bytes.Buffer // remaining instructions
    43  	opcodes []opcodefn
    44  
    45  	definedFiles []*FileEntry // files defined with DW_LINE_define_file
    46  
    47  	lastAddress uint64
    48  	lastFile    string
    49  	lastLine    int
    50  	ptrSize     int
    51  }
    52  
    53  type opcodefn func(*StateMachine, *bytes.Buffer)
    54  
    55  // Special opcodes
    56  const (
    57  	DW_LNS_copy             = 1
    58  	DW_LNS_advance_pc       = 2
    59  	DW_LNS_advance_line     = 3
    60  	DW_LNS_set_file         = 4
    61  	DW_LNS_set_column       = 5
    62  	DW_LNS_negate_stmt      = 6
    63  	DW_LNS_set_basic_block  = 7
    64  	DW_LNS_const_add_pc     = 8
    65  	DW_LNS_fixed_advance_pc = 9
    66  	DW_LNS_prologue_end     = 10
    67  	DW_LNS_epilogue_begin   = 11
    68  	DW_LNS_set_isa          = 12
    69  )
    70  
    71  // Extended opcodes
    72  const (
    73  	DW_LINE_end_sequence      = 1
    74  	DW_LINE_set_address       = 2
    75  	DW_LINE_define_file       = 3
    76  	DW_LINE_set_discriminator = 4
    77  )
    78  
    79  var standardopcodes = map[byte]opcodefn{
    80  	DW_LNS_copy:             copyfn,
    81  	DW_LNS_advance_pc:       advancepc,
    82  	DW_LNS_advance_line:     advanceline,
    83  	DW_LNS_set_file:         setfile,
    84  	DW_LNS_set_column:       setcolumn,
    85  	DW_LNS_negate_stmt:      negatestmt,
    86  	DW_LNS_set_basic_block:  setbasicblock,
    87  	DW_LNS_const_add_pc:     constaddpc,
    88  	DW_LNS_fixed_advance_pc: fixedadvancepc,
    89  	DW_LNS_prologue_end:     prologueend,
    90  	DW_LNS_epilogue_begin:   epiloguebegin,
    91  	DW_LNS_set_isa:          setisa,
    92  }
    93  
    94  var extendedopcodes = map[byte]opcodefn{
    95  	DW_LINE_end_sequence:      endsequence,
    96  	DW_LINE_set_address:       setaddress,
    97  	DW_LINE_define_file:       definefile,
    98  	DW_LINE_set_discriminator: setdiscriminator,
    99  }
   100  
   101  func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *StateMachine {
   102  	opcodes := make([]opcodefn, len(standardopcodes)+1)
   103  	opcodes[0] = execExtendedOpcode
   104  	for op := range standardopcodes {
   105  		opcodes[op] = standardopcodes[op]
   106  	}
   107  	var file string
   108  	if len(dbl.FileNames) > 0 {
   109  		file = dbl.FileNames[0].Path
   110  	}
   111  	dbl.endSeqIsValid = true
   112  	sm := &StateMachine{
   113  		dbl:         dbl,
   114  		file:        file,
   115  		line:        1,
   116  		buf:         bytes.NewBuffer(instructions),
   117  		opcodes:     opcodes,
   118  		isStmt:      dbl.Prologue.InitialIsStmt == uint8(1),
   119  		address:     dbl.staticBase,
   120  		lastAddress: ^uint64(0),
   121  		ptrSize:     ptrSize,
   122  	}
   123  	return sm
   124  }
   125  
   126  // AllPCsForFileLines Adds all PCs for a given file and set (domain of map) of lines
   127  // to the map value corresponding to each line.
   128  func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64) {
   129  	if lineInfo == nil {
   130  		return
   131  	}
   132  
   133  	var (
   134  		lastAddr uint64
   135  		sm       = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   136  	)
   137  
   138  	for {
   139  		if err := sm.next(); err != nil {
   140  			if lineInfo.Logf != nil {
   141  				lineInfo.Logf("AllPCsForFileLine error: %v", err)
   142  			}
   143  			break
   144  		}
   145  		if sm.address != lastAddr && sm.isStmt && sm.valid && sm.file == f {
   146  			if pcs, ok := m[sm.line]; ok {
   147  				pcs = append(pcs, sm.address)
   148  				m[sm.line] = pcs
   149  				lastAddr = sm.address
   150  			}
   151  		}
   152  	}
   153  }
   154  
   155  var ErrNoSource = errors.New("no source available")
   156  
   157  // AllPCsBetween returns all PC addresses between begin and end (including both begin and end)
   158  // that have the is_stmt flag set and do not belong to excludeFile:excludeLine.
   159  func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile string, excludeLine int) ([]uint64, error) {
   160  	if lineInfo == nil {
   161  		return nil, ErrNoSource
   162  	}
   163  
   164  	var (
   165  		pcs      []uint64
   166  		lastaddr uint64
   167  		sm       = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   168  	)
   169  
   170  	for {
   171  		if err := sm.next(); err != nil {
   172  			if lineInfo.Logf != nil {
   173  				lineInfo.Logf("AllPCsBetween error: %v", err)
   174  			}
   175  			break
   176  		}
   177  		if !sm.valid {
   178  			continue
   179  		}
   180  		if (sm.address > end) && (end >= sm.lastAddress) {
   181  			break
   182  		}
   183  		if sm.address >= begin && sm.address <= end && sm.address > lastaddr && sm.isStmt && !sm.endSeq && ((sm.file != excludeFile) || (sm.line != excludeLine)) {
   184  			lastaddr = sm.address
   185  			pcs = append(pcs, sm.address)
   186  		}
   187  	}
   188  	return pcs, nil
   189  }
   190  
   191  // copy returns a copy of this state machine, running the returned state
   192  // machine will not affect sm.
   193  func (sm *StateMachine) copy() *StateMachine {
   194  	var r StateMachine
   195  	r = *sm
   196  	r.buf = bytes.NewBuffer(sm.buf.Bytes())
   197  	return &r
   198  }
   199  
   200  func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
   201  	sm = lineInfo.stateMachineCache[basePC]
   202  	if sm == nil {
   203  		sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   204  		sm.PCToLine(basePC)
   205  		lineInfo.stateMachineCache[basePC] = sm
   206  	}
   207  	sm = sm.copy()
   208  	return
   209  }
   210  
   211  // PCToLine returns the filename and line number associated with pc.
   212  // If pc isn't found inside lineInfo's table it will return the filename and
   213  // line number associated with the closest PC address preceding pc.
   214  // basePC will be used for caching, it's normally the entry point for the
   215  // function containing pc.
   216  func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
   217  	if lineInfo == nil {
   218  		return "", 0
   219  	}
   220  	if basePC > pc {
   221  		panic(fmt.Errorf("basePC after pc %#x %#x", basePC, pc))
   222  	}
   223  
   224  	sm := lineInfo.stateMachineFor(basePC, pc)
   225  
   226  	file, line, _ := sm.PCToLine(pc)
   227  	return file, line
   228  }
   229  
   230  func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine {
   231  	var sm *StateMachine
   232  	if basePC == 0 {
   233  		sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   234  	} else {
   235  		// Try to use the last state machine that we used for this function, if
   236  		// there isn't one, or it's already past pc try to clone the cached state
   237  		// machine stopped at the entry point of the function.
   238  		// As a last resort start from the start of the debug_line section.
   239  		sm = lineInfo.lastMachineCache[basePC]
   240  		if sm == nil || sm.lastAddress >= pc {
   241  			sm = lineInfo.stateMachineForEntry(basePC)
   242  			lineInfo.lastMachineCache[basePC] = sm
   243  		}
   244  	}
   245  	return sm
   246  }
   247  
   248  func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
   249  	if !sm.started {
   250  		if err := sm.next(); err != nil {
   251  			if sm.dbl.Logf != nil {
   252  				sm.dbl.Logf("PCToLine error: %v", err)
   253  			}
   254  			return "", 0, false
   255  		}
   256  	}
   257  	if sm.lastAddress > pc && sm.lastAddress != ^uint64(0) {
   258  		return "", 0, false
   259  	}
   260  	for {
   261  		if sm.valid {
   262  			if (sm.address > pc) && (pc >= sm.lastAddress) {
   263  				return sm.lastFile, sm.lastLine, true
   264  			}
   265  			if sm.address == pc && !sm.endSeq {
   266  				return sm.file, sm.line, true
   267  			}
   268  		}
   269  		if err := sm.next(); err != nil {
   270  			if sm.dbl.Logf != nil {
   271  				sm.dbl.Logf("PCToLine error: %v", err)
   272  			}
   273  			break
   274  		}
   275  	}
   276  	if sm.valid && !sm.endSeq {
   277  		return sm.file, sm.line, true
   278  	}
   279  	return "", 0, false
   280  }
   281  
   282  // PCStmt is a PC address with its is_stmt flag
   283  type PCStmt struct {
   284  	PC   uint64
   285  	Stmt bool
   286  }
   287  
   288  // LineToPCs returns all PCs associated with filename:lineno
   289  func (lineInfo *DebugLineInfo) LineToPCs(filename string, lineno int) []PCStmt {
   290  	if lineInfo == nil {
   291  		return nil
   292  	}
   293  
   294  	sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   295  
   296  	pcstmts := []PCStmt{}
   297  
   298  	for {
   299  		if err := sm.next(); err != nil {
   300  			if lineInfo.Logf != nil && err != io.EOF {
   301  				lineInfo.Logf("LineToPCs error: %v", err)
   302  			}
   303  			break
   304  		}
   305  		if sm.line == lineno && sm.file == filename && sm.valid && !sm.endSeq {
   306  			pcstmts = append(pcstmts, PCStmt{sm.address, sm.isStmt})
   307  		}
   308  	}
   309  
   310  	return pcstmts
   311  }
   312  
   313  // PrologueEndPC returns the first PC address marked as prologue_end in the half open interval [start, end)
   314  func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file string, line int, ok bool) {
   315  	if lineInfo == nil {
   316  		return 0, "", 0, false
   317  	}
   318  
   319  	sm := lineInfo.stateMachineForEntry(start)
   320  	for {
   321  		if sm.valid {
   322  			if sm.address >= end {
   323  				return 0, "", 0, false
   324  			}
   325  			if sm.prologueEnd {
   326  				return sm.address, sm.file, sm.line, true
   327  			}
   328  		}
   329  		if err := sm.next(); err != nil {
   330  			if lineInfo.Logf != nil {
   331  				lineInfo.Logf("PrologueEnd error: %v", err)
   332  			}
   333  			return 0, "", 0, false
   334  		}
   335  	}
   336  }
   337  
   338  // FirstStmtForLine looks in the half open interval [start, end) for the
   339  // first PC address marked as stmt for the line at address 'start'.
   340  func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, file string, line int, ok bool) {
   341  	first := true
   342  	sm := lineInfo.stateMachineForEntry(start)
   343  	for {
   344  		if sm.valid {
   345  			if sm.address >= end {
   346  				return 0, "", 0, false
   347  			}
   348  			if first {
   349  				first = false
   350  				file, line = sm.file, sm.line
   351  			}
   352  			if sm.isStmt && sm.file == file && sm.line == line {
   353  				return sm.address, sm.file, sm.line, true
   354  			}
   355  		}
   356  		if err := sm.next(); err != nil {
   357  			if lineInfo.Logf != nil {
   358  				lineInfo.Logf("FirstStmtForLine error: %v", err)
   359  			}
   360  			return 0, "", 0, false
   361  		}
   362  	}
   363  }
   364  
   365  func (lineInfo *DebugLineInfo) FirstFile() string {
   366  	sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
   367  	for {
   368  		if sm.valid {
   369  			return sm.file
   370  		}
   371  		if err := sm.next(); err != nil {
   372  			if lineInfo.Logf != nil {
   373  				lineInfo.Logf("FirstFile error: %v", err)
   374  			}
   375  			return ""
   376  		}
   377  	}
   378  }
   379  
   380  func (sm *StateMachine) next() error {
   381  	sm.started = true
   382  	if sm.valid {
   383  		sm.lastAddress, sm.lastFile, sm.lastLine = sm.address, sm.file, sm.line
   384  
   385  		// valid is set by either a special opcode or a DW_LNS_copy, in both cases
   386  		// we need to reset basic_block, prologue_end and epilogue_begin
   387  		sm.basicBlock = false
   388  		sm.prologueEnd = false
   389  		sm.epilogueBegin = false
   390  	}
   391  	if sm.endSeq {
   392  		sm.endSeq = false
   393  		sm.file = sm.dbl.FileNames[0].Path
   394  		sm.line = 1
   395  		sm.column = 0
   396  		sm.isa = 0
   397  		sm.isStmt = sm.dbl.Prologue.InitialIsStmt == uint8(1)
   398  		sm.basicBlock = false
   399  		sm.lastAddress = ^uint64(0)
   400  	}
   401  	b, err := sm.buf.ReadByte()
   402  	if err != nil {
   403  		return err
   404  	}
   405  	if b < sm.dbl.Prologue.OpcodeBase {
   406  		if int(b) < len(sm.opcodes) {
   407  			sm.valid = false
   408  			sm.opcodes[b](sm, sm.buf)
   409  		} else {
   410  			// unimplemented standard opcode, read the number of arguments specified
   411  			// in the prologue and do nothing with them
   412  			opnum := sm.dbl.Prologue.StdOpLengths[b-1]
   413  			for i := 0; i < int(opnum); i++ {
   414  				leb128.DecodeSigned(sm.buf)
   415  			}
   416  			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)
   417  		}
   418  	} else {
   419  		execSpecialOpcode(sm, b)
   420  	}
   421  	return nil
   422  }
   423  
   424  func execSpecialOpcode(sm *StateMachine, instr byte) {
   425  	var (
   426  		opcode  = instr
   427  		decoded = opcode - sm.dbl.Prologue.OpcodeBase
   428  	)
   429  
   430  	sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange))
   431  	sm.line += sm.lastDelta
   432  	sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
   433  	sm.valid = true
   434  }
   435  
   436  func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) {
   437  	_, _ = leb128.DecodeUnsigned(buf)
   438  	b, _ := buf.ReadByte()
   439  	if fn, ok := extendedopcodes[b]; ok {
   440  		fn(sm, buf)
   441  	}
   442  }
   443  
   444  func copyfn(sm *StateMachine, buf *bytes.Buffer) {
   445  	sm.valid = true
   446  }
   447  
   448  func advancepc(sm *StateMachine, buf *bytes.Buffer) {
   449  	addr, _ := leb128.DecodeUnsigned(buf)
   450  	sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength)
   451  }
   452  
   453  func advanceline(sm *StateMachine, buf *bytes.Buffer) {
   454  	line, _ := leb128.DecodeSigned(buf)
   455  	sm.line += int(line)
   456  	sm.lastDelta = int(line)
   457  }
   458  
   459  func setfile(sm *StateMachine, buf *bytes.Buffer) {
   460  	i, _ := leb128.DecodeUnsigned(buf)
   461  	if sm.dbl.Prologue.Version < 5 {
   462  		// in DWARF v5 files are indexed starting from 0, in v4 and prior the index starts at 1
   463  		i--
   464  	}
   465  	if i < uint64(len(sm.dbl.FileNames)) {
   466  		sm.file = sm.dbl.FileNames[i].Path
   467  	} else {
   468  		j := i - uint64(len(sm.dbl.FileNames))
   469  		if j < uint64(len(sm.definedFiles)) {
   470  			sm.file = sm.definedFiles[j].Path
   471  		} else {
   472  			sm.file = ""
   473  		}
   474  	}
   475  }
   476  
   477  func setcolumn(sm *StateMachine, buf *bytes.Buffer) {
   478  	c, _ := leb128.DecodeUnsigned(buf)
   479  	sm.column = uint(c)
   480  }
   481  
   482  func negatestmt(sm *StateMachine, buf *bytes.Buffer) {
   483  	sm.isStmt = !sm.isStmt
   484  }
   485  
   486  func setbasicblock(sm *StateMachine, buf *bytes.Buffer) {
   487  	sm.basicBlock = true
   488  }
   489  
   490  func constaddpc(sm *StateMachine, buf *bytes.Buffer) {
   491  	sm.address += uint64((255-sm.dbl.Prologue.OpcodeBase)/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength)
   492  }
   493  
   494  func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) {
   495  	var operand uint16
   496  	binary.Read(buf, binary.LittleEndian, &operand)
   497  
   498  	sm.address += uint64(operand)
   499  }
   500  
   501  func endsequence(sm *StateMachine, buf *bytes.Buffer) {
   502  	sm.endSeq = true
   503  	sm.valid = sm.dbl.endSeqIsValid
   504  }
   505  
   506  func setaddress(sm *StateMachine, buf *bytes.Buffer) {
   507  	addr, err := dwarf.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize)
   508  	if err != nil {
   509  		panic(err)
   510  	}
   511  	sm.address = addr + sm.dbl.staticBase
   512  }
   513  
   514  func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) {
   515  	_, _ = leb128.DecodeUnsigned(buf)
   516  }
   517  
   518  func definefile(sm *StateMachine, buf *bytes.Buffer) {
   519  	if entry := readFileEntry(sm.dbl, sm.buf, false); entry != nil {
   520  		sm.definedFiles = append(sm.definedFiles, entry)
   521  	}
   522  }
   523  
   524  func prologueend(sm *StateMachine, buf *bytes.Buffer) {
   525  	sm.prologueEnd = true
   526  }
   527  
   528  func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) {
   529  	sm.epilogueBegin = true
   530  }
   531  
   532  func setisa(sm *StateMachine, buf *bytes.Buffer) {
   533  	c, _ := leb128.DecodeUnsigned(buf)
   534  	sm.isa = c
   535  }