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 }