Jacob
Jacob

Reputation: 80

How can I script LLDB to update an external source code view?

In GDB I have a Python script which uses gdb.prompt_hook to send a command to an open vim session whenever the current file/line number changes. Effectively giving me a "live updating" source view. The script I use is publicly visible here.

I would like to port this script to LLDB, but I can't figure out an equivalent to gdb.prompt_hook. I.e. some way to execute a Python script whenever control is returned to LLDB when execution stops, OR the current frame is changed (e.g. executing f N).

Can someone suggest which APIs I might be able to use to implement this?

(NB: I am already aware of LLDB's gui view.)

Upvotes: 0

Views: 28

Answers (2)

Jacob
Jacob

Reputation: 80

Jim's answer is probably the "correct" way of doing this so all cases are covered. However, after spending quite some time struggling with LLDB's Python APIs for event handling I stumbled across LLDB's target stop-hook ... command. This can run a Python callback whenever execution pauses, and so covers most of what I want.

Unfortunately, target stop-hook doesn't cover the need for updates as the user manually navigates the stack e.g. using commands like up, down, etc. To deal with this I re-implemented the commands I regularly use: up, down, and f.

My implementation looks roughly like this:

class LLDBStopHandler:
  def __init__(self, _target, _extra_args, _dict):
    pass

  def handle_stop(self, _exe_ctx, _stream):
    MY_STOP_HOOK()
    return True


def lldb_f_command(debugger, command, result, dict):
  debugger.HandleCommand(f'frame select {args}')
  MY_STOP_HOOK()


def lldb_down_command(debugger, command, result, dict):
  frame_id = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame().GetFrameID()
  debugger.HandleCommand(f'frame select {frame_id - 1}')
  MY_STOP_HOOK()


def lldb_up_command(debugger, command, result, dict):
  frame_id = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame().GetFrameID()
  debugger.HandleCommand(f'frame select {frame_id + 1}')
  MY_STOP_HOOK()


def __lldb_init_module(debugger, _dict):
    debugger.HandleCommand(f'target stop-hook add -P {__name__}.LLDBStopHandler')
    debugger.HandleCommand(f'command script add -f {__name__}.lldb_f_command f')
    debugger.HandleCommand(f'command script add -f {__name__}.lldb_down_command down')
    debugger.HandleCommand(f'command script add -f {__name__}.lldb_up_command up')

Note the frame command cannot be overridden directly as it's a built-in. I don't tend to use it though. Also the above is missing anything to cover switching threads, and probably some other things I haven't had to worry about yet.

Upvotes: 0

Jim Ingham
Jim Ingham

Reputation: 27110

There isn't a "prompt-printed" callback in lldb. It wouldn't be hard to add if someone is ambitious, but it doesn't exist at present.

You can do this by listening for "process state changed" "thread changed" and "frame changed" notifications from the event system. For instance, to get notified when the user changes threads or frames in the debugger, you will have to sign up for these changes using the SBThread Broadcaster. You can listen for all events for all threads by getting the SBThread::GetBroadcasterClassName(), make a Listener and sign that up with the SBDebugger for thread class broadcasters using SBListener::StartListeningForEventClass. You can similarly sign up for SBProcess state changed events to hear when the process starts and stops. Then call SBListener.WaitForEvents to get events and dispatch the change info to vim.

There's also a "open in external editor" feature but that's only implemented on macOS for target applications that take the standard open file event with file & line number.

Upvotes: 1

Related Questions