Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
aba4890
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Oct 26, 2025
199a909
Update lldb/include/lldb/Core/Disassembler.h
n2h9 Oct 28, 2025
ea1bf46
Update lldb/include/lldb/Core/Disassembler.h
n2h9 Oct 28, 2025
0331516
Update lldb/test/API/functionalities/disassembler-variables/TestVaria…
n2h9 Oct 28, 2025
2449b6c
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Oct 30, 2025
839ac8e
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Oct 31, 2025
5278024
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Oct 31, 2025
7055cc0
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
13afa00
Merge branch 'main' into rich-disassembler-structured-variable-annota…
n2h9 Nov 1, 2025
4cee0d7
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
c955af3
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
4d8706d
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
573a6a1
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
c688f70
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
31e91cf
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 1, 2025
f805dbb
Update lldb/include/lldb/Core/Disassembler.h
n2h9 Nov 6, 2025
f03a448
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 6, 2025
f10922f
[lldb] [disassembler] chore: enhance VariableAnnotator to return stru…
n2h9 Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lldb/include/lldb/API/SBInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "lldb/API/SBData.h"
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBStructuredData.h"

#include <cstdio>

Expand Down Expand Up @@ -73,6 +74,23 @@ class LLDB_API SBInstruction {

bool TestEmulation(lldb::SBStream &output_stream, const char *test_file);

/// Get variable annotations for this instruction as structured data.
/// Returns an array of dictionaries, each containing:
/// - "variable_name": string name of the variable
/// - "location_description": string description of where variable is stored
/// ("RDI", "R15", "undef", etc.)
/// - "is_live": boolean indicates if variable is live at this instruction
/// - "start_address": unsigned integer address where this annotation becomes
/// valid
/// - "end_address": unsigned integer address where this annotation becomes
/// invalid
/// - "register_kind": unsigned integer indicating the register numbering
/// scheme
/// - "decl_file": string path to the file where variable is declared
/// - "decl_line": unsigned integer line number where variable is declared
/// - "type_name": string type name of the variable
lldb::SBStructuredData GetVariableAnnotations(lldb::SBTarget target);

protected:
friend class SBInstructionList;

Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBStructuredData.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class SBStructuredData {
friend class SBBreakpointLocation;
friend class SBBreakpointName;
friend class SBTrace;
friend class SBInstruction;
friend class lldb_private::python::SWIGBridge;
friend class lldb_private::lua::SWIGBridge;
friend class SBCommandInterpreter;
Expand Down
39 changes: 29 additions & 10 deletions lldb/include/lldb/Core/Disassembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -566,24 +566,43 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>,
const Disassembler &operator=(const Disassembler &) = delete;
};

/// Structured data for a single variable annotation.
struct VariableAnnotation {
std::string variable_name;
/// Location description (e.g., "r15", "undef", "const_0").
std::string location_description;
/// Whether variable is live at this instruction.
bool is_live;
/// Register numbering scheme for location interpretation.
lldb::RegisterKind register_kind;
/// Where this annotation is valid.
std::optional<lldb_private::AddressRange> address_range;
/// Source file where variable was declared.
std::optional<std::string> decl_file;
/// Line number where variable was declared.
std::optional<uint32_t> decl_line;
/// Variable's type name.
std::optional<std::string> type_name;
};

/// Tracks live variable annotations across instructions and produces
/// per-instruction "events" like `name = RDI` or `name = <undef>`.
class VariableAnnotator {
struct VarState {
/// Display name.
std::string name;
/// Last printed location (empty means <undef>).
std::string last_loc;
};

// Live state from the previous instruction, keyed by Variable::GetID().
llvm::DenseMap<lldb::user_id_t, VarState> Live_;
llvm::DenseMap<lldb::user_id_t, VariableAnnotation> m_live_vars;

static constexpr const char *kUndefLocation = "undef";

public:
/// Compute annotation strings for a single instruction and update `Live_`.
/// Returns only the events that should be printed *at this instruction*.
std::vector<std::string> annotate(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp);
std::vector<std::string> Annotate(Instruction &inst, Target &target,
lldb::ModuleSP module_sp);

/// Returns structured data for all variables relevant at this instruction.
std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst,
Target &target,
lldb::ModuleSP module_sp);
};

} // namespace lldb_private
Expand Down
69 changes: 67 additions & 2 deletions lldb/source/API/SBInstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
#include "lldb/Utility/Instrumentation.h"

#include "lldb/API/SBAddress.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBFile.h"
#include "lldb/API/SBFrame.h"

#include "lldb/API/SBStream.h"
#include "lldb/API/SBStructuredData.h"
#include "lldb/API/SBTarget.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Core/EmulateInstruction.h"
Expand All @@ -26,6 +27,7 @@
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/StructuredData.h"

#include <memory>

Expand Down Expand Up @@ -163,7 +165,8 @@ const char *SBInstruction::GetComment(SBTarget target) {
return ConstString(inst_sp->GetComment(&exe_ctx)).GetCString();
}

lldb::InstructionControlFlowKind SBInstruction::GetControlFlowKind(lldb::SBTarget target) {
lldb::InstructionControlFlowKind
SBInstruction::GetControlFlowKind(lldb::SBTarget target) {
LLDB_INSTRUMENT_VA(this, target);

lldb::InstructionSP inst_sp(GetOpaque());
Expand Down Expand Up @@ -347,3 +350,65 @@ bool SBInstruction::TestEmulation(lldb::SBStream &output_stream,
return inst_sp->TestEmulation(output_stream.ref(), test_file);
return false;
}

lldb::SBStructuredData
SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
LLDB_INSTRUMENT_VA(this, target);

SBStructuredData result;

if (!m_opaque_sp || !m_opaque_sp->IsValid() || !target.IsValid())
return result;

lldb::InstructionSP inst_sp = m_opaque_sp->GetSP();
lldb::TargetSP target_sp = target.GetSP();

if (!inst_sp || !target_sp)
return result;

const Address &addr = inst_sp->GetAddress();
ModuleSP module_sp = addr.GetModule();

if (!module_sp)
return result;

VariableAnnotator annotator;
std::vector<VariableAnnotation> annotations =
annotator.AnnotateStructured(*inst_sp, *target_sp, module_sp);

auto array_sp = std::make_shared<StructuredData::Array>();

for (const auto &ann : annotations) {
auto dict_sp = std::make_shared<StructuredData::Dictionary>();

dict_sp->AddStringItem("variable_name", ann.variable_name);
dict_sp->AddStringItem("location_description", ann.location_description);
dict_sp->AddBooleanItem("is_live", ann.is_live);
if (ann.address_range.has_value()) {
const auto &range = *ann.address_range;
dict_sp->AddItem("start_address",
std::make_shared<StructuredData::UnsignedInteger>(
range.GetBaseAddress().GetFileAddress()));
dict_sp->AddItem(
"end_address",
std::make_shared<StructuredData::UnsignedInteger>(
range.GetBaseAddress().GetFileAddress() + range.GetByteSize()));
}
dict_sp->AddItem(
"register_kind",
std::make_shared<StructuredData::UnsignedInteger>(ann.register_kind));
if (ann.decl_file.has_value())
dict_sp->AddStringItem("decl_file", *ann.decl_file);
if (ann.decl_line.has_value())
dict_sp->AddItem(
"decl_line",
std::make_shared<StructuredData::UnsignedInteger>(*ann.decl_line));
if (ann.type_name.has_value())
dict_sp->AddStringItem("type_name", *ann.type_name);

array_sp->AddItem(dict_sp);
}

result.m_impl_up->SetObjectSP(array_sp);
return result;
}
123 changes: 90 additions & 33 deletions lldb/source/Core/Disassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,17 +299,45 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine(
// The goal is to give users helpful live variable hints alongside the
// disassembled instruction stream, similar to how debug information
// enhances source-level debugging.
std::vector<std::string>
VariableAnnotator::annotate(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp) {
std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst,
Target &target,
lldb::ModuleSP module_sp) {
auto structured_annotations = AnnotateStructured(inst, target, module_sp);

std::vector<std::string> events;
events.reserve(structured_annotations.size());

for (const auto &annotation : structured_annotations) {
std::string display_string;
display_string =
llvm::formatv(
"{0} = {1}", annotation.variable_name,
annotation.location_description == VariableAnnotator::kUndefLocation
? llvm::formatv("<{0}>", VariableAnnotator::kUndefLocation)
.str()
: annotation.location_description)
.str();
Comment on lines +312 to +319
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it could benefit from writing this as a stream:

  std::string display_string;
  llvm::raw_string_ostream os(display_string);
  os << annotation.variable_name;
  [...]
  events.push_back(std::move(display_string));

events.push_back(display_string);
}

return events;
}

std::vector<VariableAnnotation>
VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
lldb::ModuleSP module_sp) {
std::vector<VariableAnnotation> annotations;

// If we lost module context, everything becomes <undef>.
// If we lost module context, mark all live variables as undefined.
if (!module_sp) {
for (const auto &KV : Live_)
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
Live_.clear();
return events;
for (const auto &KV : m_live_vars) {
auto annotation_entity = KV.second;
annotation_entity.is_live = false;
annotation_entity.location_description = kUndefLocation;
annotations.push_back(annotation_entity);
}
m_live_vars.clear();
return annotations;
}

// Resolve function/block at this *file* address.
Expand All @@ -319,10 +347,14 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) ||
!sc.function) {
// No function context: everything dies here.
for (const auto &KV : Live_)
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
Live_.clear();
return events;
for (const auto &KV : m_live_vars) {
auto annotation_entity = KV.second;
annotation_entity.is_live = false;
annotation_entity.location_description = kUndefLocation;
annotations.push_back(annotation_entity);
}
m_live_vars.clear();
return annotations;
}

