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

Greg Clayton via lldb-dev lldb-dev at lists.llvm.org
Wed Feb 24 10:49:04 PST 2016


Here is the fixed code:

SBListener listener = debugger.GetListener();

SBLaunchInfo launch_info(args);
launch_info.SetEnvironmentEntries(env, true);
launch_info.SetWorkingDirectory("/home/dev/helloWorld");

SBProcess process = target.Launch(launch_info, error);

process.GetBroadcaster().AddListener(listener, SBProcess::eBroadcastBitStateChanged | SBProcess::eBroadcastBitSTDOUT);

while(true) 
{
    SBEvent event;

    if(listener.WaitForEvent(6, event)) 
    {
        if(!event.IsValid())
        break;

        const uint32_t event_type = event.GetType();
    
        if (SBProcess::EventIsProcessEvent (event))
        {            
            switch (event_type)
            {
            case SBProcess::eBroadcastBitStateChanged:
                {
                    const StateType state = SBProcess.GetStateFromEvent(event);
                    switch (state)
                    {
                    case eStateStopped:
                        {
                            const uint32_t num_threads = process.GetNumThreads();
                            SBThread thread = process.GetThreadAtIndex(0);
                            SBStream stream;
                            thread.GetStatus(stream);
                            event.GetDescription(stream);
                            std::cout << stream.GetData() << std::endl;
                            auto threadStopReason = thread.GetStopReason();   
                            if (threadStopReason == eStopReasonBreakpoint) 
                            {
                                uint64_t bpId = thread.GetStopReasonDataAtIndex(0);

                                if(bpId == static_cast<uint64_t>(bp1.GetID())) 
                                {
                                    std::cout << "Stopped at breakpoint" << std::endl;
                                    thread.StepOver();
                                }
                            }
                            else if (threadStopReason == eStopReasonPlanComplete) 
                            {
                                std::cout << "Stopped at step" << std::endl;
                            }
                        }
                        break;
                    }
                }
                break;
            case SBProcess::eBroadcastBitSTDOUT:
                {
                    char buffer[1024];
                    size_t num_bytes = 0;
                    do 
                    {
                        num_bytes = process.GetSTDOUT(buffer, sizeof(buffer));
                        // Print exactly num_bytes bytes of the string data in buffer
                        if (num_bytes > 0)
                            printf("%*s", (int)num_bytes, buffer);
                    } while (num_bytes == sizeof(buffer);
                }
                break;
        }
    }
}

Your main issue was you were grabbing threads even when you weren't stopped, possibly when you were getting process STDOUT...

The main differences in the code above are:
1 - use SBLaunchInfo to launch
1 - Only try to grab stuff from the process and threads when you are stopped (only do process.GetThreadAtIndex(...) when you are stopped, not for every event. What would process return if the process is running for process.GetThreadAtIndex(0)? Sometimes an invalid thread, sometimes a valid thread that might be in the middle of a step. When you are stopped, you are guaranteed to get good results. So make sure you are stopped before you ask for threads, frames, variables, etc...
2 - Make sure the event is a process event with SBProcess::EventIsProcessEvent() as you might get other events if you use the debugger's listener (target events, thread events, etc)
3 - Switch off of the event type so you know what kind of event you are getting.

Let me know if this fixes things.

Greg Clayton


> On Feb 23, 2016, at 7:10 PM, paulpeet17 at gmail.com wrote:
> 
> 
> Thank you very much for that detailed description on explaining the 'basics'.
> 
> I got it working so far but there is a small issue with the code I am currently running. When the main loop (there is only one) receives a breakpoint event and I am trying to get the thread description (After the breakpoint I issue the thread.StepOver command to the next line of code), the result is not 'deterministic' in terms of it is always showing me different current instruction location. Sometimes it's still on the same breakpoint line and sometimes it's on the proper next line. How is it that the thread is still changing even if the stopped state event got hit?
> 
> The code:
> http://pastebin.com/0arNea9m
> 
> Sorry about pastebin, I am sending this from my phone and apparently it discards the code style while copy and paste.
> 
>> On 23 Feb 2016, at 23:40, Greg Clayton <gclayton at apple.com> wrote:
>> 
>> 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