Skip to content

Commit abfb945

Browse files
Automerge: [lldb] Refactor variable annotation logic in Disassembler::PrintInstructions (#156118)
This patch is a follow-up to [#152887](llvm/llvm-project#152887), addressing review comments that came in after the original change was merged. - Move `VarState` definition out of `PrintInstructions` into a private helper, with member comments placed before fields. - Introduce a `VariableAnnotator` helper class to encapsulate state and logic for live variable tracking across instructions. - Replace `seen_this_inst` flag with a map-diff approach: recompute the current variable set per instruction and diff against the previous set. - Use `nullptr` instead of an empty `ProcessSP` when calling `ABI::FindPlugin`. - Narrow `Block*` scope with `if (Block *B = ...)`. - Set `DIDumpOptions::PrintRegisterOnly` directly from `static_cast<bool>(abi_sp)`. - Prefer `emplace_back` over `push_back` for event strings. - General cleanup to match LLVM coding style and reviewer feedback. This makes the annotation code easier to read and consistent with LLVM/LLDB conventions while preserving functionality.
2 parents f59284b + d39772c commit abfb945

File tree

2 files changed

+143
-142
lines changed

2 files changed

+143
-142
lines changed

lldb/include/lldb/Core/Disassembler.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,26 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>,
566566
const Disassembler &operator=(const Disassembler &) = delete;
567567
};
568568

569+
/// Tracks live variable annotations across instructions and produces
570+
/// per-instruction "events" like `name = RDI` or `name = <undef>`.
571+
class VariableAnnotator {
572+
struct VarState {
573+
/// Display name.
574+
std::string name;
575+
/// Last printed location (empty means <undef>).
576+
std::string last_loc;
577+
};
578+
579+
// Live state from the previous instruction, keyed by Variable::GetID().
580+
llvm::DenseMap<lldb::user_id_t, VarState> Live_;
581+
582+
public:
583+
/// Compute annotation strings for a single instruction and update `Live_`.
584+
/// Returns only the events that should be printed *at this instruction*.
585+
std::vector<std::string> annotate(Instruction &inst, Target &target,
586+
const lldb::ModuleSP &module_sp);
587+
};
588+
569589
} // namespace lldb_private
570590

571591
#endif // LLDB_CORE_DISASSEMBLER_H

lldb/source/Core/Disassembler.cpp

Lines changed: 123 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,127 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine(
286286
return false;
287287
}
288288

