https://github.com/clayborg created 
https://github.com/llvm/llvm-project/pull/212831

This patch extends the "target hook" to be able to lazily resolve addresses. It 
does so by allowing the python class that gets specified to the "target hook" 
to have a new 'def resolve_load_addr(self, load_addr, stream) -> 
lldb.SBAddress' method that is optionally implemented. 

JIT loader plug-ins can now be implemented by creating a python class and using 
the "target hook add" and specifying the python class. The script can find and 
load the corresponding module lazily and only when needed. As a backtrace is 
happening, the PC's are resolved through the target and if the PC fails to 
resolve to a section/offset address, the `resolve_load_address` method will be 
called and the address can be resolved.

>From 57c754249d01ec350022fb7d537a748294486957 Mon Sep 17 00:00:00 2001
From: Greg Clayton <[email protected]>
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      |  12 ++
 .../python/templates/scripted_hook.py         |  24 ++++
 lldb/include/lldb/API/SBAddress.h             |   9 ++
 .../Interfaces/ScriptedHookInterface.h        |  27 +++-
 .../lldb/Interpreter/ScriptInterpreter.h      |   2 +
 lldb/include/lldb/Target/Target.h             |  14 +-
 lldb/source/Commands/CommandObjectTarget.cpp  |   5 +-
 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, 430 insertions(+), 8 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..513d04bd3daa2 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;
 
diff --git a/lldb/examples/python/templates/scripted_hook.py 
b/lldb/examples/python/templates/scripted_hook.py
index 0725c2c9c5d91..a8468aea2ab3d 100644
--- a/lldb/examples/python/templates/scripted_hook.py
+++ b/lldb/examples/python/templates/scripted_hook.py
@@ -65,3 +65,27 @@ def handle_stop(
             returned to the user, `False` if the process should keep running.
         """
         pass
+
+
+    def handle_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.
+
+        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..775fc65af87b2 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 {
@@ -21,9 +21,10 @@ class ScriptedHookInterface : public ScriptedInterface {
     bool handle_module_loaded = false;
     bool handle_module_unloaded = false;
     bool handle_stop = false;
+    bool handle_resolve_addr = false;
 
     bool any() const {
-      return handle_module_loaded || handle_module_unloaded || handle_stop;
+      return handle_module_loaded || handle_module_unloaded || handle_stop || 
handle_resolve_addr;
     }
   };
 
@@ -47,6 +48,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..cf06f3bb2c8ca 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -5557,7 +5557,8 @@ Python class hooks:
             pass
         def handle_stop(self, exe_ctx, stream):
             return True  # True = should_stop, False = continue
-
+        def handle_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..3724f65a5ba25 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.handle_resolve_addr =
+      implementor.HasAttribute("handle_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>("handle_resolve_addr", error, load_addr, output_sp);
+  if (error.Fail()) {
+    LLDB_LOG(GetLog(LLDBLog::Script), "handle_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..ddaba55deb203 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.handle_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..c107666bf37fa
--- /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 handle_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
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to