How to Write an LLDB Plugin with Python: Customized Stepping Logic

In a previous post we saw an example of a new debugger command that can be added using the LLDB API.

Custom commands allow you to create custom logic for inspecting the state of the target program during a break. Custom stepping logic allows you to create customized logic to determine how to progress through the target program.

As an example we will create a custom step class, FindVar, that will step through a program until it finds a variable named r1.

The Plugin Code


import lldb

class FindVar:

  def __init__(self, thread_plan, dict):
    self.thread_plan = thread_plan
    self.start_frame = thread_plan.GetThread().GetFrameAtIndex(0)

  def explains_stop(self, event):
    res = self.thread_plan.GetThread().GetStopReason() == lldb.eStopReasonTrace
    return res

  def should_stop(self, event):

    frame = self.thread_plan.GetThread().GetFrameAtIndex(0)
    a_var = frame.FindVariable("r1")

    if not a_var.IsValid():
      print "Havent found r1 yet"
      return False
    else:
      print "Found r1!"
      self.thread_plan.SetPlanComplete(True)
      return True

  def should_step(self):
    return True

Code Explanation

The three crucial methods are explains_stop, should_stop, and should_step.

explains_stop

To understand explains_stop lets look at the output of an ordinary LLDB session. If you run an ordinary LLDB session tracking a program, say a.out, and create a breakpoint at the function main, then run the program you should see output like this:


bash-3.2$ lldb ./a.out
(lldb) target create "./a.out"
Current executable set to './a.out' (x86_64).
(lldb) breakpoint set --n main
Breakpoint 1: where = a.out`main + 23 at main.cpp:8, address = 0x0000000100000d77
(lldb) r

The code will hit main immediately and break, spitting out the following info:


Process 18492 launched: './a.out' (x86_64)
Process 18492 stopped
* thread #1: tid = 0x58329e, 0x0000000100000d77 a.out`main + 23 at
main.cpp:8,
queue = 'com.apple.main-thread',
stop reason = breakpoint 1.1

frame #0: 0x0000000100000d77 a.out`main + 23 at main.cpp:8

Notice the text of line 6: stop reason = breakpoint 1. 1. In LLDB every stop has a reason. When the debugger stops it calls each active thread plan's explains_stop method. The first one to return true is the stop reason.

LLDB supports several stop reasons, in the python API the supported stop reasons are:

eStopReasonBreakpoint
eStopReasonException
eStopReasonExec
eStopReasonInstrumentation
eStopReasonInvalid
eStopReasonNone
eStopReasonPlanComplete
eStopReasonSignal
eStopReasonThreadExiting
eStopReasonTrace
eStopReasonWatchpoint

Our example plugin is just supposed to step through the program, so it only explains the stop if the the stop reason is eStopReasonTrace.

should_stop

In the event that our plan does explain the stop we get to decide whether to return control to the user or continue execution. This decision is made by the should_stop method.

For our plugin we are trying to run the program until a variable named r1 is defined, so we search for a variable named r1 in the current frame. If it exists we return control to the user. If it does not exist we continue execution.

should_step

Once we have decided to continue execution our plan has two options. It can resume normal execution, or it can take one step. In our case we want to step through the program one instruction at a time until r1 is defined, so we always return True, saying that we want to take a single step and then stop again.

Using the plugin

To use this plugin we first need to create an executable to use as the target. In this example we will use a simple C++ program. First enter this code into a file named contrived.cpp:


#include "contrived.h"

int contrived_1(const int l, const int v, const int q, my_value my_v) {
int r1 = l - v;
int r2 = q*l;

if (my_v.useless) {
return r1 - r2;
} else {
return my_v.x + my_v.y + my_v.z - r1*r2;
}
}

Next create the corresponding contrived.h file:

struct my_value {
int x;
int y;
int z;

bool useless;
};

int contrived_1(const int l, const int v, const int q, my_value my_v);

Finally create a main.cpp file:


#include <iostream>

#include "contrived.h"

using namespace std;

int main() {
int x = 2;
int y = 4;
int z = 9;

bool useless = true;

my_value v{x, y, z, useless};
int k = contrived_1(3, 4, -12, v);

cout << k << endl;
}

With these files created compile them with:


clang++ -g -O0 -std=c++11 contrived.cpp main.cpp

This will create an executable, a.out. With a.out created we can debug it and use our plugin.

In the directory lldb_plugin run the commands:


bash-3.2$ lldb ./a.out
(lldb) target create "./a.out"
Current executable set to './a.out' (x86_64).
(lldb) breakpoint set --n main
Breakpoint 1: where = a.out`main + 23 at main.cpp:8, address = 0x0000000100000d77
(lldb) r
Process 18568 launched: './a.out' (x86_64)
Process 18568 stopped
* thread #1: tid = 0x58688d, 0x0000000100000d77 a.out`main + 23 at main.cpp:8, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000d77 a.out`main + 23 at main.cpp:8
   5       using namespace std;
   6       
   7       int main() {
-> 8         int x = 2;
   9         int y = 4;
   10        int z = 9;
   11      
(lldb) command script import find_var.py
(lldb) thread step-scripted -C find_var.FindVar
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Havent found r1 yet
Found r1!
Process 18568 stopped
* thread #1: tid = 0x58688d, 0x0000000100000d00 a.out`contrived_1(l=9, v=16777216, q=1, my_v=(x = 4, y = 2, z = 1606417248, useless = true)) at contrived.cpp:3, queue = 'com.apple.main-thread', stop reason = Python thread plan implemented by class find_var.FindVar.
    frame #0: 0x0000000100000d00 a.out`contrived_1(l=9, v=16777216, q=1, my_v=(x = 4, y = 2, z = 1606417248, useless = true)) at contrived.cpp:3
   1       #include "contrived.h"
   2       
-> 3       int contrived_1(const int l, const int v, const int q, my_value my_v) {
   4         int r1 = l - v;
   5         int r2 = q*l;
   6       
   7         if (my_v.useless) {

 

Leave a Reply

Your email address will not be published. Required fields are marked *