This change allows you to make a breakpoint resolver kernel in Python.
The breakpoint search mechanism in lldb works on top of a generic mechanism that uses a pair of Searcher - with its associated SearchFilter - and Resolver. The Searcher iterates through the contours of a target, stopping at a depth (module, comp_unit, function...) specified by the Resolver. If the object at the requested depth matches the search filter, then the Searcher calls the Resolver's SearchCallback function, handing it a SymbolContext representing that stage of the search.
In the case of a BreakpointResolver, if the Resolver finds any addresses in that SymbolContext which it wants to break on, it calls AddLocation on its owning Breakpoint to add that location to the breakpoint.
This change allows you to write a simple Python class to add whatever collection of locations makes sense using whatever logic you want. The class must provide a callback method that takes a SBSymbolContext. This will get called at each appropriate stage of the search. You can optionally provide a method that returns the search depth (which defaults to Module if unspecified), and a help method that will be used in the breakpoint description.
When objects of the given class are constructed to represent a specific breakpoint, they are passed a StructuredData object which can be used to parametrize that particular breakpoint. From the SB API's you can pass in an arbitrary SBStructuredData. From the command line I added -k and -v options to "break set" that you provide in pairs to build up a StructuredData::Dictionary which is passed to the resolver. Also, from the command-line the -f and -s options are used to construct the SearchFilter for the breakpoint's Searcher.
For instance, a simple full symbol name breakpoint can be implemented with:
> cat resolver.py import lldb class Resolver: def __init__(self, bkpt, extra_args, dict): self.bkpt = bkpt self.extra_args = extra_args def __callback__(self, sym_ctx): sym_item = self.extra_args.GetValueForKey("symbol") if not sym_item.IsValid(): return sym_name = sym_item.GetStringValue(1000) sym = sym_ctx.module.FindSymbol(sym_name, lldb.eSymbolTypeCode) if sym.IsValid(): self.bkpt.AddLocation(sym.GetStartAddress()) def get_short_help(self): return "I am a python breakpoint resolver" > lldb a.out (lldb) target create "a.out" Current executable set to 'a.out' (x86_64). (lldb) command script import resolver.py (lldb) break set -P resolver.Resolver -k symbol -v break_on_me (lldb) break set -P resolver.Resolver -k symbol -v break_on_me Breakpoint 1: where = a.out`break_on_me at main.c:5, address = 0x0000000100000f40 (lldb) break list Current breakpoints: 1: I am a python breakpoint resolver, locations = 1 1.1: where = a.out`break_on_me at main.c:5, address = a.out[0x0000000100000f40], unresolved, hit count = 0
The functionality is advanced enough that this is useful, and I don't expect that will change much. There are tests for all of this.
There are some future work items:
Docs are forthcoming.
Serialization is not all the way working, but I think it's worth getting the current state of things in before I tackle that.
I also need to invent a way for the init to vet its incoming arguments and return an error - which will abort the breakpoint creation - if they don't have some necessary entries.
Why does this need to be public?