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