Async mode and listeners

Hi,

I have a question about listeners and async mode. I'm building a debugger UI on top of LLDB in Python, and basically what I want to do is instantiate an SBDebugger, get its SBCommandInterpreter, loop on readline and feed the commands to the command interpreter pretty much the same way that the regular LLDB driver does. I’ve had a look at the driver code, but there’s quite a bit I don’t understand about how the back end works. There is other stuff going on in my tool that I won't go into that necessitates running it this way, rather than as a script loaded into the regular LLDB driver (which is actually how the existing version works).

From what I understand, the way to do this is to use async mode. I've had a look at the process_events.py example, which makes sense, but I'm not sure how to proceed with regard to listeners when letting SBCommandInterpreter do the heavy lifting rather than calling Launch* with the API.

I was hoping to be able to do something like this:

    import lldb
    import time

    debugger = lldb.SBDebugger.Create()
    debugger.SetAsync(True)
    ci = debugger.GetCommandInterpreter()
    res = lldb.SBCommandReturnObject()

    # "inferior" is a test program that when called with "loop" as the first param just does `while(1);`
    print("starting inferior")
    ci.HandleCommand("file tests/inferior", res)
    ci.HandleCommand("run loop", res)

    # let the target run a bit and then get its status
    print("sleeping 1 second")
    time.sleep(1)
    state = debugger.StateAsCString(debugger.GetTargetAtIndex(0).process.GetState())
    print("state (should be running): {}".format(state))

The state is always "stopped". After looking at `process_events.py` I figured I need to set up a listener and consume the events, so I tried something like this, with a listener running in a background thread (and a more realistic example with accepting user commands rather than the contrived example above):

    import lldb
    import os
    import threading

    debugger = lldb.SBDebugger.Create()
    debugger.SetAsync(True)
    ci = debugger.GetCommandInterpreter()

    done = threading.Event()

    def run_listener():
        event = lldb.SBEvent()
        listener = debugger.GetListener()
        while not done.is_set():
            if listener.WaitForEvent(1, event):
                print("Got a new LLDB event: {}".format(event))
            else:
                # print("Timed out waiting for LLDB event, trying again")
                pass

    t = threading.Thread(target=run_listener)
    t.start()

    try:
        while True:
            line = raw_input("> ")
            res = lldb.SBCommandReturnObject()
            ci.HandleCommand(line, res)
            if res.Succeeded():
                print(res.GetOutput().strip())
            else:
                print(res.GetError().strip())
    except:
        done.set()
        t.join()

But running the listener like that causes HandleCommand() to block. I'm assuming my listener consuming the events means that the debugger never receives them. I do not really require handling the events from my code, I'm happy for the debugger to handle that if it can do so and allow me to still perform other operations asynchronously like inspecting the state/read memory/etc.

Can anybody give me a clue as to how to proceed with this?

Thanks,
Loukas

BTW I probably should have mentioned that one of the things the tool does is spins off a background thread that handles requests from other UI views which can poke at the running target asynchronously.

Loukas

So I would suggest not using "ci.HandleCommand()" if you can. There are better API calls for this that will return your the objects that you need. In the code below I have modified each HandleCommand and replaced it with the function you want to use...

Hi,

I have a question about listeners and async mode. I'm building a debugger UI on top of LLDB in Python, and basically what I want to do is instantiate an SBDebugger, get its SBCommandInterpreter, loop on readline and feed the commands to the command interpreter pretty much the same way that the regular LLDB driver does. I’ve had a look at the driver code, but there’s quite a bit I don’t understand about how the back end works. There is other stuff going on in my tool that I won't go into that necessitates running it this way, rather than as a script loaded into the regular LLDB driver (which is actually how the existing version works).

From what I understand, the way to do this is to use async mode. I've had a look at the process_events.py example, which makes sense, but I'm not sure how to proceed with regard to listeners when letting SBCommandInterpreter do the heavy lifting rather than calling Launch* with the API.

I was hoping to be able to do something like this:

   import lldb
   import time

   debugger = lldb.SBDebugger.Create()
   debugger.SetAsync(True)
   ci = debugger.GetCommandInterpreter()
   res = lldb.SBCommandReturnObject()

   # "inferior" is a test program that when called with "loop" as the first param just does `while(1);`
   print("starting inferior")
   ci.HandleCommand("file tests/inferior", res)

filename = "tests/inferior"
triple = None
platform_name = None
add_dependent_modules = False
err = lldb.SBError()

target = debugger.CreateTarget (filename, triple, platform_name, add_dependent_modules, err);

if target:
    # Continue one with running
else:
    print err

   ci.HandleCommand("run loop", res)

if target:
    # Continue one with running
    launch_info = lldb.SBLaunchInfo(["loop"])

    process = target.Launch (launch_info, err)
    if process:

    else:
        print err
else:
    print err

   # let the target run a bit and then get its status
   print("sleeping 1 second")
   time.sleep(1)

You don't need the sleep now, just start waiting for events and respond to them like the code in process_events.py

   state = debugger.StateAsCString(debugger.GetTargetAtIndex(0).process.GetState())
   print("state (should be running): {}".format(state))

