Skip to content

Add SBDebugger:: AddNotificationCallback API #111206

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 5 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
10 changes: 10 additions & 0 deletions lldb/bindings/python/python-swigsafecast.swig
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,15 @@ PythonObject SWIGBridge::ToSWIGWrapper(
return ToSWIGHelper(module_spec_sb.release(), SWIGTYPE_p_lldb__SBModuleSpec);
}

PythonObject SWIGBridge::ToSWIGWrapper(
std::unique_ptr<lldb::SBDebugger> debugger_sb) {
return ToSWIGHelper(debugger_sb.release(), SWIGTYPE_p_lldb__SBDebugger);
}

PythonObject SWIGBridge::ToSWIGWrapper(
std::unique_ptr<lldb::SBExecutionContext> exe_ctx_sb) {
return ToSWIGHelper(exe_ctx_sb.release(), SWIGTYPE_p_lldb__SBExecutionContext);
}

} // namespace python
} // namespace lldb_private
19 changes: 19 additions & 0 deletions lldb/bindings/python/python-typemaps.swig
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,25 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) {
$1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input));
}

// For lldb::SBNotificationCallback
%typemap(in) (lldb::SBNotificationCallback notification_callback, void *baton) {
if (!($input == Py_None ||
PyCallable_Check(reinterpret_cast<PyObject *>($input)))) {
PyErr_SetString(PyExc_TypeError, "Need a callable object or None!");
SWIG_fail;
}

// Don't lose the callback reference
Py_INCREF($input);
$1 = LLDBSwigPythonCallPythonSBNotificationCallback;
$2 = $input;
}

%typemap(typecheck) (lldb::SBNotificationCallback notification_callback, void *baton) {
$1 = $input == Py_None;
$1 = $1 || PyCallable_Check(reinterpret_cast<PyObject *>($input));
}