289+
// For each instruction, this block attempts to resolve in-scope variables
290+
// and determine if the current PC falls within their
291+
// DWARF location entry. If so, it prints a simplified annotation using the
292+
// variable name and its resolved location (e.g., "var = reg; " ).
293+
//
294+
// Annotations are only included if the variable has a valid DWARF location
295+
// entry, and the location string is non-empty after filtering. Decoding
296+
// errors and DWARF opcodes are intentionally omitted to keep the output
297+
// concise and user-friendly.
298+
//
299+
// The goal is to give users helpful live variable hints alongside the
300+
// disassembled instruction stream, similar to how debug information
301+
// enhances source-level debugging.
302+
std::vector<std::string>
303+
VariableAnnotator::annotate(Instruction &inst, Target &target,
304+
const lldb::ModuleSP &module_sp) {
305+
std::vector<std::string> events;
306+
307+
// If we lost module context, everything becomes <undef>.
308+
if (!module_sp) {
309+
for (const auto &KV : Live_)
310+
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
311+
Live_.clear();
312+
return events;
313+
}
314+
315+
// Resolve function/block at this *file* address.
316+
SymbolContext sc;
317+
const Address &iaddr = inst.GetAddress();
318+
const auto mask = eSymbolContextFunction | eSymbolContextBlock;
319+
if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) ||
320+
!sc.function) {
321+
// No function context: everything dies here.
322+
for (const auto &KV : Live_)
323+
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
324+
Live_.clear();
325+
return events;
326+
}
327+
328+
// Collect in-scope variables for this instruction into Current.
329+
VariableList var_list;
330+
// Innermost block containing iaddr.
331+
if (Block *B = sc.block) {
332+
auto filter = [](Variable *v) -> bool { return v && !v->IsArtificial(); };
333+
B->AppendVariables(/*can_create*/ true,
334+
/*get_parent_variables*/ true,
335+
/*stop_if_block_is_inlined_function*/ false,
336+
/*filter*/ filter,
337+
/*variable_list*/ &var_list);
338+
}
339+
340+
const lldb::addr_t pc_file = iaddr.GetFileAddress();
341+
const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress();
342+
343+
// ABI from Target (pretty reg names if plugin exists). Safe to be null.
344+
lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, target.GetArchitecture());
345+
ABI *abi = abi_sp.get();
346+
347+
llvm::DIDumpOptions opts;
348+
opts.ShowAddresses = false;
349+
// Prefer "register-only" output when we have an ABI.
350+
opts.PrintRegisterOnly = static_cast<bool>(abi_sp);
351+
352+
llvm::DenseMap<lldb::user_id_t, VarState> Current;
353+
354+
for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) {
355+
lldb::VariableSP v = var_list.GetVariableAtIndex(i);
356+
if (!v || v->IsArtificial())
357+
continue;
358+
359+
const char *nm = v->GetName().AsCString();
360+
llvm::StringRef name = nm ? nm : "<anon>";
361+
362+
DWARFExpressionList &exprs = v->LocationExpressionList();
363+
if (!exprs.IsValid())
364+
continue;
365+
366+
auto entry_or_err = exprs.GetExpressionEntryAtAddress(func_file, pc_file);
367+
if (!entry_or_err)
368+
continue;
369+
370+
auto entry = *entry_or_err;
371+
372+
StreamString loc_ss;
373+
entry.expr->DumpLocation(&loc_ss, eDescriptionLevelBrief, abi, opts);
374+
375+
llvm::StringRef loc = llvm::StringRef(loc_ss.GetString()).trim();
376+
if (loc.empty())
377+
continue;
378+
379+
Current.try_emplace(v->GetID(),
380+
VarState{std::string(name), std::string(loc)});
381+
}
382+
383+
// Diff Live_ → Current.
384+
385+
// 1) Starts/changes: iterate Current and compare with Live_.
386+
for (const auto &KV : Current) {
387+
auto it = Live_.find(KV.first);
388+
if (it == Live_.end()) {
389+
// Newly live.
390+
events.emplace_back(
391+
llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
392+
} else if (it->second.last_loc != KV.second.last_loc) {
393+
// Location changed.
394+
events.emplace_back(
395+
llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
396+
}
397+
}
398+
399+
// 2) Ends: anything that was live but is not in Current becomes <undef>.
400+
for (const auto &KV : Live_) {
401+
if (!Current.count(KV.first))
402+
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
403+
}
404+
405+
// Commit new state.
406+
Live_ = std::move(Current);
407+
return events;
408+
}
409+
289410
void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
290411
const ExecutionContext &exe_ctx,
291412
bool mixed_source_and_assembly,
@@ -382,147 +503,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
382503
}
383504
}
384505

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-
506+
VariableAnnotator annot;
526507
previous_symbol = nullptr;
527508
SourceLine previous_line;
528509
for (size_t i = 0; i < num_instructions_found; ++i) {
@@ -695,7 +676,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
695676
address_text_size);
696677

697678
if ((options & eOptionVariableAnnotations) && target_sp) {
698-
auto annotations = annotate_static(*inst, *target_sp, module_sp);
679+
auto annotations = annot.annotate(*inst, *target_sp, module_sp);
699680
if (!annotations.empty()) {
700681
const size_t annotation_column = 100;
701682
inst_line.FillLastLineToColumn(annotation_column, ' ');

0 commit comments

Comments
 (0)