[lldb-dev] How to use the C++ API? No useful documentation?

Greg Clayton via lldb-dev lldb-dev at lists.llvm.org
Tue Feb 23 14:40:21 PST 2016


I need to spend some time writing this up, but until then here is some info.

We created a python script that uses the LLDB public API to grab async events so people can see how to do things:

svn cat http://llvm.org/svn/llvm-project/lldb/trunk/examples/python/process_events.py

If you look in here you will see the correct way to do things.

I will answer you questions inlined into your email below and then add some extra tips at the end:

> On Feb 20, 2016, at 3:04 PM, Paul Peet via lldb-dev <lldb-dev at lists.llvm.org> wrote:
> 
> Hello,
> 
> I am currently working on an IDE for C++ and I would like to integrate lldb as a debugger using the C++ API but it has been difficult for me to understand the architecture because there is no documentation available (except doxygen which isn't helpful at all).
> I am at the point understanding the Event system? How are Events created?

You need to be able to listen for events from your debug session. In order to do this, SBBroadcaster objects can broadcast events as SBEvent objects. You need to listen for events using a SBListener. Each SBBroadcaster will broadcast events where each different kind of event is represented by one bit in a 32 bit uint32_t. 

> How can I use SBListener and SBBroadcaster? (What's the function of SBBroadvaster).

Yes you can. You might want to use a SBBroadcaster to send events to your main event loop in the debugger:

using namespace lldb;
SBBroadcaster gui_event_broadcaster("gui-events");

// Define the event bits we will use for gui_event_broadcaster
enum
{
  eGUIEventBitLaunch   = (1u << 0),
  eGUIEventBitKill     = (1u << 1),
  eGUIEventBitStepOver = (1u << 2),
  eGUIEventBitStepOut  = (1u << 3),
  eGUIEventBitStepInto = (1u << 4),
  eGUIEventBitContinue = (1u << 5),
  eGUIEventBitHalt     = (1u << 6),
  eGUIEventBitQuit     = (1u << 7),
  eGUIEventAll         = UINT32_MAX
};

SBListener run_loop_listener("run-loop-listener");
// Listen for any event from gui_event_broadcaster by listening to all event bits
run_loop_listener.StartListeningForEvents(gui_event_broadcaster, eGUIEventAll);

You can then run an event loop on a thread:

void
RunLoop()
{
  SBEvent event;
  bool done = false
  while (!done)
  {
    if (listener.WaitForEvent(UINT32_MAX, event))
    {
      const uint32_t event_type = event.GetType();
      if (event.BroadcasterMatchesRef(gui_event_broadcaster))
      {
        switch (event_type)
        {
        case eGUIEventBitLaunch:
	case eGUIEventBitKill:
	case eGUIEventBitQuit:
        }
      }
    }
  }
}


Then on another thread, you can broadcast events. Lets say the user clicked the "kill" button in your IDE, you could broadcast an event:

gui_event_broadcaster.BroadcastEventByType(eGUIEventBitKill);

Then the event loop would receive the event, as long as it is listening for these events. So you can use SBBroadcaster and SBListener yourself, but many objects (like SBTarget, SBProcess and SBThread) are already broadcasters and will broadcast events to you and you can sign up to listen to the events they send out. I would recommend starting with SBProcess, and you will see how to listen to events by looking at the python code.


> 
> My current code looks something like this:
> 
> SBListener listener;
> SBProcess process = target.Launch(listener, args, env, nullptr, nullptr,
>                                   nullptr, "/home/cynecx/dev/helloWorld",
>                                   0, true, error);
> 
> process.Continue();
> 
> StateType state = process.GetState(); // is stopped
> 
> SBEvent event;
> 
> while(true) {
>   if(listener.WaitForEvent(0xFFFFFFFF, event)) {
>     // This branch is never hit
>     SBStream stream;
>     event.GetDescription(stream);
>     std::cout << stream.GetData() << std::endl;
>   } else {
>     break;
>   }
> }
> 
> It would help developers (IDE) a lot if there might be some tutorials/documentation on how to use the API.

I agree. We will try to get this up on the web at some point in the near future.

Some more pointers:

- When launching, use a SBLaunchInfo:

const char *argv[] = { "/bin/ls", "-l", "-A", "-F", nullptr };
SBLaunchInfo launch_info(argv);
launch_info.SetWorkingDirectory("/tmp");
SBError error;
SBProcess process = target.Launch (launch_info, error);

This will allow you to fill in a SBLaunchInfo from your IDE and possibly keep it around for re-use on next launch.

- When attaching, use a SBAttachInfo. Same reason as launch info.

pid_t pid = 123;
SBAttachInfo attach_info(pid)

or

const bool wait_for = true;
SBAttachInfo attach_info("my_program", wait_for);

Then do:

SBProcess process = target.Attach (attach_info, error);

- I would recommend having one thread that is the main LLDB event loop and have this event loop also do process control. You can usually use the SBDebugger's listener as it will be hooked up by default to the process if you don't specify a listener:

SBListener listener = debugger.GetListener();
SBEvent event;
const uint32_t infinite_timeout = UINT32_MAX;
StateType process_state = eStateInvalid;
while (!done)
{
  if (listener.WaitForEvent(infinite_timeout, event))
  {
    if (SBProcess::EventIsProcessEvent (event))
    {
      process_state = SBProcess::GetStateFromEvent (event);
      switch (process_state)
      {
      case eStateStopped:
      case eStateRunning:
      case eStateExited:
      case eStateDetached:
      ....
      }
    }
    else if (event.BroadcasterMatchesRef(gui_event_broadcaster))
    {
      switch (event_type)
      {
        case eGUIEventBitLaunch:
	case eGUIEventBitKill:
  	case eGUIEventBitStepOver:
  	case eGUIEventBitStepOut:
  	case eGUIEventBitStepInto:
        case eGUIEventBitContinue:
	case eGUIEventBitHalt:
	case eGUIEventBitQuit:
      }
    }
  }
}

Why do all process control on one thread? Because you don't want one thread telling the process to run and another telling the process to stop. So the easiest way to do this ensure only one thread actually controls the process. Since the event loop the place that will know if the process is stopped, it is the perfect place to also do the process control.

What we did in Xcode was we keep a stack of process control requests. So lets say we are currently stopped and the user presses the step over button 3 times really quickly. We might receive 10 eGUIEventBitStepOver events, and make a dequeue of process control requests:

eGUIEventBitStepOver
eGUIEventBitStepOver
eGUIEventBitStepOver

Now in the event handler for eGUIEventBitStepOver in your event loop you can do:

      switch (event_type)
      {
  	case eGUIEventBitStepOver:
          if (process_state == eStateStopped)
            process.GetSelectedThread().StepOver();
          else
            process_control_dequeue.push_back(eGUIEventBitStepOver);
        break;
      }

So this means, if nothing else is going on, then you can issue the step over right away, else you need to wait until the process is stopped. Then when responding to the process stopped event:


      process_state = SBProcess::GetStateFromEvent (event);
      switch (process_state)
      {
      case eStateStopped:
        if (SBProcess::GetRestartedFromEvent(event) == false)
        {
          UpdateGUI(); // Update your IDE so we see where we just stopped
          if (!process_control_dequeue.empty())
          {
             // pop the next process control event off of the process_control_dequeue 
             // and make the process resume/step/stop/continue
          }
        }
        break;

In Xcode we also did something a bit fancy: if the user ever presses the halt or kill button, we clear the process_control_dequeue. Why? Because most often someone is stepping faster than the debugger can keep up with and as the GUI is updating and showing them where their program is going, they say "yikes! Stop the program now". So any halt, kill or detach will clear any steps/continues that might have been saved up...

Hope this helps. For now, just keep asking questions on this list and we will help you out as much as possible.

Greg Clayton



More information about the lldb-dev mailing list