[Lldb-commits] [lldb] f03dbdb - [lldb][LocateModuleCallback] Implement API, Python interface
Kazuki Sakamoto via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 12 11:35:56 PDT 2023
Author: Kazuki Sakamoto
Date: 2023-07-12T11:33:51-07:00
New Revision: f03dbdb70aed74ed64c1a42a85e47e3f86315ae7
URL: https://github.com/llvm/llvm-project/commit/f03dbdb70aed74ed64c1a42a85e47e3f86315ae7
DIFF: https://github.com/llvm/llvm-project/commit/f03dbdb70aed74ed64c1a42a85e47e3f86315ae7.diff
LOG: [lldb][LocateModuleCallback] Implement API, Python interface
RFC https://discourse.llvm.org/t/rfc-python-callback-for-target-get-module/71580
Use SWIG for the locate module callback the same as other Python callbacks.
TestLocateModuleCallback.py verifies the functionalities.
Differential Revision: https://reviews.llvm.org/D153735
Added:
lldb/test/API/python_api/sbplatform/TestLocateModuleCallback.py
Modified:
lldb/bindings/python/python-typemaps.swig
lldb/bindings/python/python-wrapper.swig
lldb/include/lldb/API/SBDefines.h
lldb/include/lldb/API/SBPlatform.h
lldb/source/API/SBPlatform.cpp
Removed:
################################################################################
diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig
index d2520c415e7415..f3d0c918819b10 100644
--- a/lldb/bindings/python/python-typemaps.swig
+++ b/lldb/bindings/python/python-typemaps.swig
@@ -576,3 +576,54 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) {
return NULL;
}
}
+
+// For lldb::SBPlatformLocateModuleCallback
+%typemap(in) (lldb::SBPlatformLocateModuleCallback callback,
+ void *callback_baton) {
+ if (!($input == Py_None ||
+ PyCallable_Check(reinterpret_cast<PyObject *>($input)))) {
+ PyErr_SetString(PyExc_TypeError, "Need a callable object or None!");
+ SWIG_fail;
+ }
+
+ if ($input == Py_None) {
+ $1 = nullptr;
+ $2 = nullptr;
+ } else {
+ PythonCallable callable = Retain<PythonCallable>($input);
+ if (!callable.IsValid()) {
+ PyErr_SetString(PyExc_TypeError, "Need a valid callable object");
+ SWIG_fail;
+ }
+
+ llvm::Expected<PythonCallable::ArgInfo> arg_info = callable.GetArgInfo();
+ if (!arg_info) {
+ PyErr_SetString(PyExc_TypeError,
+ ("Could not get arguments: " +
+ llvm::toString(arg_info.takeError())).c_str());
+ SWIG_fail;
+ }
+
+ if (arg_info.get().max_positional_args != 3) {
+ PyErr_SetString(PyExc_TypeError, "Expected 3 argument callable object");
+ SWIG_fail;
+ }
+
+ // NOTE: When this is called multiple times, this will leak the Python
+ // callable object as other callbacks, because this does not call Py_DECREF
+ // the object. But it should be almost zero impact since this method is
+ // expected to be called only once.
+
+ // Don't lose the callback reference
+ Py_INCREF($input);
+
+ $1 = LLDBSwigPythonCallLocateModuleCallback;
+ $2 = $input;
+ }
+}
+
+%typemap(typecheck) (lldb::SBPlatformLocateModuleCallback callback,
+ void *callback_baton) {
+ $1 = $input == Py_None;
+ $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input));
+}
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index a7e80985993a01..c25fa0c4d9b5b1 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -1118,4 +1118,54 @@ static void LLDBSwigPythonCallPythonSBDebuggerTerminateCallback(lldb::user_id_t
SWIG_PYTHON_THREAD_END_BLOCK;
}
}
+
+static SBError LLDBSwigPythonCallLocateModuleCallback(
+ void *callback_baton, const SBModuleSpec &module_spec_sb,
+ SBFileSpec &module_file_spec_sb, SBFileSpec &symbol_file_spec_sb) {
+ SWIG_Python_Thread_Block swig_thread_block;
+
+ PyErr_Cleaner py_err_cleaner(true);
+ PythonObject module_spec_arg = SWIGBridge::ToSWIGWrapper(
+ std::move(std::make_unique<SBModuleSpec>(module_spec_sb)));
+ PythonObject module_file_spec_arg = SWIGBridge::ToSWIGWrapper(
+ std::move(std::make_unique<SBFileSpec>(module_file_spec_sb)));
+ PythonObject symbol_file_spec_arg = SWIGBridge::ToSWIGWrapper(
+ std::move(std::make_unique<SBFileSpec>(symbol_file_spec_sb)));
+
+ PythonCallable callable =
+ Retain<PythonCallable>(reinterpret_cast<PyObject *>(callback_baton));
+ if (!callable.IsValid()) {
+ return SBError("The callback callable is not valid.");
+ }
+
+ PythonObject result = callable(module_spec_arg, module_file_spec_arg,
+ symbol_file_spec_arg);
+
+ if (!result.IsAllocated())
+ return SBError("No result.");
+ lldb::SBError *sb_error_ptr = nullptr;
+ if (SWIG_ConvertPtr(result.get(), (void **)&sb_error_ptr,
+ SWIGTYPE_p_lldb__SBError, 0) == -1) {
+ return SBError("Result is not SBError.");
+ }
+
+ if (sb_error_ptr->Success()) {
+ lldb::SBFileSpec *sb_module_file_spec_ptr = nullptr;
+ if (SWIG_ConvertPtr(module_file_spec_arg.get(),
+ (void **)&sb_module_file_spec_ptr,
+ SWIGTYPE_p_lldb__SBFileSpec, 0) == -1)
+ return SBError("module_file_spec is not SBFileSpec.");
+
+ lldb::SBFileSpec *sb_symbol_file_spec_ptr = nullptr;
+ if (SWIG_ConvertPtr(symbol_file_spec_arg.get(),
+ (void **)&sb_symbol_file_spec_ptr,
+ SWIGTYPE_p_lldb__SBFileSpec, 0) == -1)
+ return SBError("symbol_file_spec is not SBFileSpec.");
+
+ module_file_spec_sb = *sb_module_file_spec_ptr;
+ symbol_file_spec_sb = *sb_symbol_file_spec_ptr;
+ }
+
+ return *sb_error_ptr;
+}
%}
diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h
index fed5094fd4d34f..0e6d7196469dfc 100644
--- a/lldb/include/lldb/API/SBDefines.h
+++ b/lldb/include/lldb/API/SBDefines.h
@@ -126,6 +126,10 @@ typedef bool (*SBBreakpointHitCallback)(void *baton, SBProcess &process,
typedef void (*SBDebuggerDestroyCallback)(lldb::user_id_t debugger_id,
void *baton);
+typedef SBError (*SBPlatformLocateModuleCallback)(
+ void *baton, const SBModuleSpec &module_spec, SBFileSpec &module_file_spec,
+ SBFileSpec &symbol_file_spec);
+
typedef void *ScriptedObject;
}
diff --git a/lldb/include/lldb/API/SBPlatform.h b/lldb/include/lldb/API/SBPlatform.h
index dcc8a14ff0c1f4..6567277a5d161e 100644
--- a/lldb/include/lldb/API/SBPlatform.h
+++ b/lldb/include/lldb/API/SBPlatform.h
@@ -169,6 +169,18 @@ class LLDB_API SBPlatform {
/// environment.
SBEnvironment GetEnvironment();
+ /// Set a callback as an implementation for locating module in order to
+ /// implement own module cache system. For example, to leverage distributed
+ /// build system, to bypass pulling files from remote platform, or to search
+ /// symbol files from symbol servers. The target will call this callback to
+ /// get a module file and a symbol file, and it will fallback to the LLDB
+ /// implementation when this callback failed or returned non-existent file.
+ /// This callback can set either module_file_spec or symbol_file_spec, or both
+ /// module_file_spec and symbol_file_spec. The callback will be cleared if
+ /// nullptr or None is set.
+ SBError SetLocateModuleCallback(lldb::SBPlatformLocateModuleCallback callback,
+ void *callback_baton);
+
protected:
friend class SBDebugger;
friend class SBTarget;
diff --git a/lldb/source/API/SBPlatform.cpp b/lldb/source/API/SBPlatform.cpp
index ed55e93ffc9de4..f8300a5bab30e4 100644
--- a/lldb/source/API/SBPlatform.cpp
+++ b/lldb/source/API/SBPlatform.cpp
@@ -11,6 +11,7 @@
#include "lldb/API/SBError.h"
#include "lldb/API/SBFileSpec.h"
#include "lldb/API/SBLaunchInfo.h"
+#include "lldb/API/SBModuleSpec.h"
#include "lldb/API/SBPlatform.h"
#include "lldb/API/SBUnixSignals.h"
#include "lldb/Host/File.h"
@@ -655,3 +656,41 @@ SBEnvironment SBPlatform::GetEnvironment() {
return SBEnvironment();
}
+
+SBError SBPlatform::SetLocateModuleCallback(
+ lldb::SBPlatformLocateModuleCallback callback, void *callback_baton) {
+ LLDB_INSTRUMENT_VA(this, callback, callback_baton);
+ PlatformSP platform_sp(GetSP());
+ if (!platform_sp)
+ return SBError("invalid platform");
+
+ if (!callback) {
+ // Clear the callback.
+ platform_sp->SetLocateModuleCallback(nullptr);
+ return SBError();
+ }
+
+ // Platform.h does not accept lldb::SBPlatformLocateModuleCallback directly
+ // because of the SBModuleSpec and SBFileSpec dependencies. Use a lambda to
+ // convert ModuleSpec/FileSpec <--> SBModuleSpec/SBFileSpec for the callback
+ // arguments.
+ platform_sp->SetLocateModuleCallback(
+ [callback, callback_baton](const ModuleSpec &module_spec,
+ FileSpec &module_file_spec,
+ FileSpec &symbol_file_spec) {
+ SBModuleSpec module_spec_sb(module_spec);
+ SBFileSpec module_file_spec_sb;
+ SBFileSpec symbol_file_spec_sb;
+
+ SBError error = callback(callback_baton, module_spec_sb,
+ module_file_spec_sb, symbol_file_spec_sb);
+
+ if (error.Success()) {
+ module_file_spec = module_file_spec_sb.ref();
+ symbol_file_spec = symbol_file_spec_sb.ref();
+ }
+
+ return error.ref();
+ });
+ return SBError();
+}
diff --git a/lldb/test/API/python_api/sbplatform/TestLocateModuleCallback.py b/lldb/test/API/python_api/sbplatform/TestLocateModuleCallback.py
new file mode 100644
index 00000000000000..672795fd742c79
--- /dev/null
+++ b/lldb/test/API/python_api/sbplatform/TestLocateModuleCallback.py
@@ -0,0 +1,338 @@
+"""
+Test platform locate module callback functionality
+"""
+
+import ctypes
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from pathlib import Path
+
+import lldb
+
+UNITTESTS_TARGET_INPUTS_PATH = "../../../../unittests/Target/Inputs"
+MODULE_PLATFORM_PATH = "/system/lib64/AndroidModule.so"
+MODULE_TRIPLE = "aarch64-none-linux"
+MODULE_RESOLVED_TRIPLE = "aarch64--linux-android"
+MODULE_UUID = "80008338-82A0-51E5-5922-C905D23890DA-BDDEFECC"
+MODULE_FUNCTION = "boom"
+MODULE_HIDDEN_FUNCTION = "boom_hidden"
+MODULE_FILE = "AndroidModule.so"
+MODULE_NON_EXISTENT_FILE = "non-existent-file"
+SYMBOL_FILE = "AndroidModule.unstripped.so"
+BREAKPAD_SYMBOL_FILE = "AndroidModule.so.sym"
+SYMBOL_STRIPPED = "stripped"
+SYMBOL_UNSTRIPPED = "unstripped"
+
+
+class LocateModuleCallbackTestCase(TestBase):
+ def setUp(self):
+ TestBase.setUp(self)
+ self.platform = self.dbg.GetSelectedPlatform()
+ self.target = self.dbg.CreateTarget("")
+ self.assertTrue(self.target)
+
+ self.input_dir = (
+ Path(self.getSourceDir()) / UNITTESTS_TARGET_INPUTS_PATH
+ ).resolve()
+ self.assertTrue(self.input_dir.is_dir())
+
+ def check_module_spec(self, module_spec: lldb.SBModuleSpec):
+ self.assertEqual(
+ MODULE_UUID.replace("-", ""),
+ ctypes.string_at(
+ int(module_spec.GetUUIDBytes()),
+ module_spec.GetUUIDLength(),
+ )
+ .hex()
+ .upper(),
+ )
+
+ self.assertEqual(MODULE_TRIPLE, module_spec.GetTriple())
+
+ self.assertEqual(MODULE_PLATFORM_PATH, module_spec.GetFileSpec().fullpath)
+
+ def check_module(self, module: lldb.SBModule, symbol_file: str, symbol_kind: str):
+ self.assertTrue(module.IsValid())
+
+ self.assertEqual(
+ MODULE_UUID,
+ module.GetUUIDString(),
+ )
+
+ self.assertEqual(MODULE_RESOLVED_TRIPLE, module.GetTriple())
+
+ self.assertEqual(MODULE_PLATFORM_PATH, module.GetPlatformFileSpec().fullpath)
+
+ self.assertEqual(
+ str(self.input_dir / MODULE_FILE),
+ module.GetFileSpec().fullpath,
+ )
+
+ self.assertEqual(
+ str(self.input_dir / symbol_file),
+ module.GetSymbolFileSpec().fullpath,
+ )
+
+ sc_list = module.FindFunctions(MODULE_FUNCTION, lldb.eSymbolTypeCode)
+ self.assertEqual(1, sc_list.GetSize())
+ sc_list = module.FindFunctions(MODULE_HIDDEN_FUNCTION, lldb.eSymbolTypeCode)
+ self.assertEqual(0 if symbol_kind == SYMBOL_STRIPPED else 1, sc_list.GetSize())
+
+ def test_set_non_callable(self):
+ # The callback should be callable.
+ non_callable = "a"
+
+ with self.assertRaises(TypeError, msg="Need a callable object or None!"):
+ self.platform.SetLocateModuleCallback(non_callable)
+
+ def test_set_wrong_args(self):
+ # The callback should accept 3 argument.
+ def test_args2(a, b):
+ pass
+
+ with self.assertRaises(TypeError, msg="Expected 3 argument callable object"):
+ self.platform.SetLocateModuleCallback(test_args2)
+
+ def test_default(self):
+ # The default behavior is to locate the module with LLDB implementation
+ # and AddModule should fail.
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_set_none(self):
+ # SetLocateModuleCallback should succeed to clear the callback with None.
+ # and AddModule should fail.
+ self.assertTrue(self.platform.SetLocateModuleCallback(None).Success())
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_error(self):
+ # The callback fails, AddModule should fail.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+ return lldb.SBError("locate module callback failed")
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_no_files(self):
+ # The callback succeeds but not return any files, AddModule should fail.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_non_existent_module(self):
+ # The callback returns non-existent module file, AddModule should fail.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ module_file_spec.SetDirectory(str(self.input_dir))
+ module_file_spec.SetFilename(MODULE_NON_EXISTENT_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_module_with_non_existent_symbol(self):
+ # The callback returns a module and non-existent symbol file,
+ # AddModule should fail.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ module_file_spec.SetDirectory(str(self.input_dir))
+ module_file_spec.SetFilename(MODULE_FILE)
+
+ symbol_file_spec.SetDirectory(str(self.input_dir))
+ symbol_file_spec.SetFilename(MODULE_NON_EXISTENT_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_non_existent_symbol(self):
+ # The callback returns non-existent symbol file, AddModule should fail.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ symbol_file_spec.SetDirectory(str(self.input_dir))
+ symbol_file_spec.SetFilename(MODULE_NON_EXISTENT_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.assertFalse(module)
+
+ def test_return_module(self):
+ # The callback returns the module file, AddModule should succeed.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ module_file_spec.SetDirectory(str(self.input_dir))
+ module_file_spec.SetFilename(MODULE_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.check_module(
+ module=module, symbol_file=MODULE_FILE, symbol_kind=SYMBOL_STRIPPED
+ )
+
+ def test_return_module_with_symbol(self):
+ # The callback returns the module file and the symbol file,
+ # AddModule should succeed.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ module_file_spec.SetDirectory(str(self.input_dir))
+ module_file_spec.SetFilename(MODULE_FILE)
+
+ symbol_file_spec.SetDirectory(str(self.input_dir))
+ symbol_file_spec.SetFilename(SYMBOL_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.check_module(
+ module=module, symbol_file=SYMBOL_FILE, symbol_kind=SYMBOL_UNSTRIPPED
+ )
+
+ def test_return_module_with_breakpad_symbol(self):
+ # The callback returns the module file and the breakpad symbol file,
+ # AddModule should succeed.
+ def test_locate_module(
+ module_spec: lldb.SBModuleSpec,
+ module_file_spec: lldb.SBFileSpec,
+ symbol_file_spec: lldb.SBFileSpec,
+ ):
+ self.check_module_spec(module_spec)
+
+ module_file_spec.SetDirectory(str(self.input_dir))
+ module_file_spec.SetFilename(MODULE_FILE)
+
+ symbol_file_spec.SetDirectory(str(self.input_dir))
+ symbol_file_spec.SetFilename(BREAKPAD_SYMBOL_FILE)
+
+ return lldb.SBError()
+
+ self.assertTrue(
+ self.platform.SetLocateModuleCallback(test_locate_module).Success()
+ )
+
+ module = self.target.AddModule(
+ MODULE_PLATFORM_PATH,
+ MODULE_TRIPLE,
+ MODULE_UUID,
+ )
+
+ self.check_module(
+ module=module,
+ symbol_file=BREAKPAD_SYMBOL_FILE,
+ symbol_kind=SYMBOL_UNSTRIPPED,
+ )
More information about the lldb-commits
mailing list