How to react to lldb exit with the python API

Hi all, first post here. I’m writing a plugin for lldb using the python API, and I’m having a hard time understanding how to listen to events. I’m using lldb 14.0.0.

My goal is to be able to execute custom code when :

  • a breakpoint it hit (so I can tell my plugin to update its data representation)
  • the process is exited (so I can tell my plugin’s GUI process to stop)
  • lldb exits (so I can tell my plugin’s GUI process to stop)

I was able to use a stop-hook for the breakpoint case, but I’m struggling to make it work for process and lldb exit.

Here is the code I tried to use :

import lldb
import threading


class LLDBEventHandler:
    def __init__(self, debugger: lldb.SBDebugger):
        self.__debugger = debugger
        self.__listener = lldb.SBListener("dave_process_listener")
        self.attached = False

        # Attach to quit command
        self.__debugger.GetCommandInterpreter().GetBroadcaster().AddListener(
            self.__listener,
            lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived,
        )

        # Start listening thread
        self.__thread = threading.Thread(target=self.__event_loop, daemon=True)
        self.__thread.start()

    def __try_to_attach_to_process(self):
        process = (
            self.__debugger.GetSelectedTarget().GetProcess()
        )  # type: lldb.SBProcess
        if process.is_alive:
            process.broadcaster.AddListener(
                self.__listener,
                lldb.SBProcess.eBroadcastBitStateChanged
                | lldb.SBProcess.eBroadcastBitInterrupt,
            )
            self.attached = True
            print("Successfully attached")


    def __event_loop(self):
        while True:
            if not self.attached:
                self.__try_to_attach_to_process()

            event = lldb.SBEvent()
            if self.__listener.WaitForEvent(1, event):
                print("New event")
                if lldb.SBProcess.EventIsProcessEvent(event):
                    print("process event")
                    if lldb.SBProcess.GetStateFromEvent(event) == lldb.eStateExited:
                        print("process exit")
                        # Do some stuff
                elif lldb.SBCommandInterpreter.EventIsCommandInterpreterEvent(event):
                    if event.GetType() == lldb.SBCommandInterpreter.eBroadcastBitQuitCommandReceived :
                        print("debugger exit")
                        # Do some stuff
                        
                        
def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict):
    event_handler = LLDBEventHandler(debugger)

But I have several issues :

  1. When starting lldb, a eBroadcastBitQuitCommandReceived is immediately detected even though I did not typed any command
  2. When explicitely typing the quit command, the eBroadcastBitQuitCommandReceived event is detected twice

Is the behaviour expected ? How should I deal with these issues ?

Thanks a lot for your help and the great tools. Don’t hesitate to ask if you need more information from me.

Best regards

I haven’t looked into what’s going on with events, but for quitting in particular it might be easier to deal with it by installing a Quit command callback using SBCommandInterpreter::SetCommandOverrideCallback.

You can do this from Python (there’s a toy example in TestCommandOverrideCallback.py). The callback returns a “handled” boolean. If you return true, then the overridden command won’t be executed, if false, then the overridden command will be run. Since you just want to observe, you would return false.

Unfortunately this method is not available in the llvm-14 API. Besides, I’m not sure it would react to a Ctrl-D which is needed in my case.

But I’ll keep looking in the SBCommandInterpreter code to see if I find something interesting.

Some questions for you, @maxmarsc :

  1. Does your plugin run in lldb or in the Python executable?
  2. How does your plugin communicate with its GUI process?
  3. Do you control your process from the lldb command line?
  4. Have you considered updating to a newer llvm release? 19.0 just branched, making 14.0 2.5 years old
  5. Have you considered using vscode and lldb-dap, and writing your plugin as a vscode extension? You’d be able to use a higher level API than the broadcast/listener stuff.

Hi,

  1. my plugins runs in lldb, this way it starts automatically with lldb, no need for external intervention
  2. It uses python’s multiprocessing module, either Queue or Pipe
  3. I’m not sure to understand, but yes the process is mostly controlled by sending custom lldb commands. The user can also use the GUI controls of the new process
  4. Unfortunately I want to be able to support older version of lldb, as this should be usable by my colleagues with vanilla debian/ubuntu distribution
  5. I thought about it, but I want this project to be the less time-consuming possible. And as far as I understand, developing for vscode requires javascript, which I never used.

In the end I went for a live signal sent by lldb 10 times a second, and the other process simply shuts down if lldb stops sending live signal. I though it would be the most robust way to achieve my goal.

Something a colleague of mine did was do some graphing inside lldb using Python and tkinter. Many years ago I built a toy gui that controlled lldb using Python and tkinter. If you do that, you won’t have to worry about communications to the gui module; you’ll have the gui module inside lldb, as part of your plugin.

If that doesn’t work for you, the pipe should return EPIPE when you try to read from it on the GUI end, and it’s closed. lldb exiting should close the pipe.

For the exit handling, does this work on your system?

Hi, sorry for the long delay to reply. I don’t check this forum very often.

I needed a dedicated process because tkinter needs to run on the main thread of its process, otherwise you can encounter various bugs.

As said before for the exit handling I went for a live signal strategy, but indeed I could use pipe eof to detect the other process has closed, thanks !