Detecting Patterns With Recognizers#
Frame recognizers allow for retrieving information about special frames based on ABI, arguments or other special properties of that frame, even without source code or debug info. Currently, one use case is to extract function arguments that would otherwise be inaccessible, or augment existing arguments.
Frame recognizers also allow you to implement “trampoline handlers”. lldb has built-in trampoline handlers, for instance, to go from a shared library stub to its target, from the dispatch of an objc message send to its target, etc, or from std::function to its target. They work by recognizing the start point of the stub, and providing a “step through” Thread Plan that drives the thread to the trampoline target.
Frame recognizers already do the “identify the target” part. To provide the
step through, have your frame recogizer implement the get_step_through_plan
method:
def get_step_through_plan(self, thread : lldb.SBThread):
It should return a Python dictionary with two keys:
class_name : the name of the class implementing the step through thread plan
extra_args : a dictionary with the keys that will be passed to the __init__
of your scripted thread plan.
and lldb will push a ScriptedThreadPlan using the class and extra_args provided.
Adding a custom frame recognizer is done by implementing a Python class and
using the frame recognizer add command. The Python class should implement the
get_recognized_arguments method and it will receive an argument of type
lldb.SBFrame representing the current frame that we are trying to recognize.
The method should return a (possibly empty) list of lldb.SBValue objects that
represent the recognized arguments.
An example of a recognizer that retrieves the file descriptor values from libc functions ‘read’, ‘write’ and ‘close’ follows:
class LibcFdRecognizer:
def get_recognized_arguments(self, frame: lldb.SBFrame):
if frame.name in ["read", "write", "close"]:
fd = frame.EvaluateExpression("$arg1").unsigned
target = frame.thread.process.target
value = target.CreateValueFromExpression("fd", "(int)%d" % fd)
return [value]
return []
The file containing this implementation can be imported via command script import
and then we can register this recognizer with frame recognizer add.
It’s important to restrict the recognizer to the libc library (which is
libsystem_kernel.dylib on macOS) to avoid matching functions with the same name
in other modules:
(lldb) command script import .../fd_recognizer.py
(lldb) frame recognizer add -l fd_recognizer.LibcFdRecognizer -n read -s libsystem_kernel.dylib
When the program is stopped at the beginning of the ‘read’ function in libc, we can view the recognizer arguments in ‘frame variable’:
(lldb) b read
(lldb) r
Process 1234 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3
frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read
(lldb) frame variable
(int) fd = 3