Python API

Hi,

I've been working on a terminal-based UI for LLDB (and GDB)[1] recently and I have a few questions about the Python API.

The way this tool works currently is by registering a new command that sends updates to client views, and then registering a stop-hook that calls that command whenever the debugger stops.

From looking at the sample python code and the LLDB source I think maybe I could do the same thing using SBListener/SBBroadcaster/etc.

I've tried stuff like this:

listener = lldb.SBListener()
listener.StartListeningForEventClass(lldb.debugger, 'lldb.process', 0xffffffff)
while 1:
   if listener.WaitForEvent(1, event):
        print('got event: ' + event.GetDataFlavor())

But I don't get notifications for processes starting and stopping.

I've also tried stealing lldb.debugger's listener:

listener = lldb.debugger.GetListener()

… but that hangs when I get the first event. I suspect maybe that's a terrible idea, and not how it's meant to be done when the LLDB CLI has created the SBDebugger instance and its listener.

Is it possible to do this from a script run within an existing LLDB instance? It seems like all the sample code doing things like this is actually instantiating the SBDebugger itself and fully automating the debugging session, rather than being imported into LLDB and running from there.

I'd also like to receive notifications for the following events:
- a new file being loaded
- a target being run
- a target exiting

Thanks,
Loukas

[1] https://github.com/snarez/voltron

Hi Loukas,

