[lldb-dev] Async mode and listeners

Greg Clayton gclayton at apple.com
Fri Jul 18 18:01:01 PDT 2014


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...

> On Jul 18, 2014, at 4:10 AM, snare <snare at ho.ax> wrote:
> 
> 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



More information about the lldb-dev mailing list