[Lldb-commits] [lldb] [lldb][ResolveSourceFileCallback] Implement API, Python interface (PR #120834)
via lldb-commits
lldb-commits at lists.llvm.org
Sat Dec 21 01:14:32 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: None (rchamala)
<details>
<summary>Changes</summary>
Summary:
RFC https://discourse.llvm.org/t/rfc-python-callback-for-target-get-module/71580
Use SWIG for the resolve source file callback the same as other Python callbacks. TestResolveSourceFileCallback.py verifies the functionalities.
Test Plan:
Added shell tests for validation
```
./llvm-lit -sv TestResolveSourceFileCallback.py
```
Differential Revision: https://phabricator.intern.facebook.com/D67541203
---
Full diff: https://github.com/llvm/llvm-project/pull/120834.diff
8 Files Affected:
- (modified) lldb/bindings/python/python-typemaps.swig (+44)
- (modified) lldb/bindings/python/python-wrapper.swig (+47-2)
- (modified) lldb/include/lldb/API/SBDefines.h (+5)
- (modified) lldb/include/lldb/API/SBPlatform.h (+3)
- (modified) lldb/source/API/SBPlatform.cpp (+37)
- (added) lldb/test/API/python_api/sbplatform/TestResolveSourceFileCallback.py (+196)
- (added) lldb/test/API/python_api/sbplatform/test.exe ()
- (added) lldb/test/API/python_api/sbplatform/test_new.cpp (+15)
``````````diff
diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig
index f8c33e15c03e66..84d26986104d31 100644
--- a/lldb/bindings/python/python-typemaps.swig
+++ b/lldb/bindings/python/python-typemaps.swig
@@ -713,3 +713,47 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) {
$1 = $input == Py_None;
$1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input));
}
+
+// For lldb::SBPlatformResolveSourceFileCallback
+%typemap(in) (lldb::SBPlatformResolveSourceFileCallback 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;
+ }
+
+ Py_INCREF($input);
+
+ $1 = LLDBSwigPythonCallResolveSourceFileCallback;
+ $2 = $input;
+ }
+}
+
+%typemap(typecheck) (lldb::SBPlatformResolveSourceFileCallback 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 b72a462d04643b..fb0b0368914fbf 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -727,7 +727,7 @@ lldb_private::python::SWIGBridge::LLDBSwigPythonHandleOptionArgumentCompletionFo
dict_sp->AddBooleanItem("no-completion", true);
return dict_sp;
}
-
+
// Convert the return dictionary to a DictionarySP.
StructuredData::ObjectSP result_obj_sp = result.CreateStructuredObject();
@@ -753,7 +753,7 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
auto pfunc = self.ResolveName<PythonCallable>("__call__");
if (!pfunc.IsAllocated()) {
- cmd_retobj.AppendError("Could not find '__call__' method in implementation class");
+ cmd_retobj.AppendError("Could not find '__call__' method in implementation class");
return false;
}
@@ -1095,4 +1095,49 @@ static SBError LLDBSwigPythonCallLocateModuleCallback(
return *sb_error_ptr;
}
+
+static SBError LLDBSwigPythonCallResolveSourceFileCallback(
+ void *callback_baton,
+ const lldb::ModuleSP &module_sp,
+ const SBFileSpec &original_source_file_spec_sb,
+ SBFileSpec &resolved_source_file_spec_sb) {
+ SWIG_Python_Thread_Block swig_thread_block;
+
+ PyErr_Cleaner py_err_cleaner(true);
+
+ PythonObject module_sb_arg = SWIGBridge::ToSWIGWrapper(module_sp);
+ PythonObject original_source_file_spec_arg = SWIGBridge::ToSWIGWrapper(
+ std::make_unique<SBFileSpec>(original_source_file_spec_sb));
+ PythonObject resolved_source_file_spec_arg = SWIGBridge::ToSWIGWrapper(
+ std::make_unique<SBFileSpec>(resolved_source_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_sb_arg, original_source_file_spec_arg,
+ resolved_source_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_resolved_source_file_spec_ptr = nullptr;
+ if (SWIG_ConvertPtr(resolved_source_file_spec_arg.get(),
+ (void **)&sb_resolved_source_file_spec_ptr,
+ SWIGTYPE_p_lldb__SBFileSpec, 0) == -1)
+ return SBError("resolved_source_file_spec is not SBFileSpec.");
+
+ resolved_source_file_spec_sb = *sb_resolved_source_file_spec_ptr;
+ }
+
+ return *sb_error_ptr;
+}
%}
diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h
index 159a9ba799b181..fa676ed799bbeb 100644
--- a/lldb/include/lldb/API/SBDefines.h
+++ b/lldb/include/lldb/API/SBDefines.h
@@ -146,6 +146,11 @@ typedef void (*SBDebuggerDestroyCallback)(lldb::user_id_t debugger_id,
typedef lldb::SBError (*SBPlatformLocateModuleCallback)(
void *baton, const lldb::SBModuleSpec &module_spec,
lldb::SBFileSpec &module_file_spec, lldb::SBFileSpec &symbol_file_spec);
+
+typedef lldb::SBError (*SBPlatformResolveSourceFileCallback)(
+ void *baton, const lldb::ModuleSP &module_sp,
+ const lldb::SBFileSpec &original_source_file_spec,
+ lldb::SBFileSpec &resolved_source_file_spec);
}
#endif // LLDB_API_SBDEFINES_H
diff --git a/lldb/include/lldb/API/SBPlatform.h b/lldb/include/lldb/API/SBPlatform.h
index d63d2ed1eaba62..37ab2ef0441cf5 100644
--- a/lldb/include/lldb/API/SBPlatform.h
+++ b/lldb/include/lldb/API/SBPlatform.h
@@ -190,6 +190,9 @@ class LLDB_API SBPlatform {
SBError SetLocateModuleCallback(lldb::SBPlatformLocateModuleCallback callback,
void *callback_baton);
+ SBError SetResolveSourceFileCallback(
+ lldb::SBPlatformResolveSourceFileCallback 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 394268b77aa21f..f0fd0a3418fa56 100644
--- a/lldb/source/API/SBPlatform.cpp
+++ b/lldb/source/API/SBPlatform.cpp
@@ -732,3 +732,40 @@ SBError SBPlatform::SetLocateModuleCallback(
});
return SBError();
}
+
+SBError SBPlatform::SetResolveSourceFileCallback(
+ lldb::SBPlatformResolveSourceFileCallback 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->SetResolveSourceFileCallback(nullptr);
+ return SBError();
+ }
+
+ // Platform.h does not accept lldb::SBPlatform ResolveSourceFileCallback
+ // directly because of the SBFileSpec dependency. Use a lambda to
+ // convert FileSpec <--> SBFileSpec for the callback arguments.
+ platform_sp->SetResolveSourceFileCallback(
+ [callback, callback_baton](const lldb::ModuleSP &module_sp,
+ const FileSpec &original_source_file_spec,
+ FileSpec &resolved_source_file_spec) {
+ SBFileSpec original_source_file_spec_sb(original_source_file_spec);
+ SBFileSpec resolved_source_file_spec_sb;
+
+ SBError error =
+ callback(callback_baton, module_sp, original_source_file_spec_sb,
+ resolved_source_file_spec_sb);
+
+ if (error.Success()) {
+ resolved_source_file_spec = resolved_source_file_spec_sb.ref();
+ }
+
+ return error.ref().Clone();
+ });
+ return SBError();
+}
diff --git a/lldb/test/API/python_api/sbplatform/TestResolveSourceFileCallback.py b/lldb/test/API/python_api/sbplatform/TestResolveSourceFileCallback.py
new file mode 100644
index 00000000000000..78bf4e47145ca7
--- /dev/null
+++ b/lldb/test/API/python_api/sbplatform/TestResolveSourceFileCallback.py
@@ -0,0 +1,196 @@
+"""
+Test resolve source file callback functionality
+"""
+
+import os
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from pathlib import Path
+
+import lldb
+
+SOURCE_ORIGINAL_FILE = "test.cpp" # File does not exist
+SOURCE_NEW_FILE = "test_new.cpp" # File exists
+SOURCE_NEW_NON_EXISTENT_FILE = "non-existent-file"
+EXE_NAME = "test.exe"
+
+
+class ResolveSourceFileCallbackTestCase(TestBase):
+ def setUp(self):
+ TestBase.setUp(self)
+
+ # Set the input directory
+ self.input_dir = (Path(self.getSourceDir())).resolve()
+
+ # Set executable to test.exe and ensure it exists
+ exe_path = (self.input_dir / EXE_NAME).resolve()
+ self.assertTrue(exe_path.exists())
+ exe_path_str = str(exe_path)
+
+ # Create target
+ self.target = self.dbg.CreateTarget(exe_path_str)
+ self.assertTrue(self.target)
+
+ # Create platform
+ self.platform = self.target.GetPlatform()
+
+ # Launch the process once, stop at breakpoint "sum" function and get the frame
+ self.frame = self.get_frame_for_paused_process("sum", exe_path_str)
+
+ # Set the original source file spec
+ source_file_path = os.path.join(self.input_dir, SOURCE_ORIGINAL_FILE)
+ self.original_source_file_spec = lldb.SBFileSpec(source_file_path)
+
+ # Set the new source file spec
+ new_source_file_path = os.path.join(self.input_dir, SOURCE_NEW_FILE)
+ self.new_source_file_spec = lldb.SBFileSpec(new_source_file_path)
+
+ def get_frame_for_paused_process(self, function_name, exe) -> lldb.SBFrame:
+ # Launch the process, stop at breakpoint on function name and get the frame
+
+ # Set breakpoint
+ breakpoint = self.target.BreakpointCreateByName(function_name, exe)
+ self.assertTrue(
+ breakpoint and breakpoint.GetNumLocations() == 1, VALID_BREAKPOINT
+ )
+
+ # Now launch the process, and do not stop at entry point.
+ process = self.target.LaunchSimple(
+ None, None, self.get_process_working_directory()
+ )
+ self.assertTrue(process, PROCESS_IS_VALID)
+
+ # Get the stopped thread
+ thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+ self.assertTrue(
+ thread.IsValid(), "There should be a thread stopped due to breakpoint"
+ )
+
+ # Get the frame
+ frame0 = thread.GetFrameAtIndex(0)
+ self.assertTrue(frame0.IsValid(), "There should be a valid frame")
+
+ return frame0
+
+ def get_source_file_for_frame(self) -> lldb.SBFileSpec:
+ line_entry = self.frame.GetLineEntry()
+ self.assertTrue(line_entry.IsValid(), "There should be a valid line entry")
+
+ return line_entry.GetFileSpec()
+
+ 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.SetResolveSourceFileCallback(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.SetResolveSourceFileCallback(test_args2)
+
+ def test_default(self):
+ # The default behavior is to locate the source file with LLDB implementation
+ # and frame.GetLineEntry should return the original file spec.
+ resolved_source_file_spec = self.get_source_file_for_frame()
+
+ # Check if the source file spec is resolved to the original file spec
+ self.assertEqual(resolved_source_file_spec, self.original_source_file_spec)
+ self.assertFalse(self.original_source_file_spec.Exists())
+
+ def test_set_none(self):
+ # SetResolveSourceFileCallback should succeed to clear the callback with None
+ # and frame.GetLineEntry will return the original file spec.
+ self.assertTrue(self.platform.SetResolveSourceFileCallback(None).Success())
+
+ resolved_source_file_spec = self.get_source_file_for_frame()
+
+ # Check if the source file spec is resolved to the original file spec
+ self.assertEqual(resolved_source_file_spec, self.original_source_file_spec)
+ self.assertFalse(resolved_source_file_spec.Exists())
+
+ def test_return_original_file_on_error(self):
+ # The callback fails, frame.GetLineEntry should return the original file spec.
+
+ # Resolve Source File Callback
+ def test_source_file_callback(
+ module_sp: lldb.SBModule,
+ original_file_spec: lldb.SBFileSpec,
+ resolved_file_spec: lldb.SBFileSpec,
+ ):
+ return lldb.SBError("Resolve Source File Callback failed")
+
+ self.assertTrue(
+ self.platform.SetResolveSourceFileCallback(
+ test_source_file_callback
+ ).Success()
+ )
+
+ resolved_source_file_spec = self.get_source_file_for_frame()
+
+ # Check if the source file spec is resolved to the original file spec
+ self.assertEqual(resolved_source_file_spec, self.original_source_file_spec)
+ self.assertFalse(resolved_source_file_spec.Exists())
+
+ def test_return_orignal_file_with_new_nonexistent_file(self):
+ # The callback should return a valid SBFileSpec but the file does not exist.
+ # frame.GetLineEntry should return the original file spec.
+
+ # Resolve Source File Callback
+ def test_source_file_callback(
+ module_sp: lldb.SBModule,
+ original_file_spec: lldb.SBFileSpec,
+ resolved_file_spec: lldb.SBFileSpec,
+ ):
+ resolved_file_spec.SetDirectory(str(self.input_dir))
+ resolved_file_spec.SetFilename(SOURCE_NEW_NON_EXISTENT_FILE)
+
+ return lldb.SBError()
+
+ # SetResolveSourceFileCallback should succeed and frame.GetLineEntry will return the original file spec
+ self.assertTrue(
+ self.platform.SetResolveSourceFileCallback(
+ test_source_file_callback
+ ).Success()
+ )
+
+ # Get resolved source file spec from frame0
+ resolved_source_file_spec = self.get_source_file_for_frame()
+
+ # Check if the source file spec is resolved to the original file spec
+ self.assertEqual(resolved_source_file_spec, self.original_source_file_spec)
+ self.assertFalse(resolved_source_file_spec.Exists())
+
+ def test_return_new_existent_file(self):
+ # The callback should return a valid SBFileSpec and file exists.
+ # frame.GetLineEntry should return the new file spec.
+
+ # Resolve Source File Callback
+ def test_source_file_callback(
+ module_sp: lldb.SBModule,
+ original_file_spec: lldb.SBFileSpec,
+ resolved_file_spec: lldb.SBFileSpec,
+ ):
+ resolved_file_spec.SetDirectory(str(self.input_dir))
+ resolved_file_spec.SetFilename(SOURCE_NEW_FILE)
+
+ return lldb.SBError()
+
+ # SetResolveSourceFileCallback should succeed and frame.GetLineEntry will return the new file spec from callback
+ self.assertTrue(
+ self.platform.SetResolveSourceFileCallback(
+ test_source_file_callback
+ ).Success()
+ )
+
+ # Get resolved source file spec from frame0
+ resolved_source_file_spec = self.get_source_file_for_frame()
+
+ # Check if the source file spec is resolved to the file set in callback
+ self.assertEqual(resolved_source_file_spec, self.new_source_file_spec)
+ self.assertFalse(self.original_source_file_spec.Exists())
+ self.assertTrue(resolved_source_file_spec.Exists())
diff --git a/lldb/test/API/python_api/sbplatform/test.exe b/lldb/test/API/python_api/sbplatform/test.exe
new file mode 100755
index 00000000000000..ee23081015ce79
Binary files /dev/null and b/lldb/test/API/python_api/sbplatform/test.exe differ
diff --git a/lldb/test/API/python_api/sbplatform/test_new.cpp b/lldb/test/API/python_api/sbplatform/test_new.cpp
new file mode 100644
index 00000000000000..e9ffc97618f5d1
--- /dev/null
+++ b/lldb/test/API/python_api/sbplatform/test_new.cpp
@@ -0,0 +1,15 @@
+#include <iostream>
+
+int sum(int a, int b) {
+ return a + b; // Find the line number of function sum here.
+}
+
+int main() {
+
+ int a = 2;
+ int b = 3;
+ int c = sum(a, b);
+
+ std::cout << "c is " << c << std::endl;
+ return 0;
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/120834
More information about the lldb-commits
mailing list