// For lldb::SBDebuggerDestroyCallback
%typemap(in) (lldb::SBDebuggerDestroyCallback destroy_callback, void *baton) {
if (!($input == Py_None ||
Expand Down
40 changes: 40 additions & 0 deletions lldb/bindings/python/python-wrapper.swig
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,46 @@ static void LLDBSwigPythonCallPythonLogOutputCallback(const char *str,
}
}

// For NotificationCallback functions
static void LLDBSwigPythonCallPythonSBNotificationCallback(
lldb::NotificationType type, lldb::SBDebugger &debugger,
lldb::SBExecutionContext &exe_ctx, void *baton) {

if (baton != Py_None) {
SWIG_PYTHON_THREAD_BEGIN_BLOCK;

// Convert debugger and exe_ctx to Python objects
PythonObject debugger_arg = SWIGBridge::ToSWIGWrapper(
std::make_unique<SBDebugger>(debugger)); // Wrap debugger reference

PythonObject exe_ctx_arg = SWIGBridge::ToSWIGWrapper(
std::make_unique<SBExecutionContext>(exe_ctx)); // Wrap ExecutionContext

// Create a tuple of arguments (type, debugger, exe_ctx)
PyObject *args = PyTuple_New(3);

// Add NotificationType as an integer to the tuple
PyTuple_SetItem(args, 0, PyLong_FromLong(static_cast<long>(type)));

// Add debugger and exe_ctx to the tuple
Py_INCREF(debugger_arg.get());
PyTuple_SetItem(args, 1, debugger_arg.get());

Py_INCREF(exe_ctx_arg.get());
PyTuple_SetItem(args, 2, exe_ctx_arg.get());

// Call the Python function with the tuple of arguments (type, debugger, exe_ctx)
PyObject *result = PyObject_CallFunction(
reinterpret_cast<PyObject *>(baton), const_cast<char *>("O"), args);

// Clean up
Py_XDECREF(result);
Py_DECREF(args); // Decrement reference count for args
Comment on lines +1042 to +1061
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this use PythonObject wrappers instead?

Something like PythonCallable(baton)(PythonInteger(type), debugger_arg, exe_ctx_arg) ?


SWIG_PYTHON_THREAD_END_BLOCK;
}
}

// For DebuggerTerminateCallback functions
static void LLDBSwigPythonCallPythonSBDebuggerTerminateCallback(lldb::user_id_t debugger_id,
void *baton) {
Expand Down
11 changes: 11 additions & 0 deletions lldb/include/lldb/API/SBDebugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,17 @@ class LLDB_API SBDebugger {
void SetDestroyCallback(lldb::SBDebuggerDestroyCallback destroy_callback,
void *baton);

/// Add a notification callback when notification type event happens. Return a
/// token, which can be used to remove said callback. Multiple callbacks can
/// be added by calling this function multiple times, and will be invoked in
/// FIFO order.
static lldb::callback_token_t
AddNotificationCallback(lldb::NotificationType notification_type,
lldb::SBNotificationCallback notification_callback,
void *baton);
/// Remove the specified callback. Return true if successful.
static bool RemoveNotificationCallback(lldb::callback_token_t token);

/// Add a callback for when the debugger is destroyed. Return a token, which
/// can be used to remove said callback. Multiple callbacks can be added by
/// calling this function multiple times, and will be invoked in FIFO order.
Expand Down
5 changes: 4 additions & 1 deletion lldb/include/lldb/API/SBDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ class LLDB_API SBUnixSignals;
typedef bool (*SBBreakpointHitCallback)(void *baton, lldb::SBProcess &process,
lldb::SBThread &thread,
lldb::SBBreakpointLocation &location);

typedef void (*SBNotificationCallback)(lldb::NotificationType type,
lldb::SBDebugger &,
lldb::SBExecutionContext &exe_ctx,
Comment on lines +142 to +143
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add accessors to the SBExecutionContext for accessing a debugger? We can modify the lldb_private::ExecutionContext to add a lldb::DebuggerSP and add accessors to the debugger object and then add that to the `lldb::SBExecutionContext" API?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's always been a little weird that ExecutionContext didn't have a debugger - though TTTT that only mattered for default constructed ones: so long as any of the other entities are filled in you can get to the debugger.
OTOH, that's a pretty big patch so if we want to go that way, that should be a separate patch.
Another way to do this would be to add an SBExecutionContext::GetDebugger and manage that all on the SB side? After all, we haven't actually needed a Debugger in the ExecutionContext that I can remember, and this would be a much more limited change.
I'm also fine with passing the debugger alongside the SBExecutionContext, we do that in some other places.

void *baton);
typedef void (*SBDebuggerDestroyCallback)(lldb::user_id_t debugger_id,
void *baton);

Expand Down
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class LLDB_API SBExecutionContext {

lldb_private::ExecutionContextRef *get() const;

friend class SBDebugger;
SBExecutionContext(lldb::ExecutionContextRefSP exe_ctx_ref_sp);

private:
Expand Down
49 changes: 40 additions & 9 deletions lldb/include/lldb/Core/Debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,18 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
SetDestroyCallback(lldb_private::DebuggerDestroyCallback destroy_callback,
void *baton);

/// Add a notification callback when notification type event happens. Return a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read literally this says you are adding the notification callback when the event happens. Maybe:

Add a notification callback that will trigger when an event of the given NotificationType occurs.

You should also state when the creation and destruction callbacks trigger w.r.t. the action the event is reporting. I.e. Creation callbacks trigger synchronously after the given object is fully set up, and Destruction callbacks trigger right before the object is destroyed.

/// token, which can be used to remove said callback. Multiple callbacks can
/// be added by calling this function multiple times, and will be invoked in
/// FIFO order.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is original_callback for? You should document this if it's needed.

static lldb::callback_token_t AddNotificationCallback(
lldb::NotificationType type,
lldb_private::NotificationCallback notification_callback, void *baton,
void *original_callback);

/// Remove the specified callback. Return true if successful.
static bool RemoveNotificationCallback(lldb::callback_token_t token);

/// Add a callback for when the debugger is destroyed. Return a token, which
/// can be used to remove said callback. Multiple callbacks can be added by
/// calling this function multiple times, and will be invoked in FIFO order.
Expand Down Expand Up @@ -683,6 +695,9 @@ class Debugger : public std::enable_shared_from_this<Debugger>,

void InstanceInitialize();

static void InvokeNotificationCallbacks(lldb::DebuggerSP debugger_sp,
lldb::NotificationType notify_type);

// these should never be NULL
lldb::FileSP m_input_file_sp;
lldb::StreamFileSP m_output_stream_sp;
Expand Down Expand Up @@ -737,19 +752,35 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
lldb::TargetSP m_dummy_target_sp;
Diagnostics::CallbackID m_diagnostics_callback_id;

std::mutex m_destroy_callback_mutex;
lldb::callback_token_t m_destroy_callback_next_token = 0;
struct DestroyCallbackInfo {
DestroyCallbackInfo() {}
DestroyCallbackInfo(lldb::callback_token_t token,
lldb_private::DebuggerDestroyCallback callback,
void *baton)
template <typename T> struct CallbackInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, CallbackInfo is the base template callback class for all kinds of callback (debugger create/destroy, and some other callbacks to be added in the future). It's better to place it somewhere else to make easier to extended in the future.

CallbackInfo() {}
CallbackInfo(lldb::callback_token_t token, T callback, void *baton)
: token(token), callback(callback), baton(baton) {}
lldb::callback_token_t token;
lldb_private::DebuggerDestroyCallback callback;
T callback;
void *baton;
};
llvm::SmallVector<DestroyCallbackInfo, 2> m_destroy_callbacks;
template <typename T>
struct NotificationCallbackInfo : public CallbackInfo<T> {
NotificationCallbackInfo() {}
NotificationCallbackInfo(lldb::callback_token_t token,
lldb::NotificationType type, T callback,
void *baton, void *original_callback)
: CallbackInfo<T>(token, callback, baton), type(type),
original_callback(original_callback) {}
lldb::NotificationType type;
void *original_callback;
};
static std::mutex s_notification_callback_mutex;
static lldb::callback_token_t s_notification_callback_next_token;
static llvm::SmallVector<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every action we perform with the notifications except for "deleting by token" knows the type of the notification that it's adding/looking up. We could even encode the type in the token and we'd always know. So it seems awkward to store them in a flat list. When we get around to listing these and other management tasks, those will very likely also be requests by type.
Maybe storing them in a map of type -> vector of callbacks for that type would be a better data structure here?

NotificationCallbackInfo<lldb_private::NotificationCallback>, 2>
s_notification_callbacks;

std::mutex m_destroy_callback_mutex;
lldb::callback_token_t m_destroy_callback_next_token = 0;
llvm::SmallVector<CallbackInfo<lldb_private::DebuggerDestroyCallback>, 2>
m_destroy_callbacks;

uint32_t m_interrupt_requested = 0; ///< Tracks interrupt requests
std::mutex m_interrupt_mutex;
Expand Down
6 changes: 6 additions & 0 deletions lldb/include/lldb/lldb-enumerations.h
Original file line number Diff line number Diff line change
Expand Up @@ -1362,6 +1362,12 @@ enum Severity {
eSeverityInfo, // Equivalent to Remark used in clang.
};

enum NotificationType {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to put a comment here saying (a) this is used for SBNotificationCallbacks and (b) this is where you should add any other notification callbacks (i.e. target creation and destruction...) That should be made clear so we don't invent another mechanism for this elsewhere...

eDebuggerWillBeCreated = (1 << 0),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be eDebuggerWasCreated to indicate that a debugger object was created and that the user can do something with it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not useful to call this before the new Debugger is fully initialized.

eDebuggerWillBeDestroyed =
(1 << 1), // Call before debugger object is destroyed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually want to treat this as a bitfield? If not, maybe they should just get the usual sequential numbers?

};

} // namespace lldb

#endif // LLDB_LLDB_ENUMERATIONS_H
4 changes: 4 additions & 0 deletions lldb/include/lldb/lldb-private-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ typedef struct type256 { uint64_t x[4]; } type256;
using ValueObjectProviderTy =
std::function<lldb::ValueObjectSP(ConstString, StackFrame *)>;

typedef void (*NotificationCallback)(lldb::NotificationType type,
lldb::DebuggerSP debugger,
lldb::ExecutionContextRefSP exe_ctx,
void *baton, void *original_callback);
typedef void (*DebuggerDestroyCallback)(lldb::user_id_t debugger_id,
void *baton);
typedef bool (*CommandOverrideCallbackWithResult)(
Expand Down
24 changes: 24 additions & 0 deletions lldb/source/API/SBDebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,30 @@ void SBDebugger::SetDestroyCallback(
}
}

lldb::callback_token_t SBDebugger::AddNotificationCallback(
lldb::NotificationType type,
lldb::SBNotificationCallback notification_callback, void *baton) {
LLDB_INSTRUMENT_VA(type, notification_callback, baton);

NotificationCallback callback = [](lldb::NotificationType type,
lldb::DebuggerSP debugger,
lldb::ExecutionContextRefSP exe_ctx,
void *baton, void *original_callback) {
SBDebugger sb_debugger(debugger);
lldb::SBNotificationCallback original_callback_func =
(lldb::SBNotificationCallback)original_callback;
Comment on lines +1716 to +1717
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lldb::SBNotificationCallback original_callback_func =
(lldb::SBNotificationCallback)original_callback;
auto original_callback_func =
reinterpret_cast<lldb::SBNotificationCallback>(original_callback);

(use c++ cast, and avoid repeating the type)

SBExecutionContext sb_exe_ctx(exe_ctx);
original_callback_func(type, sb_debugger, sb_exe_ctx, baton);
};
return Debugger::AddNotificationCallback(type, callback, baton,
(void *)notification_callback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}

bool SBDebugger::RemoveNotificationCallback(lldb::callback_token_t token) {
LLDB_INSTRUMENT_VA(token);
return Debugger::RemoveNotificationCallback(token);
}

lldb::callback_token_t
SBDebugger::AddDestroyCallback(lldb::SBDebuggerDestroyCallback destroy_callback,
void *baton) {
Expand Down
45 changes: 44 additions & 1 deletion lldb/source/Core/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ static Debugger::DebuggerList *g_debugger_list_ptr =
nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain
static llvm::DefaultThreadPool *g_thread_pool = nullptr;

std::mutex Debugger::s_notification_callback_mutex;
lldb::callback_token_t Debugger::s_notification_callback_next_token = 0;
llvm::SmallVector<
Debugger::NotificationCallbackInfo<lldb_private::NotificationCallback>, 2>
Debugger::s_notification_callbacks;

static constexpr OptionEnumValueElement g_show_disassembly_enum_values[] = {
{
Debugger::eStopDisassemblyTypeNever,
Expand Down Expand Up @@ -739,16 +745,29 @@ DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback,
g_debugger_list_ptr->push_back(debugger_sp);
}
debugger_sp->InstanceInitialize();

InvokeNotificationCallbacks(debugger_sp, lldb::eDebuggerWillBeCreated);
return debugger_sp;
}

void Debugger::InvokeNotificationCallbacks(DebuggerSP debugger_sp,
lldb::NotificationType notify_type) {
std::lock_guard<std::mutex> guard(s_notification_callback_mutex);
for (const auto &callback_info : s_notification_callbacks) {
if ((callback_info.type & notify_type) == notify_type)
callback_info.callback(notify_type, debugger_sp, nullptr,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this will deadlock if any of the callbacks try to register another callback.

You may want to consider making a copy of the list and iterating over that (without holding the mutex).

callback_info.baton,
callback_info.original_callback);
}
}

void Debugger::HandleDestroyCallback() {
const lldb::user_id_t user_id = GetID();
// Invoke and remove all the callbacks in an FIFO order. Callbacks which are
// added during this loop will be appended, invoked and then removed last.
// Callbacks which are removed during this loop will not be invoked.
while (true) {
DestroyCallbackInfo callback_info;
CallbackInfo<DebuggerDestroyCallback> callback_info;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about treating the destroy callbacks as (legacy) synonyms for NotificationCallbacks of the "destroy" kind?

Basically, have SBDebugger::AddDestroyCallback do a AddNotificationCallback(eDebuggerWillBeDestroyed, callback, baton)

Then we could delete all of this code...

{
std::lock_guard<std::mutex> guard(m_destroy_callback_mutex);
if (m_destroy_callbacks.empty())
Expand All @@ -766,6 +785,7 @@ void Debugger::Destroy(DebuggerSP &debugger_sp) {
if (!debugger_sp)
return;

InvokeNotificationCallbacks(debugger_sp, lldb::eDebuggerWillBeDestroyed);
debugger_sp->HandleDestroyCallback();
CommandInterpreter &cmd_interpreter = debugger_sp->GetCommandInterpreter();

Expand Down Expand Up @@ -1447,6 +1467,29 @@ void Debugger::SetDestroyCallback(
m_destroy_callbacks.emplace_back(token, destroy_callback, baton);
}

lldb::callback_token_t Debugger::AddNotificationCallback(
lldb::NotificationType type,
lldb_private::NotificationCallback notification_callback, void *baton,
void *original_callback) {
std::lock_guard<std::mutex> guard(s_notification_callback_mutex);
const lldb::callback_token_t token = s_notification_callback_next_token++;
s_notification_callbacks.emplace_back(token, type, notification_callback,
baton, original_callback);
return token;
}

bool Debugger::RemoveNotificationCallback(lldb::callback_token_t token) {
std::lock_guard<std::mutex> guard(s_notification_callback_mutex);
for (auto it = s_notification_callbacks.begin();
it != s_notification_callbacks.end(); ++it) {
if (it->token == token) {
s_notification_callbacks.erase(it);
return true;
}
}
return false;
}

lldb::callback_token_t Debugger::AddDestroyCallback(
lldb_private::DebuggerDestroyCallback destroy_callback, void *baton) {
std::lock_guard<std::mutex> guard(m_destroy_callback_mutex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class SBStructuredData;
class SBFileSpec;
class SBModuleSpec;
class SBStringList;
class SBDebugger;
class SBExecutionContext;
} // namespace lldb

namespace lldb_private {
Expand Down Expand Up @@ -111,6 +113,10 @@ class SWIGBridge {
ToSWIGWrapper(std::unique_ptr<lldb::SBFileSpec> file_spec_sb);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBModuleSpec> module_spec_sb);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBDebugger> debugger_sb);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBExecutionContext> exe_ctx_sb);

static python::ScopedPythonObject<lldb::SBCommandReturnObject>
ToSWIGWrapper(CommandReturnObject &cmd_retobj);
Expand Down
Loading
Loading