[Lldb-commits] [lldb] Add support for resolving addresses in "target hook". (PR #212831)
Greg Clayton via lldb-commits
lldb-commits at lists.llvm.org
Fri Sep 11 13:46:30 PDT 2026
https://github.com/clayborg updated https://github.com/llvm/llvm-project/pull/212831
>From 87dfbe0084248d0a74f1ea9a06daad963d584fc5 Mon Sep 17 00:00:00 2001
From: Greg Clayton <clayborg at gmail.com>
Date: Mon, 27 Jul 2026 10:15:34 -0700
Subject: [PATCH] Add support for resolving addresses in "target hook".
---
lldb/bindings/python/python-swigsafecast.swig | 4 +
lldb/bindings/python/python-wrapper.swig | 16 ++-
.../python/templates/scripted_hook.py | 42 ++++++
lldb/include/lldb/API/SBAddress.h | 9 ++
.../Interfaces/ScriptedHookInterface.h | 30 +++-
.../lldb/Interpreter/ScriptInterpreter.h | 2 +
lldb/include/lldb/Target/Target.h | 14 +-
lldb/source/Commands/CommandObjectTarget.cpp | 9 +-
lldb/source/Interpreter/ScriptInterpreter.cpp | 9 ++
.../ScriptedHookPythonInterface.cpp | 27 ++++
.../Interfaces/ScriptedHookPythonInterface.h | 5 +-
.../Interfaces/ScriptedPythonInterface.cpp | 14 ++
.../Interfaces/ScriptedPythonInterface.h | 8 ++
.../Python/SWIGPythonBridge.h | 2 +
lldb/source/Target/Target.cpp | 48 ++++++-
.../target/module-hook/resolve-addr/Makefile | 2 +
.../resolve-addr/TestModuleHookResolveAddr.py | 128 ++++++++++++++++++
.../module-hook/resolve-addr/addrhook.py | 94 +++++++++++++
.../target/module-hook/resolve-addr/main.cpp | 4 +
19 files changed, 455 insertions(+), 12 deletions(-)
create mode 100644 lldb/test/API/commands/target/module-hook/resolve-addr/Makefile
create mode 100644 lldb/test/API/commands/target/module-hook/resolve-addr/TestModuleHookResolveAddr.py
create mode 100644 lldb/test/API/commands/target/module-hook/resolve-addr/addrhook.py
create mode 100644 lldb/test/API/commands/target/module-hook/resolve-addr/main.cpp
diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig
index a86dc44ce4106..ec3b36a7b1ec7 100644
--- a/lldb/bindings/python/python-swigsafecast.swig
+++ b/lldb/bindings/python/python-swigsafecast.swig
@@ -59,6 +59,10 @@ PythonObject SWIGBridge::ToSWIGWrapper(const StructuredDataImpl &data_impl) {
return ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData>(new lldb::SBStructuredData(data_impl)));
}
+PythonObject SWIGBridge::ToSWIGWrapper(const Address &addr) {
+ return ToSWIGHelper(new lldb::SBAddress(addr), SWIGTYPE_p_lldb__SBAddress);
+}
+
PythonObject SWIGBridge::ToSWIGWrapper(lldb::ThreadSP thread_sp) {
return ToSWIGHelper(new lldb::SBThread(std::move(thread_sp)),
SWIGTYPE_p_lldb__SBThread);
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 2392737402e20..e4b8fd3696d05 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -533,6 +533,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObjec
return sb_ptr;
}
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBAddress(PyObject * data) {
+ lldb::SBAddress *sb_ptr = nullptr;
+
+// int valid_cast =
+// SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBAddress, 0);
+
+// if (valid_cast == -1)
+// return NULL;
+
+// return sb_ptr;
+// }
+
void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValue(PyObject * data) {
lldb::SBValue *sb_ptr = NULL;
@@ -605,8 +617,8 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBTarget(PyObject *
if (valid_cast == -1)
return NULL;
- return sb_ptr;
-}
+ return sb_ptr;
+}
bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand(
const char *python_function_name, const char *session_dictionary_name,
lldb::DebuggerSP debugger, const char *args,
diff --git a/lldb/examples/python/templates/scripted_hook.py b/lldb/examples/python/templates/scripted_hook.py
index 0725c2c9c5d91..4871e04e50000 100644
--- a/lldb/examples/python/templates/scripted_hook.py
+++ b/lldb/examples/python/templates/scripted_hook.py
@@ -12,6 +12,11 @@ class ScriptedHook(metaclass=ABCMeta):
always be attached as a stop-hook; `handle_module_loaded` and
`handle_module_unloaded` are optional and only called for hooks
registered via `target hook add -P`.
+
+ Methods in this class hook into target events and also provide additional
+ hooks to add functionality to the target. Target events are sent to methods
+ whose names start with "handle_". Methods that extend target functionality
+ have names that start with "do_".
"""
target: lldb.SBTarget
@@ -32,6 +37,9 @@ def __init__(self, target: lldb.SBTarget, args: lldb.SBStructuredData):
def handle_module_loaded(self, stream: lldb.SBStream) -> None:
"""Called whenever a module is loaded into the target.
+ Type:
+ Target event notificaton.
+
Args:
stream (lldb.SBStream): The stream to which the hook can write
output that will be reported to the user.
@@ -41,6 +49,9 @@ def handle_module_loaded(self, stream: lldb.SBStream) -> None:
def handle_module_unloaded(self, stream: lldb.SBStream) -> None:
"""Called whenever a module is unloaded from the target.
+ Type:
+ Target event notificaton.
+
Args:
stream (lldb.SBStream): The stream to which the hook can write
output that will be reported to the user.
@@ -54,6 +65,9 @@ def handle_stop(
"""Called whenever the process stops, before control is returned to
the user.
+ Type:
+ Target event notificaton.
+
Args:
exe_ctx (lldb.SBExecutionContext): The execution context at the
point of the stop.
@@ -65,3 +79,31 @@ def handle_stop(
returned to the user, `False` if the process should keep running.
"""
pass
+
+
+ def do_resolve_addr(
+ self, load_addr: int, stream: lldb.SBStream) -> lldb.SBAddress:
+ """Called whenever the target is not able to resolve a load address to
+ a section offset address.
+
+ Type:
+ Target functionality extension.
+
+
+ Clients can implement a JIT loader plugin using this method. Anytime the
+ target fails to resolve an address, this method will be called. The
+ function can find the file for the module that contains the address,
+ load it into the target, and return the resolved address. If the
+ address cannot be resolved, return a default construct lldb.SBAddress.
+
+ Args:
+ load_addr (int): The load address to attempt to resolve.
+ stream (lldb.SBStream): The stream to which the hook can write
+ output that will be reported to the user.
+
+ Returns:
+ lldb.SBAddress: If the address was resolved, return a SBAddress
+ that has been resolved to a section offset address, or return a
+ default constructed SBAddress if the address could not be resolved.
+ """
+ pass
diff --git a/lldb/include/lldb/API/SBAddress.h b/lldb/include/lldb/API/SBAddress.h
index 430dad4862dbf..bb490ee9dfedd 100644
--- a/lldb/include/lldb/API/SBAddress.h
+++ b/lldb/include/lldb/API/SBAddress.h
@@ -12,6 +12,12 @@
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBModule.h"
+namespace lldb_private {
+namespace python {
+class SWIGBridge;
+}
+} // namespace lldb_private
+
namespace lldb {
class LLDB_API SBAddress {
@@ -104,6 +110,9 @@ class LLDB_API SBAddress {
friend class SBValue;
friend class SBQueueItem;
+ friend class lldb_private::ScriptInterpreter;
+ friend class lldb_private::python::SWIGBridge;
+
lldb_private::Address *operator->();
const lldb_private::Address *operator->() const;
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
index 54d335122300d..e0d048e756905 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedHookInterface.h
@@ -10,7 +10,7 @@
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDHOOKINTERFACE_H
#include "lldb/lldb-private.h"
-
+#include "lldb/Core/Address.h"
#include "ScriptedInterface.h"
namespace lldb_private {
@@ -18,12 +18,16 @@ class ScriptedHookInterface : public ScriptedInterface {
public:
/// Describes which hook callback methods the Python class implements.
struct SupportedHookMethods {
+ // Target event callbacks.
bool handle_module_loaded = false;
bool handle_module_unloaded = false;
bool handle_stop = false;
+ // Target functionality extensions.
+ bool do_resolve_addr = false;
bool any() const {
- return handle_module_loaded || handle_module_unloaded || handle_stop;
+ return handle_module_loaded || handle_module_unloaded || handle_stop ||
+ do_resolve_addr;
}
};
@@ -47,6 +51,28 @@ class ScriptedHookInterface : public ScriptedInterface {
lldb::StreamSP &output_sp) {
return true;
}
+
+ /// Called when the target tried to resolve an address but wasn't able to
+ /// resolve it to an object file section. This allows plug-ins to resolve
+ /// an address on demand. JIT plug-ins can be completely implemented using
+ /// ScriptedHookInterface plug-ins and can lazily load the JIT'ed information
+ /// as needed instead of setting breakpoints
+ ///
+ /// \param[in] load_addr
+ /// The load address to resolve.
+ ///
+ /// \param[out] addr
+ /// The section offset address that was resolved if \a true is returned.
+ ///
+ /// \param[in] output_sp
+ /// An output stream to use for logging.
+ ///
+ /// \return
+ /// True if the address was resolved, false otherwise.
+ virtual std::optional<Address>
+ HandleResolveAddress(lldb::addr_t load_addr, lldb::StreamSP &output_sp) {
+ return std::nullopt;
+ }
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 0e65cb4b8ac4a..519155cffd360 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -652,6 +652,8 @@ class ScriptInterpreter : public PluginInterface {
SymbolContext
GetOpaqueTypeFromSBSymbolContext(const lldb::SBSymbolContext &sym_ctx) const;
+ Address GetOpaqueTypeFromSBAddress(const lldb::SBAddress &addr) const;
+
lldb::BreakpointSP
GetOpaqueTypeFromSBBreakpoint(const lldb::SBBreakpoint &breakpoint) const;
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index fb43f432a08da..4062c123abb45 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1769,6 +1769,7 @@ class Target : public std::enable_shared_from_this<Target>,
kModulesLoaded = (1u << 0),
kModulesUnloaded = (1u << 1),
kProcessStop = (1u << 2),
+ kResolveAddress = (1u << 3)
};
lldb::TargetSP &GetTarget() { return m_target_sp; }
@@ -1826,6 +1827,11 @@ class Target : public std::enable_shared_from_this<Target>,
return StopHook::StopHookResult::NoPreference;
}
+ virtual std::optional<Address> HandleResolveAddress(lldb::addr_t load_addr,
+ lldb::StreamSP &output_sp) {
+ return std::nullopt;
+ }
+
virtual void GetDescription(Stream &s, lldb::DescriptionLevel level) const;
protected:
@@ -1896,7 +1902,9 @@ class Target : public std::enable_shared_from_this<Target>,
void HandleModuleUnloaded(lldb::StreamSP output) override;
StopHook::StopHookResult HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP output) override;
-
+ std::optional<Address>
+ HandleResolveAddress(lldb::addr_t load_addr,
+ lldb::StreamSP &output_sp) override;
Status SetScriptCallback(const ScriptedMetadata &scripted_metadata);
private:
@@ -1948,6 +1956,10 @@ class Target : public std::enable_shared_from_this<Target>,
// control over the process for the first time.
bool RunStopHooks(bool at_initial_stop = false);
+ /// Runs the resolve address hooks that have been registered with this
+ /// target. Returns true if the address was resolved.
+ std::optional<Address> RunResolveAddressHooks(lldb::addr_t load_addr);
+
bool SetSuppresStopHooks(bool suppress) {
bool old_value = m_suppress_stop_hooks;
m_suppress_stop_hooks = suppress;
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index f77e87ad43aa2..31b1a22a84294 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5127,8 +5127,8 @@ class CommandObjectTargetStopHookDelete : public CommandObjectParsed {
Deletes the stop hook by index.
At any given stop, all enabled stop hooks that pass the stop filter will
-get a chance to run. That means if one stop-hook deletes another stop hook
-while executing, the deleted stop hook will still fire for the stop at which
+get a chance to run. That means if one stop-hook deletes another stop hook
+while executing, the deleted stop hook will still fire for the stop at which
it was deleted.
)");
AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar);
@@ -5557,7 +5557,8 @@ Python class hooks:
pass
def handle_stop(self, exe_ctx, stream):
return True # True = should_stop, False = continue
-
+ def do_resolve_addr(self, load_addr, stream):
+ return lldb.SBAddress() # Invalid defailt constructed address.
Filter options:
---------------
Filters (-s, -f, -l, -e, -c, -n, -x, -t, -T, -q) restrict when the hook
@@ -5913,6 +5914,8 @@ Valid trigger names: load, unload, stop.
return Target::Hook::kModulesUnloaded;
if (name == "stop")
return Target::Hook::kProcessStop;
+ if (name == "resolve-addr")
+ return Target::Hook::kResolveAddress;
return 0;
}
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 4f6095d097d10..2be78ad5f1511 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/API/SBAddress.h"
+#include "lldb/Core/Address.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/Pipe.h"
@@ -152,6 +154,13 @@ SymbolContext ScriptInterpreter::GetOpaqueTypeFromSBSymbolContext(
return {};
}
+Address ScriptInterpreter::GetOpaqueTypeFromSBAddress(
+ const lldb::SBAddress &sb_addr) const {
+ if (sb_addr.m_opaque_up)
+ return *sb_addr.m_opaque_up;
+ return {};
+}
+
std::optional<lldb_private::MemoryRegionInfo>
ScriptInterpreter::GetOpaqueTypeFromSBMemoryRegionInfo(
const lldb::SBMemoryRegionInfo &mem_region) const {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
index df4146657c53e..71155e168f48b 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.cpp
@@ -49,6 +49,8 @@ ScriptedHookPythonInterface::GetSupportedMethods() {
methods.handle_module_unloaded =
implementor.HasAttribute("handle_module_unloaded");
methods.handle_stop = implementor.HasAttribute("handle_stop");
+ methods.do_resolve_addr =
+ implementor.HasAttribute("do_resolve_addr");
return methods;
}
@@ -64,12 +66,20 @@ void ScriptedHookPythonInterface::HandleModuleLoaded(
lldb::StreamSP &output_sp) {
Status error;
Dispatch("handle_module_loaded", error, output_sp);
+ if (error.Fail()) {
+ LLDB_LOG(GetLog(LLDBLog::Script), "handle_module_loaded failed: {0}",
+ error.AsCString());
+ }
}
void ScriptedHookPythonInterface::HandleModuleUnloaded(
lldb::StreamSP &output_sp) {
Status error;
Dispatch("handle_module_unloaded", error, output_sp);
+ if (error.Fail()) {
+ LLDB_LOG(GetLog(LLDBLog::Script), "handle_module_unloaded failed: {0}",
+ error.AsCString());
+ }
}
llvm::Expected<bool>
@@ -83,6 +93,8 @@ ScriptedHookPythonInterface::HandleStop(ExecutionContext &exe_ctx,
if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
error)) {
+ LLDB_LOG(GetLog(LLDBLog::Script), "handle_stop failed: {0}",
+ error.AsCString());
if (!obj)
return true;
return error.ToError();
@@ -91,6 +103,21 @@ ScriptedHookPythonInterface::HandleStop(ExecutionContext &exe_ctx,
return obj->GetBooleanValue();
}
+std::optional<Address>
+ScriptedHookPythonInterface::HandleResolveAddress(lldb::addr_t load_addr,
+ lldb::StreamSP &output_sp) {
+ Status error;
+ Address addr =
+ Dispatch<Address>("do_resolve_addr", error, load_addr, output_sp);
+ if (error.Fail()) {
+ LLDB_LOG(GetLog(LLDBLog::Script), "do_resolve_addr failed: {0}",
+ error.AsCString());
+ }
+ if (addr.IsSectionOffset())
+ return addr;
+ return std::nullopt;
+}
+
void ScriptedHookPythonInterface::Initialize() {
const std::vector<llvm::StringRef> ci_usages = {
"target hook add -P <script-name> [-k key -v value ...]"};
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
index da15a4bb287d9..6b6403e3fd1fc 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedHookPythonInterface.h
@@ -33,14 +33,15 @@ class ScriptedHookPythonInterface : public ScriptedHookInterface,
return llvm::SmallVector<AbstractMethodRequirement>({{"handle_stop", 2}});
}
- /// Check which of the three hook methods the Python class implements.
+ /// Check which of the hook methods the Python class implements.
SupportedHookMethods GetSupportedMethods() override;
void HandleModuleLoaded(lldb::StreamSP &output_sp) override;
void HandleModuleUnloaded(lldb::StreamSP &output_sp) override;
llvm::Expected<bool> HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP &output_sp) override;
-
+ std::optional<Address> HandleResolveAddress(lldb::addr_t load_addr,
+ lldb::StreamSP &output_sp) override;
static void Initialize();
static void Terminate();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index 61ceb40dd9d32..9a99cf73e3783 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -14,6 +14,8 @@
#include "../ScriptInterpreterPythonImpl.h"
#include "ScriptedPythonInterface.h"
+#include "lldb/API/SBAddress.h"
+#include "lldb/Core/Address.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/ValueObject/ValueObjectList.h"
#include <optional>
@@ -118,6 +120,18 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
return {};
}
+template <>
+Address ScriptedPythonInterface::ExtractValueFromPythonObject<Address>(
+ python::PythonObject &p, Status &error) {
+ if (lldb::SBAddress *sb_addr = reinterpret_cast<lldb::SBAddress *>(
+ python::LLDBSWIGPython_CastPyObjectToSBAddress(p.get())))
+ return m_interpreter.GetOpaqueTypeFromSBAddress(*sb_addr);
+ error = Status::FromErrorString(
+ "Couldn't cast lldb::SBAddress to lldb_private::Address.");
+
+ return {};
+}
+
template <>
lldb::DataExtractorSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index aaa0b6a0f7a59..984fcaf6b8989 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -648,6 +648,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return python::SWIGBridge::ToSWIGWrapper(arg);
}
+ python::PythonObject Transform(Address &arg) {
+ return python::SWIGBridge::ToSWIGWrapper(arg);
+ }
+
python::PythonObject Transform(lldb::StreamSP arg) {
return python::SWIGBridge::ToSWIGWrapper(arg.get());
}
@@ -768,6 +772,10 @@ SymbolContext
ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>(
python::PythonObject &p, Status &error);
+template <>
+Address ScriptedPythonInterface::ExtractValueFromPythonObject<Address>(
+ python::PythonObject &p, Status &error);
+
template <>
lldb::StreamSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 07e0da1dcf70d..839d421b9e855 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -86,6 +86,7 @@ class SWIGBridge {
static PythonObject ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp);
static PythonObject ToSWIGWrapper(Status &&status);
static PythonObject ToSWIGWrapper(const StructuredDataImpl &data_impl);
+ static PythonObject ToSWIGWrapper(const Address &addr);
static PythonObject ToSWIGWrapper(lldb::ThreadSP thread_sp);
static PythonObject ToSWIGWrapper(lldb::StackFrameSP frame_sp);
static PythonObject ToSWIGWrapper(lldb::StackFrameListSP frames_sp);
@@ -252,6 +253,7 @@ void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBAddress(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBValueList(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data);
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 148d7e0b30dbb..1e7fed14386dc 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3481,8 +3481,14 @@ Status Target::Install(ProcessLaunchInfo *launch_info) {
bool Target::ResolveLoadAddress(addr_t load_addr, Address &so_addr,
uint32_t stop_id, bool allow_section_end) {
- return m_section_load_history.ResolveLoadAddress(stop_id, load_addr, so_addr,
- allow_section_end);
+ if (m_section_load_history.ResolveLoadAddress(stop_id, load_addr, so_addr,
+ allow_section_end))
+ return true;
+ if (std::optional<Address> opt_addr = RunResolveAddressHooks(load_addr)) {
+ so_addr = *opt_addr;
+ return true;
+ }
+ return false;
}
bool Target::ResolveFileAddress(lldb::addr_t file_addr,
@@ -4668,6 +4674,8 @@ Status Target::HookScripted::SetScriptCallback(
m_trigger_mask |= kModulesUnloaded;
if (methods.handle_stop)
m_trigger_mask |= kProcessStop;
+ if (methods.do_resolve_addr)
+ m_trigger_mask |= kResolveAddress;
return {};
}
@@ -4709,6 +4717,14 @@ Target::HookScripted::HandleStop(ExecutionContext &exc_ctx,
: StopHook::StopHookResult::RequestContinue;
}
+std::optional<Address>
+Target::HookScripted::HandleResolveAddress(lldb::addr_t load_addr,
+ lldb::StreamSP &output_sp) {
+ if (!m_interface_sp)
+ return std::nullopt;
+ return m_interface_sp->HandleResolveAddress(load_addr, output_sp);
+}
+
llvm::StringRef Target::HookScripted::GetScriptClassName() const {
if (m_interface_sp && m_interface_sp->GetScriptedMetadata())
return m_interface_sp->GetScriptedMetadata()->GetClassName();
@@ -4812,6 +4828,34 @@ void Target::SetAllHooksEnabledState(bool enabled) {
hook->SetIsEnabled(enabled);
}
+std::optional<Address> Target::RunResolveAddressHooks(lldb::addr_t load_addr) {
+ if (m_hooks.empty())
+ return std::nullopt;
+
+ // Copy active hooks into a local vector before iterating, in case a
+ // callback modifies m_hooks (same pattern as RunStopHooks).
+ std::vector<HookSP> active_hooks;
+ for (auto &[_, hook_sp] : m_hooks) {
+ if (hook_sp->IsEnabled() && hook_sp->FiresOn(Hook::kResolveAddress))
+ active_hooks.push_back(hook_sp);
+ }
+
+ if (active_hooks.empty())
+ return std::nullopt;
+
+ StreamSP output_sp = m_debugger.GetAsyncOutputStream();
+
+ for (auto &hook_sp : active_hooks) {
+ if (std::optional<Address> opt_addr = hook_sp->HandleResolveAddress(load_addr, output_sp)) {
+ assert(opt_addr->IsSectionOffset());
+ return *opt_addr;
+ }
+ }
+
+ output_sp->Flush();
+ return std::nullopt;
+}
+
void Target::RunModuleHooks(bool is_load) {
if (m_hooks.empty())
return;
diff --git a/lldb/test/API/commands/target/module-hook/resolve-addr/Makefile b/lldb/test/API/commands/target/module-hook/resolve-addr/Makefile
new file mode 100644
index 0000000000000..3d0b98f13f3d7
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/resolve-addr/Makefile
@@ -0,0 +1,2 @@
+CXX_SOURCES := main.cpp
+include Makefile.rules
diff --git a/lldb/test/API/commands/target/module-hook/resolve-addr/TestModuleHookResolveAddr.py b/lldb/test/API/commands/target/module-hook/resolve-addr/TestModuleHookResolveAddr.py
new file mode 100644
index 0000000000000..1a0a0e8a9e57e
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/resolve-addr/TestModuleHookResolveAddr.py
@@ -0,0 +1,128 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+def make_json_objfile(outpath, triple):
+ '''
+ Create a JSON object file at `outpath` for the given `triple` that can be
+ used to test the "target hook add" where
+ '''
+ data = {
+ "triple": triple,
+ "uuid": "ED6F6CBD-7357-3D32-B3E9-FDA98E07D863",
+ "type": "sharedlibrary",
+ "sections": [
+ {
+ "user_id": 0x200,
+ "name": ".text",
+ "address": 0,
+ "size": 0x1000,
+ "flags": 0x202,
+ "file_offset": 0,
+ "file_size": 0,
+ "read": True,
+ "write": False,
+ "execute": True,
+ }
+ ],
+ "symbols": [
+ {
+ "name": "foo",
+ "type": "code",
+ "address": 0x0,
+ "size": 0x100,
+ },
+ {
+ "name": "bar",
+ "type": "code",
+ "address": 0x100,
+ "size": 0x200,
+ },
+ {
+ "name": "baz",
+ "type": "code",
+ "address": 0x300,
+ "size": 0x300,
+ },
+ ],
+ }
+ with open(outpath, "w") as file:
+ json.dump(data, file, indent=4)
+ return True
+ return False
+
+
+class TestCase(TestBase):
+ @no_debug_info_test
+ def test_resolve_addr(self):
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ addrhook_python_path = self.getSourcePath("addrhook.py")
+ addrhook_module_path = self.getBuildArtifact("addrhook.json")
+ target = self.dbg.CreateTarget(exe)
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// Set a breakpoint here", lldb.SBFileSpec("main.cpp", False)
+ )
+
+ # We need to find a memory region that has no permissions that doesn't
+ # start at 0x0 and is at least 0x1000 bytes in size. We'll use this
+ # region to load our JSON object file to ensure it doesn't collide with
+ # any other modules in the target. We'll use the base address of this
+ # region as the base address for our JSON object file.
+ region_to_use = None
+ curr_addr = 0x0
+ while True:
+ region = lldb.SBMemoryRegionInfo()
+ error = target.process.GetMemoryRegionInfo(curr_addr, region)
+ if error.Fail():
+ print(f'error: "{error}"')
+ break
+ base_addr = region.GetRegionBase()
+ end_addr = region.GetRegionEnd()
+ print(f"Found memory region: [{base_addr:#x}-{end_addr:#x})")
+ # We don't want a memory region that starts at 0x0, invalid ranges
+ # and any regions that are too small.
+ if (base_addr > 0x0) and ((end_addr - base_addr) >= 0x1000):
+ if not region.IsReadable() and not region.IsWritable() and not region.IsExecutable():
+ print(f"Found a memory region with no permissions at {region}")
+ region_to_use = region
+ break
+ if end_addr == 0xffffffffffffffff:
+ break
+ curr_addr = end_addr
+
+ # If we didn't find a memory region with no permissions, then skip the
+ # test.
+ if region_to_use is None:
+ return self.skipTest("No memory region with no permissions found")
+
+ json_module_load_addr = region_to_use.GetRegionBase()
+ # Load the target hook script file.
+ self.dbg.HandleCommand(f'command script import "{addrhook_python_path}"')
+ # Setup the "target hook" to use the addrhook.py script and pass in the
+ # path to the JSON object file and the base address to load it at as
+ # extra arguments to the script.
+ self.dbg.HandleCommand(f'target hook add --script-class addrhook.Hooks -k path -v "{addrhook_module_path}" -k base_addr -v {json_module_load_addr:#x}')
+
+ # Verify we can resolve addresses for "foo", "bar" and "baz" symbols in
+ # the JSON object file.
+ addr = target.ResolveLoadAddress(json_module_load_addr + 0x0)
+ self.assertTrue(addr.IsValid(), "Address should be valid")
+ self.assertTrue(addr.GetSection().IsValid(), f"Address ({addr}) should have a valid section")
+ self.assertEqual(addr.GetSection().GetName(), ".text", "Address should be in .text section")
+ self.assertEqual(addr.GetSymbol().GetName(), "foo", "Address should resolve to symbol 'foo'")
+
+ addr = target.ResolveLoadAddress(json_module_load_addr + 0x100)
+ self.assertTrue(addr.IsValid(), "Address should be valid")
+ self.assertTrue(addr.GetSection().IsValid(), "Address should have a valid section")
+ self.assertEqual(addr.GetSection().GetName(), ".text", "Address should be in .text section")
+ self.assertEqual(addr.GetSymbol().GetName(), "bar", "Address should resolve to symbol 'bar'")
+
+ addr = target.ResolveLoadAddress(json_module_load_addr + 0x300)
+ self.assertTrue(addr.IsValid(), "Address should be valid")
+ self.assertTrue(addr.GetSection().IsValid(), "Address should have a valid section")
+ self.assertEqual(addr.GetSection().GetName(), ".text", "Address should be in .text section")
+ self.assertEqual(addr.GetSymbol().GetName(), "baz", "Address should resolve to symbol 'baz'")
diff --git a/lldb/test/API/commands/target/module-hook/resolve-addr/addrhook.py b/lldb/test/API/commands/target/module-hook/resolve-addr/addrhook.py
new file mode 100644
index 0000000000000..9b0d148de8435
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/resolve-addr/addrhook.py
@@ -0,0 +1,94 @@
+import lldb
+import json
+
+from lldb.plugins.scripted_hook import ScriptedHook
+
+# Make a JSON object file at `outpath` for the given `triple` that can be
+# used to test the "target hook add" where the target hook will be called to
+# resolve an address that is not in any of the target's modules.
+def make_json_objfile(outpath, triple):
+ data = {
+ "triple": triple,
+ "uuid": "ED6F6CBD-7357-3D32-B3E9-FDA98E07D863",
+ "type": "sharedlibrary",
+ "sections": [
+ {
+ "user_id": 0x200,
+ "name": ".text",
+ "address": 0x0,
+ "size": 0x1000,
+ "flags": 0x202,
+ "file_offset": 0,
+ "file_size": 0,
+ "read": True,
+ "write": False,
+ "execute": True,
+ }
+ ],
+ "symbols": [
+ {
+ "name": "foo",
+ "type": "code",
+ "address": 0x0,
+ "size": 0x100,
+ },
+ {
+ "name": "bar",
+ "type": "code",
+ "address": 0x100,
+ "size": 0x200,
+ },
+ {
+ "name": "baz",
+ "type": "code",
+ "address": 0x300,
+ "size": 0x300,
+ },
+ ],
+ }
+ with open(outpath, "w") as file:
+ json.dump(data, file, indent=4)
+ return True
+ return False
+
+class Hooks(ScriptedHook):
+ def __init__(self, target, extra_args, internal_dict):
+ super().__init__(target, extra_args)
+ print(f'extra_args type is "{type(extra_args)}"')
+ self.path = str(extra_args.GetValueForKey("path"))
+ self.base_addr = int(extra_args.GetValueForKey("base_addr"))
+ print(f'self.path = "{self.path}"')
+ print(f'self.base_addr = {self.base_addr:#x}')
+
+ def handle_stop(self,
+ exe_ctx: lldb.SBExecutionContext,
+ stream: lldb.SBStream) -> bool:
+ # This method is required to be implemented. Return true to stop the
+ # process, or false to continue.
+ return True # Stop
+
+ def do_resolve_addr(self,
+ load_addr: int,
+ stream: lldb.SBStream) -> lldb.SBAddress:
+ '''
+ Called when the target hook needs to resolve an address when the target
+ was not able to resolve the address. We will create a JSON object
+ file and load it into the target, then resolve the address in this JSON
+ object file.
+ '''
+ module_load_addr = self.base_addr
+ module_load_addr_end = self.base_addr + 0x1000
+ if module_load_addr <= load_addr and load_addr < module_load_addr_end:
+ if self.target.module['addrhook.json'] is None:
+ if make_json_objfile(self.path, self.target.triple):
+ print(f"Successfully created JSON object file at '{self.path}'")
+ module = self.target.AddModule(self.path, None, None)
+ if module:
+ print(f"Module loaded successfully, setting load address for .text section to {module_load_addr:#x}")
+ self.target.SetSectionLoadAddress(
+ module.FindSection(".text"),
+ module_load_addr)
+ addr = self.target.ResolveLoadAddress(load_addr)
+ print(f"Resolved address {load_addr:#x} to {addr}")
+ return addr
+ return lldb.SBAddress() # Return an invalid address if we didn't resolve it
diff --git a/lldb/test/API/commands/target/module-hook/resolve-addr/main.cpp b/lldb/test/API/commands/target/module-hook/resolve-addr/main.cpp
new file mode 100644
index 0000000000000..cd4d683139e6f
--- /dev/null
+++ b/lldb/test/API/commands/target/module-hook/resolve-addr/main.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+ return 0; // Set a breakpoint here
+}
More information about the lldb-commits
mailing list