-
Notifications
You must be signed in to change notification settings - Fork 15.3k
Stateful variable-location annotations in Disassembler::PrintInstructions() (follow-up to #147460) #152887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stateful variable-location annotations in Disassembler::PrintInstructions() (follow-up to #147460) #152887
Changes from 52 commits
8ed8c54
1db5002
a26010b
72237b7
94e4951
e8142da
7e8741e
c4cd77f
62c02a9
d015971
60898ea
3462165
2ed8443
8c6b22d
842a9e5
2fa6d24
6bbc8aa
cbbc924
b887db2
912ba6d
09c4d04
6e17f77
31431c0
9c5cb8f
ca8510c
ffefe5f
fae745a
dcddf16
7bac074
79c0a9e
c7f1b30
3d19b02
6ca4bb6
4bf584e
b1f13e7
7b526fc
10fddc4
b784868
cb0cd3a
fbd4e65
77fa1ed
7069b6a
212a401
d234d3e
e282782
0b71f82
9b3f552
75e6b44
e5e168f
cc77627
51e27b6
c41b79c
11d85ea
c7d489b
a82957c
2cda69c
6b24fff
cc0aee9
e1d0f91
c0babd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,6 +154,10 @@ Status CommandObjectDisassemble::CommandOptions::SetOptionValue( | |
| } | ||
| } break; | ||
|
|
||
| case 'v': //< --rich variable annotations | ||
UltimateForce21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| enable_rich_annotations = true; | ||
|
||
| break; | ||
|
|
||
| case '\x01': | ||
| force = true; | ||
| break; | ||
|
|
@@ -180,6 +184,7 @@ void CommandObjectDisassemble::CommandOptions::OptionParsingStarting( | |
| end_addr = LLDB_INVALID_ADDRESS; | ||
| symbol_containing_addr = LLDB_INVALID_ADDRESS; | ||
| raw = false; | ||
| enable_rich_annotations = false; | ||
| plugin_name.clear(); | ||
|
|
||
| Target *target = | ||
|
|
@@ -528,6 +533,9 @@ void CommandObjectDisassemble::DoExecute(Args &command, | |
| if (m_options.raw) | ||
| options |= Disassembler::eOptionRawOuput; | ||
|
|
||
| if (m_options.enable_rich_annotations) | ||
| options |= Disassembler::eOptionRichAnnotations; | ||
|
|
||
| llvm::Expected<std::vector<AddressRange>> ranges = | ||
| GetRangesForSelectedMode(result); | ||
| if (!ranges) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,7 +26,11 @@ | |||||||||||||
| #include "lldb/Symbol/Function.h" | ||||||||||||||
| #include "lldb/Symbol/Symbol.h" | ||||||||||||||
| #include "lldb/Symbol/SymbolContext.h" | ||||||||||||||
| #include "lldb/Symbol/Variable.h" | ||||||||||||||
| #include "lldb/Symbol/VariableList.h" | ||||||||||||||
| #include "lldb/Target/ABI.h" | ||||||||||||||
| #include "lldb/Target/ExecutionContext.h" | ||||||||||||||
| #include "lldb/Target/Process.h" | ||||||||||||||
| #include "lldb/Target/SectionLoadList.h" | ||||||||||||||
| #include "lldb/Target/StackFrame.h" | ||||||||||||||
| #include "lldb/Target/Target.h" | ||||||||||||||
|
|
@@ -41,6 +45,8 @@ | |||||||||||||
| #include "lldb/lldb-private-enumerations.h" | ||||||||||||||
| #include "lldb/lldb-private-interfaces.h" | ||||||||||||||
| #include "lldb/lldb-private-types.h" | ||||||||||||||
| #include "llvm/ADT/DenseMap.h" | ||||||||||||||
| #include "llvm/ADT/StringRef.h" | ||||||||||||||
| #include "llvm/Support/Compiler.h" | ||||||||||||||
| #include "llvm/TargetParser/Triple.h" | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -376,6 +382,148 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Add rich variable location annotations to the disassembly output. | ||||||||||||||
UltimateForce21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||
| // | ||||||||||||||
| // For each instruction, this block attempts to resolve in-scope variables | ||||||||||||||
| // and determine if the current PC falls within their | ||||||||||||||
| // DWARF location entry. If so, it prints a simplified annotation using the | ||||||||||||||
| // variable name and its resolved location (e.g., "var = reg; " ). | ||||||||||||||
| // | ||||||||||||||
| // Annotations are only included if the variable has a valid DWARF location | ||||||||||||||
| // entry, and the location string is non-empty after filtering. Decoding | ||||||||||||||
| // errors and DWARF opcodes are intentionally omitted to keep the output | ||||||||||||||
| // concise and user-friendly. | ||||||||||||||
| // | ||||||||||||||
| // The goal is to give users helpful live variable hints alongside the | ||||||||||||||
| // disassembled instruction stream, similar to how debug information | ||||||||||||||
| // enhances source-level debugging. | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like it could be its own abstraction, like a small subclass in the disassembler file? Especially if there's state that needs to be shared. |
||||||||||||||
|
|
||||||||||||||
| struct VarState { | ||||||||||||||
| std::string name; ///< Display name. | ||||||||||||||
| std::string last_loc; //< Last printed location (empty means <undef>). | ||||||||||||||
UltimateForce21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||
| bool seen_this_inst = false; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| // Track live variables across instructions (keyed by stable LLDB user_id_t. 8 | ||||||||||||||
| // is a good small-buffer guess. | ||||||||||||||
| llvm::SmallDenseMap<lldb::user_id_t, VarState, 8> live_vars; | ||||||||||||||
|
||||||||||||||
|
|
||||||||||||||
| // Stateful annotator: updates live_vars and returns only what should be | ||||||||||||||
| // printed for THIS instruction. | ||||||||||||||
| auto annotate_static = [&](Instruction &inst, Target &target, | ||||||||||||||
| ModuleSP module_sp) -> std::vector<std::string> { | ||||||||||||||
| std::vector<std::string> events; | ||||||||||||||
|
|
||||||||||||||
| // Reset per-instruction seen flags. | ||||||||||||||
| for (auto &kv : live_vars) | ||||||||||||||
| kv.second.seen_this_inst = false; | ||||||||||||||
|
Comment on lines
+416
to
+418
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need to remember the key still? Can you just clear the map? |
||||||||||||||
|
|
||||||||||||||
| const Address &iaddr = inst.GetAddress(); | ||||||||||||||
| if (!module_sp) { | ||||||||||||||
| // Everything previously live becomes <undef>. | ||||||||||||||
| for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is using the LLVM coding style. |
||||||||||||||
| auto Cur = I++; | ||||||||||||||
| events.push_back( | ||||||||||||||
| llvm::formatv("{0} = <undef>", Cur->second.name).str()); | ||||||||||||||
| live_vars.erase(Cur); | ||||||||||||||
| } | ||||||||||||||
| return events; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Resolve innermost block at this *file* address. | ||||||||||||||
| SymbolContext sc; | ||||||||||||||
| const lldb::SymbolContextItem mask = | ||||||||||||||
| eSymbolContextFunction | eSymbolContextBlock; | ||||||||||||||
| if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) || | ||||||||||||||
| !sc.function) { | ||||||||||||||
| // No function context: everything dies here. | ||||||||||||||
| for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { | ||||||||||||||
| auto Cur = I++; | ||||||||||||||
| events.push_back( | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| llvm::formatv("{0} = <undef>", Cur->second.name).str()); | ||||||||||||||
| live_vars.erase(Cur); | ||||||||||||||
| } | ||||||||||||||
| return events; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| Block *B = sc.block; ///< Innermost block containing iaddr. | ||||||||||||||
| VariableList var_list; | ||||||||||||||
| if (B) { | ||||||||||||||
|
Comment on lines
+448
to
+450
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| auto filter = [](Variable *v) -> bool { return v && !v->IsArtificial(); }; | ||||||||||||||
|
|
||||||||||||||
| B->AppendVariables(/*can_create*/ true, | ||||||||||||||
| /*get_parent_variables*/ true, | ||||||||||||||
| /*stop_if_block_is_inlined_function*/ false, | ||||||||||||||
| /*filter*/ filter, | ||||||||||||||
| /*variable_list*/ &var_list); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const lldb::addr_t pc_file = iaddr.GetFileAddress(); | ||||||||||||||
| const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress(); | ||||||||||||||
|
|
||||||||||||||
| // ABI from Target (pretty reg names if plugin exists). Safe to be null. | ||||||||||||||
| lldb::ProcessSP no_process; | ||||||||||||||
| lldb::ABISP abi_sp = ABI::FindPlugin(no_process, target.GetArchitecture()); | ||||||||||||||
|
Comment on lines
+464
to
+465
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think nullptr is more obvious:
Suggested change
|
||||||||||||||
| ABI *abi = abi_sp.get(); | ||||||||||||||
|
|
||||||||||||||
| llvm::DIDumpOptions opts; | ||||||||||||||
| opts.ShowAddresses = false; | ||||||||||||||
| if (abi) | ||||||||||||||
| opts.PrintRegisterOnly = true; | ||||||||||||||
|
Comment on lines
+470
to
+471
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) { | ||||||||||||||
| lldb::VariableSP v = var_list.GetVariableAtIndex(i); | ||||||||||||||
| if (!v || v->IsArtificial()) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| const char *nm = v->GetName().AsCString(); | ||||||||||||||
| llvm::StringRef name = nm ? nm : "<anon>"; | ||||||||||||||
|
|
||||||||||||||
| lldb_private::DWARFExpressionList &exprs = v->LocationExpressionList(); | ||||||||||||||
| if (!exprs.IsValid()) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| auto entry_or_err = exprs.GetExpressionEntryAtAddress(func_file, pc_file); | ||||||||||||||
| if (!entry_or_err) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| auto entry = *entry_or_err; | ||||||||||||||
|
|
||||||||||||||
| StreamString loc_ss; | ||||||||||||||
| entry.expr->DumpLocation(&loc_ss, eDescriptionLevelBrief, abi, opts); | ||||||||||||||
| llvm::StringRef loc = llvm::StringRef(loc_ss.GetString()).trim(); | ||||||||||||||
| if (loc.empty()) | ||||||||||||||
| continue; | ||||||||||||||
|
|
||||||||||||||
| auto ins = live_vars.insert( | ||||||||||||||
| {v->GetID(), VarState{name.str(), loc.str(), /*seen*/ true}}); | ||||||||||||||
| if (ins.second) { | ||||||||||||||
| // Newly live. | ||||||||||||||
| events.push_back(llvm::formatv("{0} = {1}", name, loc).str()); | ||||||||||||||
| } else { | ||||||||||||||
| VarState &vs = ins.first->second; | ||||||||||||||
| vs.seen_this_inst = true; | ||||||||||||||
| if (vs.last_loc != loc) { | ||||||||||||||
| vs.last_loc = loc.str(); | ||||||||||||||
| events.push_back(llvm::formatv("{0} = {1}", vs.name, loc).str()); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Anything previously live that we didn't see a location for at this inst | ||||||||||||||
| // is now <undef>. | ||||||||||||||
| for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { | ||||||||||||||
| auto Cur = I++; | ||||||||||||||
| if (!Cur->second.seen_this_inst) { | ||||||||||||||
| events.push_back( | ||||||||||||||
| llvm::formatv("{0} = <undef>", Cur->second.name).str()); | ||||||||||||||
| live_vars.erase(Cur); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return events; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| previous_symbol = nullptr; | ||||||||||||||
| SourceLine previous_line; | ||||||||||||||
| for (size_t i = 0; i < num_instructions_found; ++i) { | ||||||||||||||
|
|
@@ -540,10 +688,26 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, | |||||||||||||
| const bool show_bytes = (options & eOptionShowBytes) != 0; | ||||||||||||||
| const bool show_control_flow_kind = | ||||||||||||||
| (options & eOptionShowControlFlowKind) != 0; | ||||||||||||||
| inst->Dump(&strm, max_opcode_byte_size, true, show_bytes, | ||||||||||||||
|
|
||||||||||||||
| StreamString inst_line; | ||||||||||||||
|
|
||||||||||||||
| inst->Dump(&inst_line, max_opcode_byte_size, true, show_bytes, | ||||||||||||||
| show_control_flow_kind, &exe_ctx, &sc, &prev_sc, nullptr, | ||||||||||||||
| address_text_size); | ||||||||||||||
|
|
||||||||||||||
| if ((options & eOptionRichAnnotations) && target_sp) { | ||||||||||||||
| auto annotations = annotate_static(*inst, *target_sp, module_sp); | ||||||||||||||
| if (!annotations.empty()) { | ||||||||||||||
| const size_t annotation_column = 100; | ||||||||||||||
| inst_line.FillLastLineToColumn(annotation_column, ' '); | ||||||||||||||
| inst_line.PutCString("; "); | ||||||||||||||
| inst_line.PutCString(llvm::join(annotations, ", ")); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| strm.PutCString(inst_line.GetString()); | ||||||||||||||
| strm.EOL(); | ||||||||||||||
|
|
||||||||||||||
| } else { | ||||||||||||||
| break; | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -724,9 +888,7 @@ bool Instruction::DumpEmulation(const ArchSpec &arch) { | |||||||||||||
| return false; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| bool Instruction::CanSetBreakpoint () { | ||||||||||||||
| return !HasDelaySlot(); | ||||||||||||||
| } | ||||||||||||||
| bool Instruction::CanSetBreakpoint() { return !HasDelaySlot(); } | ||||||||||||||
|
|
||||||||||||||
| bool Instruction::HasDelaySlot() { | ||||||||||||||
| // Default is false. | ||||||||||||||
|
|
@@ -1073,10 +1235,8 @@ void InstructionList::Append(lldb::InstructionSP &inst_sp) { | |||||||||||||
| m_instructions.push_back(inst_sp); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| uint32_t | ||||||||||||||
| InstructionList::GetIndexOfNextBranchInstruction(uint32_t start, | ||||||||||||||
| bool ignore_calls, | ||||||||||||||
| bool *found_calls) const { | ||||||||||||||
| uint32_t InstructionList::GetIndexOfNextBranchInstruction( | ||||||||||||||
| uint32_t start, bool ignore_calls, bool *found_calls) const { | ||||||||||||||
| size_t num_instructions = m_instructions.size(); | ||||||||||||||
|
|
||||||||||||||
| uint32_t next_branch = UINT32_MAX; | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eOptionVariableAnnotations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping @UltimateForce21