Skip to content

[lldb][ResolveSourceFileCallback] Implement API, Python interface #120834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions lldb/bindings/python/python-typemaps.swig
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
49 changes: 47 additions & 2 deletions lldb/bindings/python/python-wrapper.swig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
%}
5 changes: 5 additions & 0 deletions lldb/include/lldb/API/SBDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions lldb/include/lldb/API/SBPlatform.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 37 additions & 0 deletions lldb/source/API/SBPlatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
196 changes: 196 additions & 0 deletions lldb/test/API/python_api/sbplatform/TestResolveSourceFileCallback.py
Original file line number Diff line number Diff line change
@@ -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())
Binary file added lldb/test/API/python_api/sbplatform/test.exe
Binary file not shown.
15 changes: 15 additions & 0 deletions lldb/test/API/python_api/sbplatform/test_new.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
Loading