// Collect in-scope variables for this instruction into Current.
Expand All @@ -349,7 +381,7 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
// Prefer "register-only" output when we have an ABI.
opts.PrintRegisterOnly = static_cast<bool>(abi_sp);

llvm::DenseMap<lldb::user_id_t, VarState> Current;
llvm::DenseMap<lldb::user_id_t, VariableAnnotation> current_vars;

for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) {
lldb::VariableSP v = var_list.GetVariableAtIndex(i);
Expand All @@ -376,35 +408,60 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
if (loc.empty())
continue;

Current.try_emplace(v->GetID(),
VarState{std::string(name), std::string(loc)});
std::optional<std::string> decl_file;
std::optional<uint32_t> decl_line;
std::optional<std::string> type_name;

const Declaration &decl = v->GetDeclaration();
if (decl.GetFile()) {
decl_file = decl.GetFile().GetFilename().AsCString();
if (decl.GetLine() > 0)
decl_line = decl.GetLine();
}

if (Type *type = v->GetType())
if (const char *type_str = type->GetName().AsCString())
type_name = type_str;

current_vars.try_emplace(v->GetID(),
VariableAnnotation{std::string(name), std::string(loc),
true, entry.expr->GetRegisterKind(),
entry.file_range, decl_file,
decl_line, type_name});
}

// Diff Live_Current.
// Diff m_live_varscurrent_vars.

// 1) Starts/changes: iterate Current and compare with Live_.
for (const auto &KV : Current) {
auto it = Live_.find(KV.first);
if (it == Live_.end()) {
// 1) Starts/changes: iterate current_vars and compare with m_live_vars.
for (const auto &KV : current_vars) {
auto it = m_live_vars.find(KV.first);
if (it == m_live_vars.end()) {
// Newly live.
events.emplace_back(
llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
} else if (it->second.last_loc != KV.second.last_loc) {
auto annotation_entity = KV.second;
annotation_entity.is_live = true;
annotations.push_back(annotation_entity);
} else if (it->second.location_description !=
KV.second.location_description) {
// Location changed.
events.emplace_back(
llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
auto annotation_entity = KV.second;
annotation_entity.is_live = true;
annotations.push_back(annotation_entity);
}
}

// 2) Ends: anything that was live but is not in Current becomes <undef>.
for (const auto &KV : Live_) {
if (!Current.count(KV.first))
events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
}
// 2) Ends: anything that was live but is not in current_vars becomes
// <kUndefLocation>.
for (const auto &KV : m_live_vars)
if (!current_vars.count(KV.first)) {
auto annotation_entity = KV.second;
annotation_entity.is_live = false;
annotation_entity.location_description = kUndefLocation;
annotations.push_back(annotation_entity);
}

// Commit new state.
Live_ = std::move(Current);
return events;
m_live_vars = std::move(current_vars);
return annotations;
}

void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
Expand Down Expand Up @@ -676,7 +733,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
address_text_size);

if ((options & eOptionVariableAnnotations) && target_sp) {
auto annotations = annot.annotate(*inst, *target_sp, module_sp);
auto annotations = annot.Annotate(*inst, *target_sp, module_sp);
if (!annotations.empty()) {
const size_t annotation_column = 100;
inst_line.FillLastLineToColumn(annotation_column, ' ');
Expand Down
Loading