I too have been working on a terminal UI for LLDB (but not GDB) with some
colleagues (CC'd). I will commit what we have thus far shortly and you can
see what we did, and maybe share some efforts/ideas.

BTW, in terms of listeners, we initialize them like so:

self.listener = debugger.GetListener()

        self.listener.StartListeningForEventClass(self.debugger,
  lldb.SBTarget.GetBroadcasterClassName(),
  > lldb.SBTarget.eBroadcastBitBreakpointChanged
  > lldb.SBTarget.eBroadcastBitWatchpointChanged
                                           )

        self.listener.StartListeningForEventClass(self.debugger,
                   
lldb.SBThread.GetBroadcasterClassName(),
                   
lldb.SBThread.eBroadcastBitStackChanged
  > lldb.SBThread.eBroadcastBitThreadSuspended
  > lldb.SBThread.eBroadcastBitThreadResumed
  > lldb.SBThread.eBroadcastBitSelectedFrameChanged
  > lldb.SBThread.eBroadcastBitThreadSelected
                                           )

        self.listener.StartListeningForEventClass(self.debugger,
  lldb.SBProcess.GetBroadcasterClassName(),
  lldb.SBProcess.eBroadcastBitStateChanged
  > lldb.SBProcess.eBroadcastBitInterrupt
  > lldb.SBProcess.eBroadcastBitSTDOUT
  > lldb.SBProcess.eBroadcastBitSTDERR
  > lldb.SBProcess.eBroadcastBitProfileData
                                           )
        self.listener.StartListeningForEventClass(self.debugger,
  lldb.SBCommandInterpreter.GetBroadcasterClass(),
  lldb.SBCommandInterpreter.eBroadcastBitThreadShouldExit
  > lldb.SBCommandInterpreter.eBroadcastBitResetPrompt
  > lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived
  > lldb.SBCommandInterpreter.eBroadcastBitAsynchronousOutputData
  > lldb.SBCommandInterpreter.eBroadcastBitAsynchronousErrorData
                                           )

Cheers,
Dan

Hi Daniel,

That sounds good, I look forward to checking it out. Thanks for the examples, this seems to work *sometimes*:

Hi Loukas,

We are also developing a terminal-based GUI for lldb. Our
Approach is to check the output from lldb to detect
The status change. So far it seems working fine.
Could you let me know why you use listener approach?
Do you get any case that parsing output doesn't work?

Thanks,

Yin

Hi Yin,

Voltron is not so much built "on top" of LLDB as it is "glued to the side". You still use the main LLDB CLI for interaction with the debugger, voltron just spins off a background thread to send out data updates to client views (e.g. stack view, register view, etc). Have a look at the screenshot on the github page and you'll see what I mean (though the screenshot is GDB): https://github.com/snarez/voltron

As such, it does not control everything that goes into and comes out of the CLI, so parsing the output isn't an option.

Loukas

Right now, having multiple listeners getting process events doesn't really work. So if you have both the driver listening to process events, and you try to peek at them from your code as well, that very likely will cause problems.

It wouldn't be impossible to make this work. There is some asymmetry in process events, since some work goes on when the event is first fetched off the public queue (breakpoint commands get run, etc.) So you'd have to be careful to make sure that work doesn't get re-done. But it still could be made to work if needed. It is on our queue of things to clean up, but not high on the list...

Jim

Hi Jim,

Thanks for the insight into what's going on. I figured I might be misusing the listener stuff. It would be great if there were a way to register for these kinds of events from the python interface.

For now, the stop-hook will suffice. The only issue that I have is with that is registering the stop hook only works when there is a target loaded:

(lldb) target stop-hook add -o 'voltron update'
error: invalid target

(lldb) file ~/Projects/voltron/test_x64
Current executable set to '~/Projects/voltron/test_x64' (x86_64).
(lldb) target stop-hook add -o 'voltron update'
Stop hook #1 added.

Loukas

Hi Jim,

Thanks for the insight into what's going on. I figured I might be misusing the listener stuff. It would be great if there were a way to register for these kinds of events from the python interface.

For now, the stop-hook will suffice. The only issue that I have is with that is registering the stop hook only works when there is a target loaded:

(lldb) target stop-hook add -o 'voltron update'
error: invalid target

(lldb) file ~/Projects/voltron/test_x64
Current executable set to '~/Projects/voltron/test_x64' (x86_64).
(lldb) target stop-hook add -o 'voltron update'
Stop hook #1 added.

Yes, there is a class of target-related entities that currently can't be created without a real target. This includes stop-hooks but also breakpoints and signal handling behaviors.

At first I thought we should provide some kind of "template Target" that you can prime with breakpoints, stop hooks, signal handling behaviors, etc, and then have those behaviors copied into new targets as they are created. But at the same time I wanted to make it possible to prime these "template Targets" using some predicates to express things like "if the Platform is MacOSX, set these breakpoints, if Linux, these" or again based on architecture. It isn't clear how you would specify this, however, since it would have to be part of the command, say "break set" or "target stop-hook add" etc.

So another way I though of would be to add a m ore general notion of "hooks" that would get triggered by various interesting debugger "life-cycle" events. In this case, we could provide "target created" hooks, and you could add stop hooks & breakpoints, etc, there. This might give an easier way to specify the predicates, either something like:

(lldb) hook add target.create --arch x86_64 --executable-name MyProg --platform-name remote-macosx
Enter Target Create hook code, type DONE when done

hook add process.stop-hook -o "voltron update"
DONE

or another way we have toyed with that would also extend to settings is something like:

(lldb) hook add target[arch=x86_64;exec-name=MyProg;platform-name=remote-macosx].create
Enter Target Create hook code, type DONE when done

hook add process.stop-hook -o "voltron update"
DONE

Note, if we're going to do this, I'd also like to move the target stop-hooks into a general hook mechanism. And you could add "process created/died" "shared library loaded" and "thread created/died" hooks for platforms that track this. Some of these already get published by the event system, for those these "life cycle" hooks would just provide a convenient way to access the events from the command line.

The more I think about it the more I like this life cycle event mechanism over the "template target". It isn't quite as obvious as putting:

break set -n Foo

in your .lldbinit file and have all targets set this breakpoint. But it offers the same functionality while being more general, and it means we can add predicates freely without having to muck up all the commands whose settings might be controlled by the predicates.

Jim

Hi Jim,

I think you’re right - a generalised hooking mechanism that could call arbitrary commands on various events would make more sense than a “template” mechanism. It would probably be easier to use than exposing the SBListener/SBBroadcaster stuff through the python API, and more useful than the current mechanism.

The current mechanism is useful for individual cases, but not for general cases the way that GDB’s “hook-stop” macro is. The inspiration for this tool came from fractalg’s .gdbinit script[1], which (among other things) defines a stop hook that prints a bunch of nicely formatted context information whenever the debugger stops - registers, some disassembly, whether the branch will be taken if the current instruction is a branch, etc. It just defines a single stop-hook and does some magic based on what architecture the current target is. If I want to do this with LLDB it seems that I would have to manually add a stop-hook to every target.

(lldb) hook add target.create --arch x86_64 --executable-name MyProg --platform-name remote-macosx
Enter Target Create hook code, type DONE when done

hook add process.stop-hook -o “voltron update”
DONE

This looks good to me. If this had something like the -f option in command script for calling a python function directly it would be even better :smiley:

Note, if we’re going to do this, I’d also like to move the target stop-hooks into a general hook mechanism. And you could add “process created/died” “shared library loaded” and “thread created/died” hooks for platforms that track this. Some of these already get published by the event system, for those these “life cycle” hooks would just provide a convenient way to access the events from the command line.

Yeah this would be great.

Cheers,
Loukas

[1] https://github.com/gdbinit/Gdbinit