Custom Breakpoint Resolvers#
Another use of the Python APIâs in lldb is to create a custom breakpoint resolver.
It allows you to provide the algorithm which will be used in the breakpointâs search of the space of the code in a given Target to determine where to set the breakpoint locations - the actual places where the breakpoint will trigger. To understand how this works you need to know a little about how lldb handles breakpoints.
In lldb, a breakpoint is composed of three parts:
the Searcher
the Resolver,
the Stop Options.
The Searcher and Resolver cooperate to determine how breakpoint locations are set and differ between each breakpoint type. Stop options determine what happens when a location triggers and includes the commands, conditions, ignore counts, etc. Stop options are common between all breakpoint types, so for our purposes only the Searcher and Resolver are relevant.
Breakpoint Searcher#
The Searcherâs job is to traverse in a structured way the code in the current target. It proceeds from the Target, to search all the Modules in the Target, in each Module it can recurse into the Compile Units in that module, and within each Compile Unit it can recurse over the Functions it contains.
The Searcher can be provided with a SearchFilter that it will use to restrict this search. For instance, if the SearchFilter specifies a list of Modules, the Searcher will not recurse into Modules that arenât on the list. When you pass the -s modulename flag to break set you are creating a Module-based search filter. When you pass -f filename.c to break set -n you are creating a file based search filter. If neither of these is specified, the breakpoint will have a no-op search filter, so all parts of the program are searched and all locations accepted.
Breakpoint Resolver#
The Resolver has two functions:
The most important one is the callback it provides. This will get called at the appropriate time in the course of the search. The callback is where the job of adding locations to the breakpoint gets done.
The other function is specifying to the Searcher at what depth in the above described recursion it wants to be called. Setting a search depth also provides a stop for the recursion. For instance, if you request a Module depth search, then the callback will be called for each Module as it gets added to the Target, but the searcher will not recurse into the Compile Units in the module.
One other slight subtlety is that the depth at which you get called back is not necessarily the depth at which the SearchFilter is specified. For instance, if you are doing symbol searches, it is convenient to use the Module depth for the search, since symbols are stored in the module. But the SearchFilter might specify some subset of CompileUnits, so not all the symbols you might find in each module will pass the search. You donât need to handle this situation yourself, since SBBreakpoint::AddLocation will only add locations that pass the Search Filter. This API returns an SBError to inform you whether your location was added.
When the breakpoint is originally created, its Searcher will process all the currently loaded modules. The Searcher will also visit any new modules as they are added to the target. This happens, for instance, when a new shared library gets added to the target in the course of running, or on rerunning if any of the currently loaded modules have been changed. Note, in the latter case, all the locations set in the old module will get deleted and you will be asked to recreate them in the new version of the module when your callback gets called with that module. For this reason, you shouldnât try to manage the locations you add to the breakpoint yourself. Note that the Breakpoint takes care of deduplicating equal addresses in AddLocation, so you shouldnât need to worry about that anyway.
Scripted Breakpoint Resolver#
At present, when adding a ScriptedBreakpoint type, you can only provide a custom Resolver, not a custom SearchFilter.
The custom Resolver is provided as a Python class with the following methods:
Name |
Arguments |
Description |
---|---|---|
|
|
This is the constructor for the new Resolver. |
|
|
This is the Resolver callback. The |
|
|
Specify the depth at which you wish your callback to get called. The currently supported options are: |
|
|
This is an optional method. If provided, the returned string will be printed at the beginning of the description for this breakpoint. |
To define a new breakpoint command defined by this class from the lldb command line, use the command:
(lldb) breakpoint set -P MyModule.MyResolverClass
You can also populate the extra_args SBStructuredData with a dictionary of key/value pairs with:
(lldb) breakpoint set -P MyModule.MyResolverClass -k key_1 -v value_1 -k key_2 -v value_2
Although you canât write a scripted SearchFilter, both the command line and the SB APIâs for adding a scripted resolver allow you to specify a SearchFilter restricted to certain modules or certain compile units. When using the command line to create the resolver, you can specify a Module specific SearchFilter by passing the -s ModuleName option - which can be specified multiple times. You can also specify a SearchFilter restricted to certain compile units by passing in the -f CompUnitName option. This can also be specified more than once. And you can mix the two to specify âthis comp unit in this moduleâ. So, for instance,
(lldb) breakpoint set -P MyModule.MyResolverClass -s a.out
will use your resolver, but will only recurse into or accept new locations in the module a.out.
Another option for creating scripted breakpoints is to use the SBTarget.BreakpointCreateFromScript API. This one has the advantage that you can pass in an arbitrary SBStructuredData object, so you can create more complex parametrizations. SBStructuredData has a handy SetFromJSON method which you can use for this purpose. Your init function gets passed this SBStructuredData object. This API also allows you to directly provide the list of Modules and the list of CompileUnits that will make up the SearchFilter. If you pass in empty lists, the breakpoint will use the default âsearch everywhere,accept everythingâ filter.
Providing Facade Locations:#
The breakpoint resolver interface also allows you to present a separate set of locations for the breakpoint than the ones that actually implement the breakpoint in the target.
An example use case for this is if you are providing a debugging interface for a library that implements an interpreter for a language lldb canât debug. But while debugging that library at the level of the implementation language (e.g. C/C++, etc) you would like to offer the ability to âstop when a line in a source language file is executedâ.
You can do this if you know where new lines of code are dispatched in the interpreter. You would set a breakpoint there, and then look at the state when that breakpoint is hit to see if it is dispatching the source file and line that were requested, and stop appropriately.
Facade breakpoint locations are intended to make a more natural presentation of that sort of feature. The idea is that you would make a custom breakpoint resolver that sets actual locations in the places of interest in the interpreter.
Then your resolver would add âfacade locationsâ that represent the places in the interpreted code that you want the breakpoint to stop at, using SBBreakpoint::AddFacadeLocation. When lldb describes the breakpoint, it will only show the Facade locations. Since facade breakpoint locationâs description is customizable, you can make these locations more descriptive. And when the ârealâ location is hit, lldb will call the âwas_hitâ method of your resolver. That will return the facade location you consider to have been hit this time around, or if you return None, the breakpoint will be considered not to have been hit.
Note, this feature is also useful if you donât intend to present facade locations since it essentially provides a scripted breakpoint condition. Every time one of the locations in your breakpoint is hit, you can run the code in your âwas_hitâ to determine whether to consider the breakpoint hit or not, and return the location you were passed in if you want it to be a hit, and None if not.
The Facade location adds these optional affordances to the Resolver class:
Name |
Arguments |
Description |
---|---|---|
|
|
This will get called when one of the ârealâ locations set by your resolver is hit. |
|
|
Use this to provide a helpful description of each facade location. |