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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits