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 }