@@ -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+
289410void 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