[Lldb-commits] [lldb] [lldb] [disassembler] chore: enhance VariableAnnotator to return structured data (PR #165163)
via lldb-commits
lldb-commits at lists.llvm.org
Sun Nov 16 10:41:37 PST 2025
https://github.com/n2h9 updated https://github.com/llvm/llvm-project/pull/165163
>From aba4890ee4ce4e3c37961a3997d21832f904f599 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sun, 26 Oct 2025 18:49:43 +0100
Subject: [PATCH 01/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/API/SBInstruction.h | 18 ++
lldb/include/lldb/API/SBStructuredData.h | 1 +
lldb/include/lldb/Core/Disassembler.h | 38 ++++
lldb/source/API/SBInstruction.cpp | 71 +++++++-
lldb/source/Core/Disassembler.cpp | 104 +++++++++--
.../TestVariableAnnotationsDisassembler.py | 171 ++++++++++++++++++
6 files changed, 384 insertions(+), 19 deletions(-)
diff --git a/lldb/include/lldb/API/SBInstruction.h b/lldb/include/lldb/API/SBInstruction.h
index 755e3b4a47c9b..05e7087f2e679 100644
--- a/lldb/include/lldb/API/SBInstruction.h
+++ b/lldb/include/lldb/API/SBInstruction.h
@@ -11,6 +11,7 @@
#include "lldb/API/SBData.h"
#include "lldb/API/SBDefines.h"
+#include "lldb/API/SBStructuredData.h"
#include <cstdio>
@@ -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;
diff --git a/lldb/include/lldb/API/SBStructuredData.h b/lldb/include/lldb/API/SBStructuredData.h
index dfd8ec0e180ce..75fb16b795a5a 100644
--- a/lldb/include/lldb/API/SBStructuredData.h
+++ b/lldb/include/lldb/API/SBStructuredData.h
@@ -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;
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index db186dd33d774..0539d4919c096 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -566,6 +566,21 @@ 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;
+ std::string location_description; // e.g., "r15", "undef", "const_0"
+ lldb::addr_t start_address; // Where this annotation starts being valid
+ lldb::addr_t end_address; // Where this annotation ends being valid
+ bool is_live; // Whether variable is live at this instruction
+ lldb::RegisterKind
+ register_kind; // Register numbering scheme for location interpretation
+ std::optional<std::string>
+ decl_file; // Source file where variable was declared
+ std::optional<uint32_t> decl_line; // Line number where variable was declared
+ std::optional<std::string> type_name; // Variable's type name
+};
+
/// Tracks live variable annotations across instructions and produces
/// per-instruction "events" like `name = RDI` or `name = <undef>`.
class VariableAnnotator {
@@ -574,16 +589,39 @@ class VariableAnnotator {
std::string name;
/// Last printed location (empty means <undef>).
std::string last_loc;
+ /// Address range where this variable state is valid.
+ lldb::addr_t start_address;
+ lldb::addr_t end_address;
+ /// Register numbering scheme for location interpretation.
+ lldb::RegisterKind register_kind;
+
+ std::optional<std::string> decl_file;
+ std::optional<uint32_t> decl_line;
+ std::optional<std::string> type_name;
};
// Live state from the previous instruction, keyed by Variable::GetID().
llvm::DenseMap<lldb::user_id_t, VarState> Live_;
+ 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);
+
+ /// Compute structured annotation data for a single instruction and update
+ /// `Live_`. Returns structured data for all variables relevant at this
+ /// instruction.
+ std::vector<VariableAnnotation>
+ annotateStructured(Instruction &inst, Target &target,
+ const lldb::ModuleSP &module_sp);
+
+private:
+ VariableAnnotation createAnnotation(
+ const VarState &var_state, bool is_live,
+ const std::optional<std::string> &location_desc = std::nullopt);
};
} // namespace lldb_private
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index 6755089af39a4..8ce7281a99872 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -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"
@@ -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>
@@ -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());
@@ -347,3 +350,67 @@ 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);
+ dict_sp->AddItem(
+ "start_address",
+ std::make_shared<StructuredData::UnsignedInteger>(ann.start_address));
+ dict_sp->AddItem(
+ "end_address",
+ std::make_shared<StructuredData::UnsignedInteger>(ann.end_address));
+ 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;
+}
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index f2ed1f7395346..9786823676275 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -302,14 +302,39 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine(
std::vector<std::string>
VariableAnnotator::annotate(Instruction &inst, Target &target,
const 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();
+ events.push_back(display_string);
+ }
+
+ return events;
+}
+
+std::vector<VariableAnnotation>
+VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
+ const 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());
+ for (const auto &KV : Live_) {
+ annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ }
Live_.clear();
- return events;
+ return annotations;
}
// Resolve function/block at this *file* address.
@@ -319,10 +344,11 @@ 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());
+ for (const auto &KV : Live_) {
+ annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ }
Live_.clear();
- return events;
+ return annotations;
}
// Collect in-scope variables for this instruction into Current.
@@ -376,8 +402,35 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
if (loc.empty())
continue;
- Current.try_emplace(v->GetID(),
- VarState{std::string(name), std::string(loc)});
+ lldb::addr_t start_addr = inst.GetAddress().GetFileAddress();
+ lldb::addr_t end_addr = LLDB_INVALID_ADDRESS;
+ if (entry.file_range.has_value()) {
+ start_addr = entry.file_range->GetBaseAddress().GetFileAddress();
+ end_addr = start_addr + entry.file_range->GetByteSize();
+ }
+
+ 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.try_emplace(
+ v->GetID(), VarState{std::string(name), std::string(loc), start_addr,
+ end_addr, entry.expr->GetRegisterKind(), decl_file,
+ decl_line, type_name});
}
// Diff Live_ → Current.
@@ -387,24 +440,41 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
auto it = Live_.find(KV.first);
if (it == Live_.end()) {
// Newly live.
- events.emplace_back(
- llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
+ annotations.push_back(createAnnotation(KV.second, true));
} else if (it->second.last_loc != KV.second.last_loc) {
// Location changed.
- events.emplace_back(
- llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str());
+ annotations.push_back(createAnnotation(KV.second, true));
}
}
- // 2) Ends: anything that was live but is not in Current becomes <undef>.
+ // 2) Ends: anything that was live but is not in Current becomes
+ // <kUndefLocation>.
for (const auto &KV : Live_) {
- if (!Current.count(KV.first))
- events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str());
+ if (!Current.count(KV.first)) {
+ annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ }
}
// Commit new state.
Live_ = std::move(Current);
- return events;
+ return annotations;
+}
+
+VariableAnnotation VariableAnnotator::createAnnotation(
+ const VarState &var_state, bool is_live,
+ const std::optional<std::string> &location_desc) {
+ VariableAnnotation annotation;
+ annotation.variable_name = var_state.name;
+ annotation.location_description =
+ location_desc.has_value() ? *location_desc : var_state.last_loc;
+ annotation.start_address = var_state.start_address;
+ annotation.end_address = var_state.end_address;
+ annotation.is_live = is_live;
+ annotation.register_kind = var_state.register_kind;
+ annotation.decl_file = var_state.decl_file;
+ annotation.decl_line = var_state.decl_line;
+ annotation.type_name = var_state.type_name;
+ return annotation;
}
void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index f107efbddddeb..b32ddfbf8cb97 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -116,3 +116,174 @@ def test_seed_reg_const_undef(self):
print(out)
self.assertRegex(out, r"\b(i|argc)\s*=\s*(DW_OP_reg\d+\b|R[A-Z0-9]+)")
self.assertNotIn("<decoding error>", out)
+
+ @no_debug_info_test
+ @skipIf(archs=no_match(["x86_64"]))
+ def test_structured_annotations_api(self):
+ """Test GetVariableAnnotations() API returns structured data"""
+ obj = self._build_obj("d_original_example.o")
+ target = self._create_target(obj)
+
+ main_symbols = target.FindSymbols("main")
+ self.assertTrue(main_symbols.IsValid() and main_symbols.GetSize() > 0,
+ "Could not find 'main' symbol")
+
+ main_symbol = main_symbols.GetContextAtIndex(0).GetSymbol()
+ start_addr = main_symbol.GetStartAddress()
+ self.assertTrue(start_addr.IsValid(), "Invalid start address for main")
+
+ instructions = target.ReadInstructions(start_addr, 16)
+ self.assertGreater(instructions.GetSize(), 0, "No instructions read")
+
+ print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
+
+ # Track what we find
+ found_annotations = False
+ found_variables = set()
+
+ # Track variable locations to detect changes (for selective printing)
+ prev_locations = {}
+
+ # Test each instruction
+ for i in range(instructions.GetSize()):
+ inst = instructions.GetInstructionAtIndex(i)
+ self.assertTrue(inst.IsValid(), f"Invalid instruction at index {i}")
+
+ annotations = inst.GetVariableAnnotations(target)
+
+ self.assertIsInstance(annotations, lldb.SBStructuredData,
+ "GetVariableAnnotations should return SBStructuredData")
+
+ if annotations.GetSize() > 0:
+ found_annotations = True
+
+ # Track current locations and detect changes
+ current_locations = {}
+ should_print = False
+
+ # Validate each annotation
+ for j in range(annotations.GetSize()):
+ ann = annotations.GetItemAtIndex(j)
+ self.assertTrue(ann.IsValid(),
+ f"Invalid annotation at index {j}")
+
+ self.assertEqual(ann.GetType(), lldb.eStructuredDataTypeDictionary,
+ "Each annotation should be a dictionary")
+
+ var_name_obj = ann.GetValueForKey("variable_name")
+ self.assertTrue(var_name_obj.IsValid(),
+ "Missing 'variable_name' field")
+
+ location_obj = ann.GetValueForKey("location_description")
+ self.assertTrue(location_obj.IsValid(),
+ "Missing 'location_description' field")
+
+ is_live_obj = ann.GetValueForKey("is_live")
+ self.assertTrue(is_live_obj.IsValid(),
+ "Missing 'is_live' field")
+
+ start_addr_obj = ann.GetValueForKey("start_address")
+ self.assertTrue(start_addr_obj.IsValid(),
+ "Missing 'start_address' field")
+
+ end_addr_obj = ann.GetValueForKey("end_address")
+ self.assertTrue(end_addr_obj.IsValid(),
+ "Missing 'end_address' field")
+
+ register_kind_obj = ann.GetValueForKey("register_kind")
+ self.assertTrue(register_kind_obj.IsValid(),
+ "Missing 'register_kind' field")
+
+ # Extract and validate values
+ var_name = var_name_obj.GetStringValue(1024)
+ location = location_obj.GetStringValue(1024)
+ is_live = is_live_obj.GetBooleanValue()
+ start_addr = start_addr_obj.GetUnsignedIntegerValue()
+ end_addr = end_addr_obj.GetUnsignedIntegerValue()
+ register_kind = register_kind_obj.GetUnsignedIntegerValue()
+
+ # Validate types and values
+ self.assertIsInstance(var_name, str, "variable_name should be string")
+ self.assertGreater(len(var_name), 0, "variable_name should not be empty")
+
+ self.assertIsInstance(location, str, "location_description should be string")
+ self.assertGreater(len(location), 0, "location_description should not be empty")
+
+ self.assertIsInstance(is_live, bool, "is_live should be boolean")
+
+ self.assertIsInstance(start_addr, int, "start_address should be integer")
+ self.assertIsInstance(end_addr, int, "end_address should be integer")
+ self.assertGreater(end_addr, start_addr,
+ "end_address should be greater than start_address")
+
+ self.assertIsInstance(register_kind, int, "register_kind should be integer")
+
+ # Check for expected variables in this function
+ self.assertIn(var_name, ["argc", "argv", "i"],
+ f"Unexpected variable name: {var_name}")
+
+ found_variables.add(var_name)
+
+ # Track current location
+ current_locations[var_name] = location
+
+ # Detect if this is a new variable or location changed
+ if var_name not in prev_locations or prev_locations[var_name] != location:
+ should_print = True
+
+ # Check optional fields (may or may not be present)
+ decl_file_obj = ann.GetValueForKey("decl_file")
+ if decl_file_obj.IsValid():
+ decl_file = decl_file_obj.GetStringValue(1024)
+ self.assertIsInstance(decl_file, str)
+ self.assertIn("d_original_example.c", decl_file,
+ f"Expected source file d_original_example.c in {decl_file}")
+
+ decl_line_obj = ann.GetValueForKey("decl_line")
+ if decl_line_obj.IsValid():
+ decl_line = decl_line_obj.GetUnsignedIntegerValue()
+ self.assertIsInstance(decl_line, int)
+
+ # Validate declaration line matches the source code (according to d_original_example.c)
+ if var_name == "argc":
+ self.assertEqual(decl_line, 3, "argc should be declared on line 3")
+ elif var_name == "argv":
+ self.assertEqual(decl_line, 3, "argv should be declared on line 3")
+ elif var_name == "i":
+ self.assertEqual(decl_line, 4, "i should be declared on line 4")
+
+ type_name_obj = ann.GetValueForKey("type_name")
+ if type_name_obj.IsValid():
+ type_name = type_name_obj.GetStringValue(1024)
+ self.assertIsInstance(type_name, str)
+
+ # Validate declaration line matches the source code (according to d_original_example.c)
+ if var_name == "argc":
+ self.assertEqual(type_name, "int", "argc should be type 'int'")
+ elif var_name == "argv":
+ self.assertEqual(type_name, "char **", "argv should be type 'char **'")
+ elif var_name == "i":
+ self.assertEqual(type_name, "int", "i should be type 'int'")
+
+ # Only print if something happened (location changed or variable appeared/disappeared)
+ if should_print or len(current_locations) != len(prev_locations):
+ print(f"\nInstruction {i} at {inst.GetAddress()}: {annotations.GetSize()} annotations")
+ for var_name, location in current_locations.items():
+ change_marker = " <- CHANGED" if var_name in prev_locations and prev_locations[var_name] != location else ""
+ new_marker = " <- NEW" if var_name not in prev_locations else ""
+ print(f" {var_name} = {location}{change_marker}{new_marker}")
+ # Check for disappeared variables
+ for var_name in prev_locations:
+ if var_name not in current_locations:
+ print(f" {var_name} <- GONE")
+
+ # Update tracking
+ prev_locations = current_locations.copy()
+
+ self.assertTrue(found_annotations,
+ "Should find at least one instruction with variable annotations")
+
+ self.assertGreater(len(found_variables), 0,
+ "Should find at least one variable")
+
+ print(f"\nTest complete. Found variables: {found_variables}")
>From 199a90989a389c1894f2a19b3f792dc47e489911 Mon Sep 17 00:00:00 2001
From: n2h9 <13541181+n2h9 at users.noreply.github.com>
Date: Tue, 28 Oct 2025 19:12:42 +0100
Subject: [PATCH 02/21] Update lldb/include/lldb/Core/Disassembler.h
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/include/lldb/Core/Disassembler.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 0539d4919c096..0f2407d72e5eb 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -566,7 +566,7 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>,
const Disassembler &operator=(const Disassembler &) = delete;
};
-/// Structured data for a single variable annotation
+/// Structured data for a single variable annotation.
struct VariableAnnotation {
std::string variable_name;
std::string location_description; // e.g., "r15", "undef", "const_0"
>From ea1bf46e6b5c299a4ef1f6abd415b31e08a504e3 Mon Sep 17 00:00:00 2001
From: n2h9 <13541181+n2h9 at users.noreply.github.com>
Date: Tue, 28 Oct 2025 19:14:58 +0100
Subject: [PATCH 03/21] Update lldb/include/lldb/Core/Disassembler.h
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/include/lldb/Core/Disassembler.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 0f2407d72e5eb..97e392008adfd 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -615,7 +615,7 @@ class VariableAnnotator {
/// `Live_`. Returns structured data for all variables relevant at this
/// instruction.
std::vector<VariableAnnotation>
- annotateStructured(Instruction &inst, Target &target,
+ AnnotateStructured(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp);
private:
>From 0331516bb922c2f052745a657ec62542dd3e3a77 Mon Sep 17 00:00:00 2001
From: n2h9 <13541181+n2h9 at users.noreply.github.com>
Date: Tue, 28 Oct 2025 19:17:12 +0100
Subject: [PATCH 04/21] Update
lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
.../TestVariableAnnotationsDisassembler.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index b32ddfbf8cb97..eccae3a3ac36c 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -137,7 +137,7 @@ def test_structured_annotations_api(self):
print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
- # Track what we find
+ # Track what we find.
found_annotations = False
found_variables = set()
>From 2449b6c3bb64205313a4ef953c3b4db78e0c8c91 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Thu, 30 Oct 2025 19:26:58 +0100
Subject: [PATCH 05/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: remove braces around single line ifs and cycles
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/source/API/SBInstruction.cpp | 18 ++++++------------
lldb/source/Core/Disassembler.cpp | 21 +++++++--------------
2 files changed, 13 insertions(+), 26 deletions(-)
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index 8ce7281a99872..f66f4b5d26f9f 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -357,23 +357,20 @@ SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
SBStructuredData result;
- if (!m_opaque_sp || !m_opaque_sp->IsValid() || !target.IsValid()) {
+ 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) {
+ if (!inst_sp || !target_sp)
return result;
- }
const Address &addr = inst_sp->GetAddress();
ModuleSP module_sp = addr.GetModule();
- if (!module_sp) {
+ if (!module_sp)
return result;
- }
VariableAnnotator annotator;
std::vector<VariableAnnotation> annotations =
@@ -396,17 +393,14 @@ SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
dict_sp->AddItem(
"register_kind",
std::make_shared<StructuredData::UnsignedInteger>(ann.register_kind));
- if (ann.decl_file.has_value()) {
+ if (ann.decl_file.has_value())
dict_sp->AddStringItem("decl_file", *ann.decl_file);
- }
- if (ann.decl_line.has_value()) {
+ 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()) {
+ if (ann.type_name.has_value())
dict_sp->AddStringItem("type_name", *ann.type_name);
- }
array_sp->AddItem(dict_sp);
}
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 9786823676275..140b8ce9a7d0d 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -330,9 +330,8 @@ VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
// If we lost module context, mark all live variables as undefined.
if (!module_sp) {
- for (const auto &KV : Live_) {
+ for (const auto &KV : Live_)
annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
- }
Live_.clear();
return annotations;
}
@@ -344,9 +343,8 @@ VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) ||
!sc.function) {
// No function context: everything dies here.
- for (const auto &KV : Live_) {
+ for (const auto &KV : Live_)
annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
- }
Live_.clear();
return annotations;
}
@@ -416,16 +414,13 @@ VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
const Declaration &decl = v->GetDeclaration();
if (decl.GetFile()) {
decl_file = decl.GetFile().GetFilename().AsCString();
- if (decl.GetLine() > 0) {
+ if (decl.GetLine() > 0)
decl_line = decl.GetLine();
- }
}
- if (Type *type = v->GetType()) {
- if (const char *type_str = type->GetName().AsCString()) {
+ if (Type *type = v->GetType())
+ if (const char *type_str = type->GetName().AsCString())
type_name = type_str;
- }
- }
Current.try_emplace(
v->GetID(), VarState{std::string(name), std::string(loc), start_addr,
@@ -449,11 +444,9 @@ VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
// 2) Ends: anything that was live but is not in Current becomes
// <kUndefLocation>.
- for (const auto &KV : Live_) {
- if (!Current.count(KV.first)) {
+ for (const auto &KV : Live_)
+ if (!Current.count(KV.first))
annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
- }
- }
// Commit new state.
Live_ = std::move(Current);
>From 839ac8e88356491999ee30e433f837e6871f2cf3 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Fri, 31 Oct 2025 21:35:43 +0100
Subject: [PATCH 06/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: wrap print instructions with if self.TraceOn():
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
.../TestVariableAnnotationsDisassembler.py | 29 ++++++++++---------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index eccae3a3ac36c..b32f5f821af13 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -135,7 +135,8 @@ def test_structured_annotations_api(self):
instructions = target.ReadInstructions(start_addr, 16)
self.assertGreater(instructions.GetSize(), 0, "No instructions read")
- print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
+ if self.TraceOn():
+ print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
# Track what we find.
found_annotations = False
@@ -265,17 +266,18 @@ def test_structured_annotations_api(self):
elif var_name == "i":
self.assertEqual(type_name, "int", "i should be type 'int'")
- # Only print if something happened (location changed or variable appeared/disappeared)
- if should_print or len(current_locations) != len(prev_locations):
- print(f"\nInstruction {i} at {inst.GetAddress()}: {annotations.GetSize()} annotations")
- for var_name, location in current_locations.items():
- change_marker = " <- CHANGED" if var_name in prev_locations and prev_locations[var_name] != location else ""
- new_marker = " <- NEW" if var_name not in prev_locations else ""
- print(f" {var_name} = {location}{change_marker}{new_marker}")
- # Check for disappeared variables
- for var_name in prev_locations:
- if var_name not in current_locations:
- print(f" {var_name} <- GONE")
+ if self.TraceOn():
+ # Only print if something happened (location changed or variable appeared/disappeared)
+ if should_print or len(current_locations) != len(prev_locations):
+ print(f"\nInstruction {i} at {inst.GetAddress()}: {annotations.GetSize()} annotations")
+ for var_name, location in current_locations.items():
+ change_marker = " <- CHANGED" if var_name in prev_locations and prev_locations[var_name] != location else ""
+ new_marker = " <- NEW" if var_name not in prev_locations else ""
+ print(f" {var_name} = {location}{change_marker}{new_marker}")
+ # Check for disappeared variables
+ for var_name in prev_locations:
+ if var_name not in current_locations:
+ print(f" {var_name} <- GONE")
# Update tracking
prev_locations = current_locations.copy()
@@ -286,4 +288,5 @@ def test_structured_annotations_api(self):
self.assertGreater(len(found_variables), 0,
"Should find at least one variable")
- print(f"\nTest complete. Found variables: {found_variables}")
+ if self.TraceOn():
+ print(f"\nTest complete. Found variables: {found_variables}")
>From 52780241f263ae62c370ad47b37de926dff00f88 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Fri, 31 Oct 2025 21:38:17 +0100
Subject: [PATCH 07/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: end comments with dots in python file
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
.../TestVariableAnnotationsDisassembler.py | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index b32f5f821af13..4fe49a2b44cbd 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -142,7 +142,7 @@ def test_structured_annotations_api(self):
found_annotations = False
found_variables = set()
- # Track variable locations to detect changes (for selective printing)
+ # Track variable locations to detect changes (for selective printing).
prev_locations = {}
# Test each instruction
@@ -158,11 +158,11 @@ def test_structured_annotations_api(self):
if annotations.GetSize() > 0:
found_annotations = True
- # Track current locations and detect changes
+ # Track current locations and detect changes.
current_locations = {}
should_print = False
- # Validate each annotation
+ # Validate each annotation.
for j in range(annotations.GetSize()):
ann = annotations.GetItemAtIndex(j)
self.assertTrue(ann.IsValid(),
@@ -195,7 +195,7 @@ def test_structured_annotations_api(self):
self.assertTrue(register_kind_obj.IsValid(),
"Missing 'register_kind' field")
- # Extract and validate values
+ # Extract and validate values.
var_name = var_name_obj.GetStringValue(1024)
location = location_obj.GetStringValue(1024)
is_live = is_live_obj.GetBooleanValue()
@@ -203,7 +203,7 @@ def test_structured_annotations_api(self):
end_addr = end_addr_obj.GetUnsignedIntegerValue()
register_kind = register_kind_obj.GetUnsignedIntegerValue()
- # Validate types and values
+ # Validate types and values.
self.assertIsInstance(var_name, str, "variable_name should be string")
self.assertGreater(len(var_name), 0, "variable_name should not be empty")
@@ -219,20 +219,20 @@ def test_structured_annotations_api(self):
self.assertIsInstance(register_kind, int, "register_kind should be integer")
- # Check for expected variables in this function
+ # Check for expected variables in this function.
self.assertIn(var_name, ["argc", "argv", "i"],
f"Unexpected variable name: {var_name}")
found_variables.add(var_name)
- # Track current location
+ # Track current location.
current_locations[var_name] = location
- # Detect if this is a new variable or location changed
+ # Detect if this is a new variable or location changed.
if var_name not in prev_locations or prev_locations[var_name] != location:
should_print = True
- # Check optional fields (may or may not be present)
+ # Check optional fields (may or may not be present).
decl_file_obj = ann.GetValueForKey("decl_file")
if decl_file_obj.IsValid():
decl_file = decl_file_obj.GetStringValue(1024)
@@ -245,7 +245,7 @@ def test_structured_annotations_api(self):
decl_line = decl_line_obj.GetUnsignedIntegerValue()
self.assertIsInstance(decl_line, int)
- # Validate declaration line matches the source code (according to d_original_example.c)
+ # Validate declaration line matches the source code (according to d_original_example.c).
if var_name == "argc":
self.assertEqual(decl_line, 3, "argc should be declared on line 3")
elif var_name == "argv":
@@ -258,7 +258,7 @@ def test_structured_annotations_api(self):
type_name = type_name_obj.GetStringValue(1024)
self.assertIsInstance(type_name, str)
- # Validate declaration line matches the source code (according to d_original_example.c)
+ # Validate declaration line matches the source code (according to d_original_example.c).
if var_name == "argc":
self.assertEqual(type_name, "int", "argc should be type 'int'")
elif var_name == "argv":
@@ -267,19 +267,19 @@ def test_structured_annotations_api(self):
self.assertEqual(type_name, "int", "i should be type 'int'")
if self.TraceOn():
- # Only print if something happened (location changed or variable appeared/disappeared)
+ # Only print if something happened (location changed or variable appeared/disappeared).
if should_print or len(current_locations) != len(prev_locations):
print(f"\nInstruction {i} at {inst.GetAddress()}: {annotations.GetSize()} annotations")
for var_name, location in current_locations.items():
change_marker = " <- CHANGED" if var_name in prev_locations and prev_locations[var_name] != location else ""
new_marker = " <- NEW" if var_name not in prev_locations else ""
print(f" {var_name} = {location}{change_marker}{new_marker}")
- # Check for disappeared variables
+ # Check for disappeared variables.
for var_name in prev_locations:
if var_name not in current_locations:
print(f" {var_name} <- GONE")
- # Update tracking
+ # Update tracking.
prev_locations = current_locations.copy()
self.assertTrue(found_annotations,
>From 7055cc06a2d420abea1ceb35207acb6395a35bc4 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 13:09:33 +0100
Subject: [PATCH 08/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: rename class method to much declaration and LLDB's
coding style
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/source/API/SBInstruction.cpp | 2 +-
lldb/source/Core/Disassembler.cpp | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index f66f4b5d26f9f..65a75354e0ed1 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -374,7 +374,7 @@ SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
VariableAnnotator annotator;
std::vector<VariableAnnotation> annotations =
- annotator.annotateStructured(*inst_sp, *target_sp, module_sp);
+ annotator.AnnotateStructured(*inst_sp, *target_sp, module_sp);
auto array_sp = std::make_shared<StructuredData::Array>();
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 140b8ce9a7d0d..0396b1391b22c 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -302,7 +302,7 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine(
std::vector<std::string>
VariableAnnotator::annotate(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp) {
- auto structured_annotations = annotateStructured(inst, target, module_sp);
+ auto structured_annotations = AnnotateStructured(inst, target, module_sp);
std::vector<std::string> events;
events.reserve(structured_annotations.size());
@@ -324,7 +324,7 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
}
std::vector<VariableAnnotation>
-VariableAnnotator::annotateStructured(Instruction &inst, Target &target,
+VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp) {
std::vector<VariableAnnotation> annotations;
>From 4cee0d76b072801450a41595451346aa9a3b7cbb Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 13:22:29 +0100
Subject: [PATCH 09/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: update comments to be Doxygen-style
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 97e392008adfd..49df7cd006151 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -569,16 +569,22 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>,
/// Structured data for a single variable annotation.
struct VariableAnnotation {
std::string variable_name;
- std::string location_description; // e.g., "r15", "undef", "const_0"
- lldb::addr_t start_address; // Where this annotation starts being valid
- lldb::addr_t end_address; // Where this annotation ends being valid
- bool is_live; // Whether variable is live at this instruction
- lldb::RegisterKind
- register_kind; // Register numbering scheme for location interpretation
- std::optional<std::string>
- decl_file; // Source file where variable was declared
- std::optional<uint32_t> decl_line; // Line number where variable was declared
- std::optional<std::string> type_name; // Variable's type name
+ /// Location description (e.g., "r15", "undef", "const_0").
+ std::string location_description;
+ /// Where this annotation starts being valid.
+ lldb::addr_t start_address;
+ /// Where this annotation ends being valid.
+ lldb::addr_t end_address;
+ /// Whether variable is live at this instruction.
+ bool is_live;
+ /// Register numbering scheme for location interpretation.
+ lldb::RegisterKind register_kind;
+ /// 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
>From c955af308f31cb9639a5b36d8dec58135731e9c6 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 14:01:08 +0100
Subject: [PATCH 10/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: remove VarState and keep only VariableAnnotation
struct
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 23 +---------
lldb/source/Core/Disassembler.cpp | 63 ++++++++++++++-------------
2 files changed, 33 insertions(+), 53 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 49df7cd006151..08649e4521d9e 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -590,24 +590,8 @@ struct VariableAnnotation {
/// 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;
- /// Address range where this variable state is valid.
- lldb::addr_t start_address;
- lldb::addr_t end_address;
- /// Register numbering scheme for location interpretation.
- lldb::RegisterKind register_kind;
-
- std::optional<std::string> decl_file;
- std::optional<uint32_t> decl_line;
- std::optional<std::string> type_name;
- };
-
// 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> Live_;
static constexpr const char *kUndefLocation = "undef";
@@ -623,11 +607,6 @@ class VariableAnnotator {
std::vector<VariableAnnotation>
AnnotateStructured(Instruction &inst, Target &target,
const lldb::ModuleSP &module_sp);
-
-private:
- VariableAnnotation createAnnotation(
- const VarState &var_state, bool is_live,
- const std::optional<std::string> &location_desc = std::nullopt);
};
} // namespace lldb_private
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 0396b1391b22c..664947437096d 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -330,8 +330,12 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
// If we lost module context, mark all live variables as undefined.
if (!module_sp) {
- for (const auto &KV : Live_)
- annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ for (const auto &KV : Live_) {
+ auto annotation_entity = KV.second;
+ annotation_entity.is_live = false;
+ annotation_entity.location_description = kUndefLocation;
+ annotations.push_back(annotation_entity);
+ }
Live_.clear();
return annotations;
}
@@ -343,8 +347,12 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) ||
!sc.function) {
// No function context: everything dies here.
- for (const auto &KV : Live_)
- annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ for (const auto &KV : Live_) {
+ auto annotation_entity = KV.second;
+ annotation_entity.is_live = false;
+ annotation_entity.location_description = kUndefLocation;
+ annotations.push_back(annotation_entity);
+ }
Live_.clear();
return annotations;
}
@@ -373,7 +381,7 @@ VariableAnnotator::AnnotateStructured(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;
for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) {
lldb::VariableSP v = var_list.GetVariableAtIndex(i);
@@ -422,10 +430,11 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
if (const char *type_str = type->GetName().AsCString())
type_name = type_str;
- Current.try_emplace(
- v->GetID(), VarState{std::string(name), std::string(loc), start_addr,
- end_addr, entry.expr->GetRegisterKind(), decl_file,
- decl_line, type_name});
+ Current.try_emplace(v->GetID(),
+ VariableAnnotation{std::string(name), std::string(loc),
+ start_addr, end_addr, true,
+ entry.expr->GetRegisterKind(),
+ decl_file, decl_line, type_name});
}
// Diff Live_ → Current.
@@ -435,41 +444,33 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
auto it = Live_.find(KV.first);
if (it == Live_.end()) {
// Newly live.
- annotations.push_back(createAnnotation(KV.second, true));
- } 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.
- annotations.push_back(createAnnotation(KV.second, true));
+ 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
// <kUndefLocation>.
for (const auto &KV : Live_)
- if (!Current.count(KV.first))
- annotations.push_back(createAnnotation(KV.second, false, kUndefLocation));
+ if (!Current.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 annotations;
}
-VariableAnnotation VariableAnnotator::createAnnotation(
- const VarState &var_state, bool is_live,
- const std::optional<std::string> &location_desc) {
- VariableAnnotation annotation;
- annotation.variable_name = var_state.name;
- annotation.location_description =
- location_desc.has_value() ? *location_desc : var_state.last_loc;
- annotation.start_address = var_state.start_address;
- annotation.end_address = var_state.end_address;
- annotation.is_live = is_live;
- annotation.register_kind = var_state.register_kind;
- annotation.decl_file = var_state.decl_file;
- annotation.decl_line = var_state.decl_line;
- annotation.type_name = var_state.type_name;
- return annotation;
-}
-
void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch,
const ExecutionContext &exe_ctx,
bool mixed_source_and_assembly,
>From 4d8706dc5441a4d8912e4702a259017403cdd97b Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 19:17:35 +0100
Subject: [PATCH 11/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: update both annotate and AnnotateStructured to use
shared pointer tp module by value
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 8 ++++----
lldb/source/Core/Disassembler.cpp | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 08649e4521d9e..8fee9575f52bc 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -599,14 +599,14 @@ class VariableAnnotator {
/// 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);
+ lldb::ModuleSP module_sp);
/// Compute structured annotation data for a single instruction and update
/// `Live_`. Returns structured data for all variables relevant at this
/// instruction.
- std::vector<VariableAnnotation>
- AnnotateStructured(Instruction &inst, Target &target,
- const lldb::ModuleSP &module_sp);
+ std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst,
+ Target &target,
+ lldb::ModuleSP module_sp);
};
} // namespace lldb_private
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 664947437096d..789145431adda 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -299,9 +299,9 @@ 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;
@@ -325,7 +325,7 @@ VariableAnnotator::annotate(Instruction &inst, Target &target,
std::vector<VariableAnnotation>
VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
- const lldb::ModuleSP &module_sp) {
+ lldb::ModuleSP module_sp) {
std::vector<VariableAnnotation> annotations;
// If we lost module context, mark all live variables as undefined.
>From 573a6a1921d2a24bb1594170bc26d5604328a182 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 19:24:13 +0100
Subject: [PATCH 12/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: update both annotate to Annotate class function
name to follow lldb coding style
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 2 +-
lldb/source/Core/Disassembler.cpp | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 8fee9575f52bc..b88a908833207 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -598,7 +598,7 @@ class VariableAnnotator {
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,
+ std::vector<std::string> Annotate(Instruction &inst, Target &target,
lldb::ModuleSP module_sp);
/// Compute structured annotation data for a single instruction and update
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 789145431adda..4774302dda708 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -299,7 +299,7 @@ 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,
+std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst,
Target &target,
lldb::ModuleSP module_sp) {
auto structured_annotations = AnnotateStructured(inst, target, module_sp);
@@ -740,7 +740,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, ' ');
>From c688f7004720f48ddb290323752f6e3ab902a242 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 19:27:03 +0100
Subject: [PATCH 13/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: update descriptive comment for AnnotateStructured
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index b88a908833207..709bd713165e8 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -601,9 +601,7 @@ class VariableAnnotator {
std::vector<std::string> Annotate(Instruction &inst, Target &target,
lldb::ModuleSP module_sp);
- /// Compute structured annotation data for a single instruction and update
- /// `Live_`. Returns structured data for all variables relevant at this
- /// instruction.
+ /// Returns structured data for all variables relevant at this instruction.
std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst,
Target &target,
lldb::ModuleSP module_sp);
>From 31e91cf933c0068e214550ee91b4353bf2efc0f2 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 1 Nov 2025 20:16:23 +0100
Subject: [PATCH 14/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: use AddressRange instead of start_address and
end_address
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 6 ++----
lldb/source/API/SBInstruction.cpp | 16 ++++++++++------
lldb/source/Core/Disassembler.cpp | 13 +++----------
3 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 709bd713165e8..8a86ae4ddb66e 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -571,14 +571,12 @@ struct VariableAnnotation {
std::string variable_name;
/// Location description (e.g., "r15", "undef", "const_0").
std::string location_description;
- /// Where this annotation starts being valid.
- lldb::addr_t start_address;
- /// Where this annotation ends being valid.
- lldb::addr_t end_address;
/// 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.
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index 65a75354e0ed1..992bd3c3fdd5d 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -384,12 +384,16 @@ SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
dict_sp->AddStringItem("variable_name", ann.variable_name);
dict_sp->AddStringItem("location_description", ann.location_description);
dict_sp->AddBooleanItem("is_live", ann.is_live);
- dict_sp->AddItem(
- "start_address",
- std::make_shared<StructuredData::UnsignedInteger>(ann.start_address));
- dict_sp->AddItem(
- "end_address",
- std::make_shared<StructuredData::UnsignedInteger>(ann.end_address));
+ 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));
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 4774302dda708..f4327e4bcce85 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -408,13 +408,6 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
if (loc.empty())
continue;
- lldb::addr_t start_addr = inst.GetAddress().GetFileAddress();
- lldb::addr_t end_addr = LLDB_INVALID_ADDRESS;
- if (entry.file_range.has_value()) {
- start_addr = entry.file_range->GetBaseAddress().GetFileAddress();
- end_addr = start_addr + entry.file_range->GetByteSize();
- }
-
std::optional<std::string> decl_file;
std::optional<uint32_t> decl_line;
std::optional<std::string> type_name;
@@ -432,9 +425,9 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
Current.try_emplace(v->GetID(),
VariableAnnotation{std::string(name), std::string(loc),
- start_addr, end_addr, true,
- entry.expr->GetRegisterKind(),
- decl_file, decl_line, type_name});
+ true, entry.expr->GetRegisterKind(),
+ entry.file_range, decl_file,
+ decl_line, type_name});
}
// Diff Live_ → Current.
>From f805dbb7909c9a7cfe9b23cc795d8c00556405cf Mon Sep 17 00:00:00 2001
From: n2h9 <13541181+n2h9 at users.noreply.github.com>
Date: Thu, 6 Nov 2025 20:59:54 +0100
Subject: [PATCH 15/21] Update lldb/include/lldb/Core/Disassembler.h
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/include/lldb/Core/Disassembler.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 8a86ae4ddb66e..d24a8bf593661 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -589,7 +589,7 @@ struct VariableAnnotation {
/// per-instruction "events" like `name = RDI` or `name = <undef>`.
class VariableAnnotator {
// Live state from the previous instruction, keyed by Variable::GetID().
- llvm::DenseMap<lldb::user_id_t, VariableAnnotation> Live_;
+ llvm::DenseMap<lldb::user_id_t, VariableAnnotation> m_live_vars;
static constexpr const char *kUndefLocation = "undef";
>From f03a4485bc8d2c985a39e0fb671fa57cd2d4cd61 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Thu, 6 Nov 2025 21:10:45 +0100
Subject: [PATCH 16/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: rename Live_ to m_live_vars to follow lldb codding
style
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/source/Core/Disassembler.cpp | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index f4327e4bcce85..43238f4c316d2 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -330,13 +330,13 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
// If we lost module context, mark all live variables as undefined.
if (!module_sp) {
- for (const auto &KV : Live_) {
+ 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);
}
- Live_.clear();
+ m_live_vars.clear();
return annotations;
}
@@ -347,13 +347,13 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) ||
!sc.function) {
// No function context: everything dies here.
- for (const auto &KV : Live_) {
+ 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);
}
- Live_.clear();
+ m_live_vars.clear();
return annotations;
}
@@ -430,12 +430,12 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
decl_line, type_name});
}
- // Diff Live_ → Current.
+ // Diff m_live_vars → Current.
- // 1) Starts/changes: iterate Current and compare with Live_.
+ // 1) Starts/changes: iterate Current and compare with m_live_vars.
for (const auto &KV : Current) {
- auto it = Live_.find(KV.first);
- if (it == Live_.end()) {
+ auto it = m_live_vars.find(KV.first);
+ if (it == m_live_vars.end()) {
// Newly live.
auto annotation_entity = KV.second;
annotation_entity.is_live = true;
@@ -451,7 +451,7 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
// 2) Ends: anything that was live but is not in Current becomes
// <kUndefLocation>.
- for (const auto &KV : Live_)
+ for (const auto &KV : m_live_vars)
if (!Current.count(KV.first)) {
auto annotation_entity = KV.second;
annotation_entity.is_live = false;
@@ -460,7 +460,7 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
}
// Commit new state.
- Live_ = std::move(Current);
+ m_live_vars = std::move(Current);
return annotations;
}
>From f10922f01bfbea332ffd58fb595fbbad3efefb7e Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Thu, 6 Nov 2025 21:17:15 +0100
Subject: [PATCH 17/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: rename Current to current_vars to follow lldb
codding style and to be consistent with m_live_vars
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/source/Core/Disassembler.cpp | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 43238f4c316d2..6e259326cb887 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -381,7 +381,7 @@ VariableAnnotator::AnnotateStructured(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, VariableAnnotation> 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);
@@ -423,17 +423,17 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
if (const char *type_str = type->GetName().AsCString())
type_name = type_str;
- Current.try_emplace(v->GetID(),
+ 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 m_live_vars → Current.
+ // Diff m_live_vars → current_vars.
- // 1) Starts/changes: iterate Current and compare with m_live_vars.
- for (const auto &KV : Current) {
+ // 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.
@@ -449,10 +449,10 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
}
}
- // 2) Ends: anything that was live but is not in Current becomes
+ // 2) Ends: anything that was live but is not in current_vars becomes
// <kUndefLocation>.
for (const auto &KV : m_live_vars)
- if (!Current.count(KV.first)) {
+ if (!current_vars.count(KV.first)) {
auto annotation_entity = KV.second;
annotation_entity.is_live = false;
annotation_entity.location_description = kUndefLocation;
@@ -460,7 +460,7 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
}
// Commit new state.
- m_live_vars = std::move(Current);
+ m_live_vars = std::move(current_vars);
return annotations;
}
>From 35c036f3a98124275cd08d8f7a5d278708e065e4 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Mon, 10 Nov 2025 20:00:32 +0100
Subject: [PATCH 18/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: replace llvm::formatv with stream, formatting
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/Core/Disassembler.h | 1 +
lldb/source/Core/Disassembler.cpp | 32 +++++++++++++++------------
2 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index d24a8bf593661..0e69bc4526c3b 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -592,6 +592,7 @@ class VariableAnnotator {
llvm::DenseMap<lldb::user_id_t, VariableAnnotation> m_live_vars;
static constexpr const char *kUndefLocation = "undef";
+ static const std::string kUndefLocationFormatted;
public:
/// Compute annotation strings for a single instruction and update `Live_`.
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index 6e259326cb887..cb9c6eb3441d6 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -286,6 +286,9 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine(
return false;
}
+const std::string VariableAnnotator::kUndefLocationFormatted =
+ llvm::formatv("<{0}>", kUndefLocation).str();
+
// 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
@@ -309,15 +312,16 @@ std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst,
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();
- events.push_back(display_string);
+ llvm::raw_string_ostream os(display_string);
+
+ os << annotation.variable_name;
+ os << " = ";
+ os << (annotation.location_description == VariableAnnotator::kUndefLocation
+ ? VariableAnnotator::kUndefLocationFormatted
+ : annotation.location_description);
+ os.flush();
+
+ events.push_back(std::move(display_string));
}
return events;
@@ -423,11 +427,11 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
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});
+ 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 m_live_vars → current_vars.
>From 6fe62e48c8c09fc9948c5ea08f9f921c84c7756f Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 15 Nov 2025 14:43:55 +0100
Subject: [PATCH 19/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: simplify test_structured_annotations_api
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
.../TestVariableAnnotationsDisassembler.py | 105 +-----------------
1 file changed, 6 insertions(+), 99 deletions(-)
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index 4fe49a2b44cbd..d27c4a14ca9e3 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -138,13 +138,9 @@ def test_structured_annotations_api(self):
if self.TraceOn():
print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
- # Track what we find.
- found_annotations = False
+ expected_vars = ["argc", "argv", "i"]
found_variables = set()
- # Track variable locations to detect changes (for selective printing).
- prev_locations = {}
-
# Test each instruction
for i in range(instructions.GetSize()):
inst = instructions.GetInstructionAtIndex(i)
@@ -155,18 +151,14 @@ def test_structured_annotations_api(self):
self.assertIsInstance(annotations, lldb.SBStructuredData,
"GetVariableAnnotations should return SBStructuredData")
- if annotations.GetSize() > 0:
- found_annotations = True
-
- # Track current locations and detect changes.
- current_locations = {}
- should_print = False
+ self.assertTrue(annotations.GetSize() > 0,
+ "GetVariableAnnotations should return non empty array")
+ if annotations.GetSize() > 0:
# Validate each annotation.
for j in range(annotations.GetSize()):
ann = annotations.GetItemAtIndex(j)
- self.assertTrue(ann.IsValid(),
- f"Invalid annotation at index {j}")
+ self.assertTrue(ann.IsValid(), f"Invalid annotation at index {j}")
self.assertEqual(ann.GetType(), lldb.eStructuredDataTypeDictionary,
"Each annotation should be a dictionary")
@@ -195,98 +187,13 @@ def test_structured_annotations_api(self):
self.assertTrue(register_kind_obj.IsValid(),
"Missing 'register_kind' field")
- # Extract and validate values.
var_name = var_name_obj.GetStringValue(1024)
- location = location_obj.GetStringValue(1024)
- is_live = is_live_obj.GetBooleanValue()
- start_addr = start_addr_obj.GetUnsignedIntegerValue()
- end_addr = end_addr_obj.GetUnsignedIntegerValue()
- register_kind = register_kind_obj.GetUnsignedIntegerValue()
-
- # Validate types and values.
- self.assertIsInstance(var_name, str, "variable_name should be string")
- self.assertGreater(len(var_name), 0, "variable_name should not be empty")
-
- self.assertIsInstance(location, str, "location_description should be string")
- self.assertGreater(len(location), 0, "location_description should not be empty")
-
- self.assertIsInstance(is_live, bool, "is_live should be boolean")
-
- self.assertIsInstance(start_addr, int, "start_address should be integer")
- self.assertIsInstance(end_addr, int, "end_address should be integer")
- self.assertGreater(end_addr, start_addr,
- "end_address should be greater than start_address")
-
- self.assertIsInstance(register_kind, int, "register_kind should be integer")
# Check for expected variables in this function.
- self.assertIn(var_name, ["argc", "argv", "i"],
+ self.assertIn(var_name, expected_vars,
f"Unexpected variable name: {var_name}")
found_variables.add(var_name)
- # Track current location.
- current_locations[var_name] = location
-
- # Detect if this is a new variable or location changed.
- if var_name not in prev_locations or prev_locations[var_name] != location:
- should_print = True
-
- # Check optional fields (may or may not be present).
- decl_file_obj = ann.GetValueForKey("decl_file")
- if decl_file_obj.IsValid():
- decl_file = decl_file_obj.GetStringValue(1024)
- self.assertIsInstance(decl_file, str)
- self.assertIn("d_original_example.c", decl_file,
- f"Expected source file d_original_example.c in {decl_file}")
-
- decl_line_obj = ann.GetValueForKey("decl_line")
- if decl_line_obj.IsValid():
- decl_line = decl_line_obj.GetUnsignedIntegerValue()
- self.assertIsInstance(decl_line, int)
-
- # Validate declaration line matches the source code (according to d_original_example.c).
- if var_name == "argc":
- self.assertEqual(decl_line, 3, "argc should be declared on line 3")
- elif var_name == "argv":
- self.assertEqual(decl_line, 3, "argv should be declared on line 3")
- elif var_name == "i":
- self.assertEqual(decl_line, 4, "i should be declared on line 4")
-
- type_name_obj = ann.GetValueForKey("type_name")
- if type_name_obj.IsValid():
- type_name = type_name_obj.GetStringValue(1024)
- self.assertIsInstance(type_name, str)
-
- # Validate declaration line matches the source code (according to d_original_example.c).
- if var_name == "argc":
- self.assertEqual(type_name, "int", "argc should be type 'int'")
- elif var_name == "argv":
- self.assertEqual(type_name, "char **", "argv should be type 'char **'")
- elif var_name == "i":
- self.assertEqual(type_name, "int", "i should be type 'int'")
-
- if self.TraceOn():
- # Only print if something happened (location changed or variable appeared/disappeared).
- if should_print or len(current_locations) != len(prev_locations):
- print(f"\nInstruction {i} at {inst.GetAddress()}: {annotations.GetSize()} annotations")
- for var_name, location in current_locations.items():
- change_marker = " <- CHANGED" if var_name in prev_locations and prev_locations[var_name] != location else ""
- new_marker = " <- NEW" if var_name not in prev_locations else ""
- print(f" {var_name} = {location}{change_marker}{new_marker}")
- # Check for disappeared variables.
- for var_name in prev_locations:
- if var_name not in current_locations:
- print(f" {var_name} <- GONE")
-
- # Update tracking.
- prev_locations = current_locations.copy()
-
- self.assertTrue(found_annotations,
- "Should find at least one instruction with variable annotations")
-
- self.assertGreater(len(found_variables), 0,
- "Should find at least one variable")
-
if self.TraceOn():
print(f"\nTest complete. Found variables: {found_variables}")
>From 651b407a5f955b21b9b514811570cb3f07df7b3e Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sat, 15 Nov 2025 16:27:16 +0100
Subject: [PATCH 20/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: remove target and module from Annotate methods
signature
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
lldb/include/lldb/API/SBInstruction.h | 2 +-
lldb/include/lldb/Core/Disassembler.h | 7 ++-----
lldb/source/API/SBInstruction.cpp | 13 +++++--------
lldb/source/Core/Disassembler.cpp | 15 +++++++--------
.../TestVariableAnnotationsDisassembler.py | 2 +-
5 files changed, 16 insertions(+), 23 deletions(-)
diff --git a/lldb/include/lldb/API/SBInstruction.h b/lldb/include/lldb/API/SBInstruction.h
index 05e7087f2e679..c2111cd3d46af 100644
--- a/lldb/include/lldb/API/SBInstruction.h
+++ b/lldb/include/lldb/API/SBInstruction.h
@@ -89,7 +89,7 @@ class LLDB_API SBInstruction {
/// - "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);
+ lldb::SBStructuredData GetVariableAnnotations();
protected:
friend class SBInstructionList;
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 0e69bc4526c3b..a37c5edd4e356 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -597,13 +597,10 @@ class VariableAnnotator {
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,
- lldb::ModuleSP module_sp);
+ std::vector<std::string> Annotate(Instruction &inst);
/// Returns structured data for all variables relevant at this instruction.
- std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst,
- Target &target,
- lldb::ModuleSP module_sp);
+ std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst);
};
} // namespace lldb_private
diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp
index 992bd3c3fdd5d..ef0db7a37abb8 100644
--- a/lldb/source/API/SBInstruction.cpp
+++ b/lldb/source/API/SBInstruction.cpp
@@ -351,19 +351,16 @@ bool SBInstruction::TestEmulation(lldb::SBStream &output_stream,
return false;
}
-lldb::SBStructuredData
-SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
- LLDB_INSTRUMENT_VA(this, target);
+lldb::SBStructuredData SBInstruction::GetVariableAnnotations() {
+ LLDB_INSTRUMENT_VA(this);
SBStructuredData result;
- if (!m_opaque_sp || !m_opaque_sp->IsValid() || !target.IsValid())
+ if (!m_opaque_sp || !m_opaque_sp->IsValid())
return result;
lldb::InstructionSP inst_sp = m_opaque_sp->GetSP();
- lldb::TargetSP target_sp = target.GetSP();
-
- if (!inst_sp || !target_sp)
+ if (!inst_sp)
return result;
const Address &addr = inst_sp->GetAddress();
@@ -374,7 +371,7 @@ SBInstruction::GetVariableAnnotations(lldb::SBTarget target) {
VariableAnnotator annotator;
std::vector<VariableAnnotation> annotations =
- annotator.AnnotateStructured(*inst_sp, *target_sp, module_sp);
+ annotator.AnnotateStructured(*inst_sp);
auto array_sp = std::make_shared<StructuredData::Array>();
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index cb9c6eb3441d6..9b3d94dfa0982 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -302,10 +302,8 @@ const std::string VariableAnnotator::kUndefLocationFormatted =
// 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,
- lldb::ModuleSP module_sp) {
- auto structured_annotations = AnnotateStructured(inst, target, module_sp);
+std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst) {
+ auto structured_annotations = AnnotateStructured(inst);
std::vector<std::string> events;
events.reserve(structured_annotations.size());
@@ -328,10 +326,11 @@ std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst,
}
std::vector<VariableAnnotation>
-VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
- lldb::ModuleSP module_sp) {
+VariableAnnotator::AnnotateStructured(Instruction &inst) {
std::vector<VariableAnnotation> annotations;
+ ModuleSP module_sp = inst.GetAddress().GetModule();
+
// If we lost module context, mark all live variables as undefined.
if (!module_sp) {
for (const auto &KV : m_live_vars) {
@@ -377,7 +376,7 @@ VariableAnnotator::AnnotateStructured(Instruction &inst, Target &target,
const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress();
// ABI from Target (pretty reg names if plugin exists). Safe to be null.
- lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, target.GetArchitecture());
+ lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, module_sp->GetArchitecture());
ABI *abi = abi_sp.get();
llvm::DIDumpOptions opts;
@@ -737,7 +736,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);
if (!annotations.empty()) {
const size_t annotation_column = 100;
inst_line.FillLastLineToColumn(annotation_column, ' ');
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index d27c4a14ca9e3..ea2f099b0d057 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -146,7 +146,7 @@ def test_structured_annotations_api(self):
inst = instructions.GetInstructionAtIndex(i)
self.assertTrue(inst.IsValid(), f"Invalid instruction at index {i}")
- annotations = inst.GetVariableAnnotations(target)
+ annotations = inst.GetVariableAnnotations()
self.assertIsInstance(annotations, lldb.SBStructuredData,
"GetVariableAnnotations should return SBStructuredData")
>From df99e15856e46659357931dd9002a059a90e5937 Mon Sep 17 00:00:00 2001
From: Nikita B <n2h9z4 at gmail.com>
Date: Sun, 16 Nov 2025 19:41:13 +0100
Subject: [PATCH 21/21] [lldb] [disassembler] chore: enhance VariableAnnotator
to return structured data: update TestVariableAnnotationsDisassembler.py
formatting with darker
Signed-off-by: Nikita B <n2h9z4 at gmail.com>
---
.../TestVariableAnnotationsDisassembler.py | 63 ++++++++++++-------
1 file changed, 40 insertions(+), 23 deletions(-)
diff --git a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
index ea2f099b0d057..21698db07ac5d 100644
--- a/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
+++ b/lldb/test/API/functionalities/disassembler-variables/TestVariableAnnotationsDisassembler.py
@@ -125,8 +125,10 @@ def test_structured_annotations_api(self):
target = self._create_target(obj)
main_symbols = target.FindSymbols("main")
- self.assertTrue(main_symbols.IsValid() and main_symbols.GetSize() > 0,
- "Could not find 'main' symbol")
+ self.assertTrue(
+ main_symbols.IsValid() and main_symbols.GetSize() > 0,
+ "Could not find 'main' symbol",
+ )
main_symbol = main_symbols.GetContextAtIndex(0).GetSymbol()
start_addr = main_symbol.GetStartAddress()
@@ -136,7 +138,9 @@ def test_structured_annotations_api(self):
self.assertGreater(instructions.GetSize(), 0, "No instructions read")
if self.TraceOn():
- print(f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions")
+ print(
+ f"\nTesting GetVariableAnnotations() API on {instructions.GetSize()} instructions"
+ )
expected_vars = ["argc", "argv", "i"]
found_variables = set()
@@ -148,11 +152,16 @@ def test_structured_annotations_api(self):
annotations = inst.GetVariableAnnotations()
- self.assertIsInstance(annotations, lldb.SBStructuredData,
- "GetVariableAnnotations should return SBStructuredData")
+ self.assertIsInstance(
+ annotations,
+ lldb.SBStructuredData,
+ "GetVariableAnnotations should return SBStructuredData",
+ )
- self.assertTrue(annotations.GetSize() > 0,
- "GetVariableAnnotations should return non empty array")
+ self.assertTrue(
+ annotations.GetSize() > 0,
+ "GetVariableAnnotations should return non empty array",
+ )
if annotations.GetSize() > 0:
# Validate each annotation.
@@ -160,38 +169,46 @@ def test_structured_annotations_api(self):
ann = annotations.GetItemAtIndex(j)
self.assertTrue(ann.IsValid(), f"Invalid annotation at index {j}")
- self.assertEqual(ann.GetType(), lldb.eStructuredDataTypeDictionary,
- "Each annotation should be a dictionary")
+ self.assertEqual(
+ ann.GetType(),
+ lldb.eStructuredDataTypeDictionary,
+ "Each annotation should be a dictionary",
+ )
var_name_obj = ann.GetValueForKey("variable_name")
- self.assertTrue(var_name_obj.IsValid(),
- "Missing 'variable_name' field")
+ self.assertTrue(
+ var_name_obj.IsValid(), "Missing 'variable_name' field"
+ )
location_obj = ann.GetValueForKey("location_description")
- self.assertTrue(location_obj.IsValid(),
- "Missing 'location_description' field")
+ self.assertTrue(
+ location_obj.IsValid(), "Missing 'location_description' field"
+ )
is_live_obj = ann.GetValueForKey("is_live")
- self.assertTrue(is_live_obj.IsValid(),
- "Missing 'is_live' field")
+ self.assertTrue(is_live_obj.IsValid(), "Missing 'is_live' field")
start_addr_obj = ann.GetValueForKey("start_address")
- self.assertTrue(start_addr_obj.IsValid(),
- "Missing 'start_address' field")
+ self.assertTrue(
+ start_addr_obj.IsValid(), "Missing 'start_address' field"
+ )
end_addr_obj = ann.GetValueForKey("end_address")
- self.assertTrue(end_addr_obj.IsValid(),
- "Missing 'end_address' field")
+ self.assertTrue(
+ end_addr_obj.IsValid(), "Missing 'end_address' field"
+ )
register_kind_obj = ann.GetValueForKey("register_kind")
- self.assertTrue(register_kind_obj.IsValid(),
- "Missing 'register_kind' field")
+ self.assertTrue(
+ register_kind_obj.IsValid(), "Missing 'register_kind' field"
+ )
var_name = var_name_obj.GetStringValue(1024)
# Check for expected variables in this function.
- self.assertIn(var_name, expected_vars,
- f"Unexpected variable name: {var_name}")
+ self.assertIn(
+ var_name, expected_vars, f"Unexpected variable name: {var_name}"
+ )
found_variables.add(var_name)
More information about the lldb-commits
mailing list