The above code should be one in your event loop when you receive an process state change event. You will do something when you get a running event and something else when you get a stopped event.

The state is always "stopped". After looking at `process_events.py` I figured I need to set up a listener and consume the events, so I tried something like this, with a listener running in a background thread (and a more realistic example with accepting user commands rather than the contrived example above):

   import lldb
   import os
   import threading

   debugger = lldb.SBDebugger.Create()
   debugger.SetAsync(True)
   ci = debugger.GetCommandInterpreter()

   done = threading.Event()

   def run_listener():
       event = lldb.SBEvent()
       listener = debugger.GetListener()
       while not done.is_set():
           if listener.WaitForEvent(1, event):
               print("Got a new LLDB event: {}".format(event))
           else:
               # print("Timed out waiting for LLDB event, trying again")
               pass

   t = threading.Thread(target=run_listener)
   t.start()

   try:
       while True:
           line = raw_input("> ")
           res = lldb.SBCommandReturnObject()
           ci.HandleCommand(line, res)
           if res.Succeeded():
               print(res.GetOutput().strip())
           else:
               print(res.GetError().strip())
   except:
       done.set()
       t.join()

But running the listener like that causes HandleCommand() to block.

Why is it blocking? You should look at a sample or interrupt with a debugger and see why.

I'm assuming my listener consuming the events means that the debugger never receives them.

No, the debugger has a listener which is given many events and you consume these events.

I do not really require handling the events from my code, I'm happy for the debugger to handle that if it can do so and allow me to still perform other operations asynchronously like inspecting the state/read memory/etc.

You should probably let the debugger handle everything including the command interpreter. Just call:

auto_handle_events = True
spawn_thread = False

debugger.RunCommandInterpreter (auto_handle_events, spawn_thread)

This will run everything for you just like the command line LLDB. If spawn_thread is false, then it will block until the command interpreter is exited, else if spawn_thread is true, the command interpreter will be run on another thread and it will return allowing you to do other things.

There currently isn't an option to just say "handle the events for me". The built in one exposed in SBDebugger::RunCommandInterpreter(...) will always print messages out to the stdout/stderr that you gave to the debugger:

    void
    SBDebugger::SetInputFileHandle (FILE *f, bool transfer_ownership);

    void
    SBDebugger::SetOutputFileHandle (FILE *f, bool transfer_ownership);

    void
    SBDebugger::SetErrorFileHandle (FILE *f, bool transfer_ownership);

So if you don't want it to do that and provide the feedback just like the command line tool, then you should be running your own event loop.

Greg

Hi Greg,

Thanks for your feedback.

So I would suggest not using “ci.HandleCommand()” if you can. There are better API calls for this that will return your the objects that you need.

I realise this, but the only reason I’m writing code that instantiates the debugger and controls this itself is because I cannot get the callbacks I need from the regular LLDB CLI driver (previous discussion here: http://lists.cs.uiuc.edu/pipermail/lldb-dev/2013-October/002554.html)

I would really rather not have to reimplement the entire CLI myself, I just want some additional callbacks on top of LLDB’s CLI - which is why I’m using SBCommandInterpreter in the contrived example.

Why is it blocking? You should look at a sample or interrupt with a debugger and see why.

OK I will look.

You should probably let the debugger handle everything including the command interpreter. Just call:

auto_handle_events = True
spawn_thread = False

debugger.RunCommandInterpreter (auto_handle_events, spawn_thread)

This will run everything for you just like the command line LLDB. If spawn_thread is false, then it will block until the command interpreter is exited, else if spawn_thread is true, the command interpreter will be run on another thread and it will return allowing you to do other things.

There currently isn’t an option to just say “handle the events for me”. The built in one exposed in SBDebugger::RunCommandInterpreter(…) will always print messages out to the stdout/stderr that you gave to the debugger:

void
SBDebugger::SetInputFileHandle (FILE *f, bool transfer_ownership);

void
SBDebugger::SetOutputFileHandle (FILE *f, bool transfer_ownership);

void
SBDebugger::SetErrorFileHandle (FILE *f, bool transfer_ownership);

RunCommandInterpreter() sounds like it may do what I want it to do, I will give it a try.

So if you don’t want it to do that and provide the feedback just like the command line tool, then you should be running your own event loop.

Yeah this is what I’m trying to avoid, as above.

Thanks,
Loukas

So if you don’t want it to do that and provide the feedback just like the command line tool, then you should be running your own event loop.

Yeah this is what I’m trying to avoid, as above.

Actually, that’s not right - I’m fine with running the event loop, I’d just rather not have to reimplement the entire CLI.

Loukas

If you want the event handler to work for you and you don’t mind the async output to stdout/err at any moment, then you can currently use the built in event loop, but just the event loop isn’t exported as an option you can run in a separate thread. You can add an API to SBDebugger that is something like:

bool
SBDebugger::SpawnEventHandlingThread();

And then run that. Is that what you were looking for? That would be a viable API to vend through SBDebugger.

Thanks for the help, Greg. I’ll investigate that option as well. That could do what I need.