|
26 | 26 | #include "lldb/Symbol/Function.h" |
27 | 27 | #include "lldb/Symbol/Symbol.h" |
28 | 28 | #include "lldb/Symbol/SymbolContext.h" |
| 29 | +#include "lldb/Symbol/Variable.h" |
| 30 | +#include "lldb/Symbol/VariableList.h" |
| 31 | +#include "lldb/Target/ABI.h" |
29 | 32 | #include "lldb/Target/ExecutionContext.h" |
| 33 | +#include "lldb/Target/Process.h" |
30 | 34 | #include "lldb/Target/SectionLoadList.h" |
31 | 35 | #include "lldb/Target/StackFrame.h" |
32 | 36 | #include "lldb/Target/Target.h" |
|
41 | 45 | #include "lldb/lldb-private-enumerations.h" |
42 | 46 | #include "lldb/lldb-private-interfaces.h" |
43 | 47 | #include "lldb/lldb-private-types.h" |
| 48 | +#include "llvm/ADT/DenseMap.h" |
| 49 | +#include "llvm/ADT/StringRef.h" |
44 | 50 | #include "llvm/Support/Compiler.h" |
45 | 51 | #include "llvm/TargetParser/Triple.h" |
46 | 52 |
|
@@ -376,6 +382,147 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, |
376 | 382 | } |
377 | 383 | } |
378 | 384 |
|
| 385 | + // Add variable location annotations to the disassembly output. |
| 386 | + // |
| 387 | + // For each instruction, this block attempts to resolve in-scope variables |
| 388 | + // and determine if the current PC falls within their |
| 389 | + // DWARF location entry. If so, it prints a simplified annotation using the |
| 390 | + // variable name and its resolved location (e.g., "var = reg; " ). |
| 391 | + // |
| 392 | + // Annotations are only included if the variable has a valid DWARF location |
| 393 | + // entry, and the location string is non-empty after filtering. Decoding |
| 394 | + // errors and DWARF opcodes are intentionally omitted to keep the output |
| 395 | + // concise and user-friendly. |
| 396 | + // |
| 397 | + // The goal is to give users helpful live variable hints alongside the |
| 398 | + // disassembled instruction stream, similar to how debug information |
| 399 | + // enhances source-level debugging. |
| 400 | + |
| 401 | + struct VarState { |
| 402 | + std::string name; ///< Display name. |
| 403 | + std::string last_loc; ///< Last printed location (empty means <undef>). |
| 404 | + bool seen_this_inst = false; |
| 405 | + }; |
| 406 | + |
| 407 | + // Track live variables across instructions. |
| 408 | + llvm::DenseMap<lldb::user_id_t, VarState> live_vars; |
| 409 | + |
| 410 | + // Stateful annotator: updates live_vars and returns only what should be |
| 411 | + // printed for THIS instruction. |
| 412 | + auto annotate_static = [&](Instruction &inst, Target &target, |
| 413 | + ModuleSP module_sp) -> std::vector<std::string> { |
| 414 | + std::vector<std::string> events; |
| 415 | + |
| 416 | + // Reset per-instruction seen flags. |
| 417 | + for (auto &kv : live_vars) |
| 418 | + kv.second.seen_this_inst = false; |
| 419 | + |
| 420 | + const Address &iaddr = inst.GetAddress(); |
| 421 | + if (!module_sp) { |
| 422 | + // Everything previously live becomes <undef>. |
| 423 | + for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { |
| 424 | + auto Cur = I++; |
| 425 | + events.push_back( |
| 426 | + llvm::formatv("{0} = <undef>", Cur->second.name).str()); |
| 427 | + live_vars.erase(Cur); |
| 428 | + } |
| 429 | + return events; |
| 430 | + } |
| 431 | + |
| 432 | + // Resolve innermost block at this *file* address. |
| 433 | + SymbolContext sc; |
| 434 | + const lldb::SymbolContextItem mask = |
| 435 | + eSymbolContextFunction | eSymbolContextBlock; |
| 436 | + if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) || |
| 437 | + !sc.function) { |
| 438 | + // No function context: everything dies here. |
| 439 | + for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { |
| 440 | + auto Cur = I++; |
| 441 | + events.push_back( |
| 442 | + llvm::formatv("{0} = <undef>", Cur->second.name).str()); |
| 443 | + live_vars.erase(Cur); |
| 444 | + } |
| 445 | + return events; |
| 446 | + } |
| 447 | + |
| 448 | + Block *B = sc.block; ///< Innermost block containing iaddr. |
| 449 | + VariableList var_list; |
| 450 | + if (B) { |
| 451 | + auto filter = [](Variable *v) -> bool { return v && !v->IsArtificial(); }; |
| 452 | + |
| 453 | + B->AppendVariables(/*can_create*/ true, |
| 454 | + /*get_parent_variables*/ true, |
| 455 | + /*stop_if_block_is_inlined_function*/ false, |
| 456 | + /*filter*/ filter, |
| 457 | + /*variable_list*/ &var_list); |
| 458 | + } |
| 459 | + |
| 460 | + const lldb::addr_t pc_file = iaddr.GetFileAddress(); |
| 461 | + const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress(); |
| 462 | + |
| 463 | + // ABI from Target (pretty reg names if plugin exists). Safe to be null. |
| 464 | + lldb::ProcessSP no_process; |
| 465 | + lldb::ABISP abi_sp = ABI::FindPlugin(no_process, target.GetArchitecture()); |
| 466 | + ABI *abi = abi_sp.get(); |
| 467 | + |
| 468 | + llvm::DIDumpOptions opts; |
| 469 | + opts.ShowAddresses = false; |
| 470 | + if (abi) |
| 471 | + opts.PrintRegisterOnly = true; |
| 472 | + |
| 473 | + for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) { |
| 474 | + lldb::VariableSP v = var_list.GetVariableAtIndex(i); |
| 475 | + if (!v || v->IsArtificial()) |
| 476 | + continue; |
| 477 | + |
| 478 | + const char *nm = v->GetName().AsCString(); |
| 479 | + llvm::StringRef name = nm ? nm : "<anon>"; |
| 480 | + |
| 481 | + lldb_private::DWARFExpressionList &exprs = v->LocationExpressionList(); |
| 482 | + if (!exprs.IsValid()) |
| 483 | + continue; |
| 484 | + |
| 485 | + auto entry_or_err = exprs.GetExpressionEntryAtAddress(func_file, pc_file); |
| 486 | + if (!entry_or_err) |
| 487 | + continue; |
| 488 | + |
| 489 | + auto entry = *entry_or_err; |
| 490 | + |
| 491 | + StreamString loc_ss; |
| 492 | + entry.expr->DumpLocation(&loc_ss, eDescriptionLevelBrief, abi, opts); |
| 493 | + llvm::StringRef loc = llvm::StringRef(loc_ss.GetString()).trim(); |
| 494 | + if (loc.empty()) |
| 495 | + continue; |
| 496 | + |
| 497 | + auto ins = live_vars.insert( |
| 498 | + {v->GetID(), VarState{name.str(), loc.str(), /*seen*/ true}}); |
| 499 | + if (ins.second) { |
| 500 | + // Newly live. |
| 501 | + events.push_back(llvm::formatv("{0} = {1}", name, loc).str()); |
| 502 | + } else { |
| 503 | + VarState &vs = ins.first->second; |
| 504 | + vs.seen_this_inst = true; |
| 505 | + if (vs.last_loc != loc) { |
| 506 | + vs.last_loc = loc.str(); |
| 507 | + events.push_back(llvm::formatv("{0} = {1}", vs.name, loc).str()); |
| 508 | + } |
| 509 | + } |
| 510 | + } |
| 511 | + |
| 512 | + // Anything previously live that we didn't see a location for at this inst |
| 513 | + // is now <undef>. |
| 514 | + for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { |
| 515 | + auto Cur = I++; |
| 516 | + if (!Cur->second.seen_this_inst) { |
| 517 | + events.push_back( |
| 518 | + llvm::formatv("{0} = <undef>", Cur->second.name).str()); |
| 519 | + live_vars.erase(Cur); |
| 520 | + } |
| 521 | + } |
| 522 | + |
| 523 | + return events; |
| 524 | + }; |
| 525 | + |
379 | 526 | previous_symbol = nullptr; |
380 | 527 | SourceLine previous_line; |
381 | 528 | for (size_t i = 0; i < num_instructions_found; ++i) { |
@@ -540,10 +687,26 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, |
540 | 687 | const bool show_bytes = (options & eOptionShowBytes) != 0; |
541 | 688 | const bool show_control_flow_kind = |
542 | 689 | (options & eOptionShowControlFlowKind) != 0; |
543 | | - inst->Dump(&strm, max_opcode_byte_size, true, show_bytes, |
| 690 | + |
| 691 | + StreamString inst_line; |
| 692 | + |
| 693 | + inst->Dump(&inst_line, max_opcode_byte_size, true, show_bytes, |
544 | 694 | show_control_flow_kind, &exe_ctx, &sc, &prev_sc, nullptr, |
545 | 695 | address_text_size); |
| 696 | + |
| 697 | + if ((options & eOptionVariableAnnotations) && target_sp) { |
| 698 | + auto annotations = annotate_static(*inst, *target_sp, module_sp); |
| 699 | + if (!annotations.empty()) { |
| 700 | + const size_t annotation_column = 100; |
| 701 | + inst_line.FillLastLineToColumn(annotation_column, ' '); |
| 702 | + inst_line.PutCString("; "); |
| 703 | + inst_line.PutCString(llvm::join(annotations, ", ")); |
| 704 | + } |
| 705 | + } |
| 706 | + |
| 707 | + strm.PutCString(inst_line.GetString()); |
546 | 708 | strm.EOL(); |
| 709 | + |
547 | 710 | } else { |
548 | 711 | break; |
549 | 712 | } |
@@ -724,9 +887,7 @@ bool Instruction::DumpEmulation(const ArchSpec &arch) { |
724 | 887 | return false; |
725 | 888 | } |
726 | 889 |
|
727 | | -bool Instruction::CanSetBreakpoint () { |
728 | | - return !HasDelaySlot(); |
729 | | -} |
| 890 | +bool Instruction::CanSetBreakpoint() { return !HasDelaySlot(); } |
730 | 891 |
|
731 | 892 | bool Instruction::HasDelaySlot() { |
732 | 893 | // Default is false. |
@@ -1073,10 +1234,8 @@ void InstructionList::Append(lldb::InstructionSP &inst_sp) { |
1073 | 1234 | m_instructions.push_back(inst_sp); |
1074 | 1235 | } |
1075 | 1236 |
|
1076 | | -uint32_t |
1077 | | -InstructionList::GetIndexOfNextBranchInstruction(uint32_t start, |
1078 | | - bool ignore_calls, |
1079 | | - bool *found_calls) const { |
| 1237 | +uint32_t InstructionList::GetIndexOfNextBranchInstruction( |
| 1238 | + uint32_t start, bool ignore_calls, bool *found_calls) const { |
1080 | 1239 | size_t num_instructions = m_instructions.size(); |
1081 | 1240 |
|
1082 | 1241 | uint32_t next_branch = UINT32_MAX; |
|
0 commit comments