Debugging with GDB: Writing a Frame Filter |
---|
Next: Unwinding Frames in Python, Previous: Frame Decorator API, Up: Python API [Contents][Index]
There are three basic elements that a frame filter must implement: it must correctly implement the documented interface (see Frame Filter API), it must register itself with GDB, and finally, it must decide if it is to work on the data provided by GDB. In all cases, whether it works on the iterator or not, each frame filter must return an iterator. A bare-bones frame filter follows the pattern in the following example.
import gdb class FrameFilter(): def __init__(self): # Frame filter attribute creation. # # 'name' is the name of the filter that GDB will display. # # 'priority' is the priority of the filter relative to other # filters. # # 'enabled' is a boolean that indicates whether this filter is # enabled and should be executed. self.name = "Foo" self.priority = 100 self.enabled = True # Register this frame filter with the global frame_filters # dictionary. gdb.frame_filters[self.name] = self def filter(self, frame_iter): # Just return the iterator. return frame_iter
The frame filter in the example above implements the three requirements for all frame filters. It implements the API, self registers, and makes a decision on the iterator (in this case, it just returns the iterator untouched).
The first step is attribute creation and assignment, and as shown in
the comments the filter assigns the following attributes: name
,
priority
and whether the filter should be enabled with the
enabled
attribute.
The second step is registering the frame filter with the dictionary or
dictionaries that the frame filter has interest in. As shown in the
comments, this filter just registers itself with the global dictionary
gdb.frame_filters
. As noted earlier, gdb.frame_filters
is a dictionary that is initialized in the gdb
module when
GDB starts. What dictionary a filter registers with is an
important consideration. Generally, if a filter is specific to a set
of code, it should be registered either in the objfile
or
progspace
dictionaries as they are specific to the program
currently loaded in GDB. The global dictionary is always
present in GDB and is never unloaded. Any filters registered
with the global dictionary will exist until GDB exits. To
avoid filters that may conflict, it is generally better to register
frame filters against the dictionaries that more closely align with
the usage of the filter currently in question. See Python Auto-loading, for further information on auto-loading Python scripts.
GDB takes a hands-off approach to frame filter registration,
therefore it is the frame filter’s responsibility to ensure
registration has occurred, and that any exceptions are handled
appropriately. In particular, you may wish to handle exceptions
relating to Python dictionary key uniqueness. It is mandatory that
the dictionary key is the same as frame filter’s name
attribute. When a user manages frame filters (see Frame Filter Management), the names GDB will display are those contained
in the name
attribute.
The final step of this example is the implementation of the
filter
method. As shown in the example comments, we define the
filter
method and note that the method must take an iterator,
and also must return an iterator. In this bare-bones example, the
frame filter is not very useful as it just returns the iterator
untouched. However this is a valid operation for frame filters that
have the enabled
attribute set, but decide not to operate on
any frames.
In the next example, the frame filter operates on all frames and utilizes a frame decorator to perform some work on the frames. See Frame Decorator API, for further information on the frame decorator interface.
This example works on inlined frames. It highlights frames which are
inlined by tagging them with an “[inlined]” tag. By applying a
frame decorator to all frames with the Python itertools imap
method, the example defers actions to the frame decorator. Frame
decorators are only processed when GDB prints the backtrace.
This introduces a new decision making topic: whether to perform decision making operations at the filtering step, or at the printing step. In this example’s approach, it does not perform any filtering decisions at the filtering step beyond mapping a frame decorator to each frame. This allows the actual decision making to be performed when each frame is printed. This is an important consideration, and well worth reflecting upon when designing a frame filter. An issue that frame filters should avoid is unwinding the stack if possible. Some stacks can run very deep, into the tens of thousands in some cases. To search every frame to determine if it is inlined ahead of time may be too expensive at the filtering step. The frame filter cannot know how many frames it has to iterate over, and it would have to iterate through them all. This ends up duplicating effort as GDB performs this iteration when it prints the frames.
In this example decision making can be deferred to the printing step. As each frame is printed, the frame decorator can examine each frame in turn when GDB iterates. From a performance viewpoint, this is the most appropriate decision to make as it avoids duplicating the effort that the printing step would undertake anyway. Also, if there are many frame filters unwinding the stack during filtering, it can substantially delay the printing of the backtrace which will result in large memory usage, and a poor user experience.
class InlineFilter(): def __init__(self): self.name = "InlinedFrameFilter" self.priority = 100 self.enabled = True gdb.frame_filters[self.name] = self def filter(self, frame_iter): frame_iter = itertools.imap(InlinedFrameDecorator, frame_iter) return frame_iter
This frame filter is somewhat similar to the earlier example, except
that the filter
method applies a frame decorator object called
InlinedFrameDecorator
to each element in the iterator. The
imap
Python method is light-weight. It does not proactively
iterate over the iterator, but rather creates a new iterator which
wraps the existing one.
Below is the frame decorator for this example.
class InlinedFrameDecorator(FrameDecorator): def __init__(self, fobj): super(InlinedFrameDecorator, self).__init__(fobj) def function(self): frame = fobj.inferior_frame() name = str(frame.name()) if frame.type() == gdb.INLINE_FRAME: name = name + " [inlined]" return name
This frame decorator only defines and overrides the function
method. It lets the supplied FrameDecorator
, which is shipped
with GDB, perform the other work associated with printing
this frame.
The combination of these two objects create this output from a backtrace:
#0 0x004004e0 in bar () at inline.c:11 #1 0x00400566 in max [inlined] (b=6, a=12) at inline.c:21 #2 0x00400566 in main () at inline.c:31
So in the case of this example, a frame decorator is applied to all
frames, regardless of whether they may be inlined or not. As
GDB iterates over the iterator produced by the frame filters,
GDB executes each frame decorator which then makes a decision
on what to print in the function
callback. Using a strategy
like this is a way to defer decisions on the frame content to printing
time.
It might be that the above example is not desirable for representing
inlined frames, and a hierarchical approach may be preferred. If we
want to hierarchically represent frames, the elided
frame
decorator interface might be preferable.
This example approaches the issue with the elided
method. This
example is quite long, but very simplistic. It is out-of-scope for
this section to write a complete example that comprehensively covers
all approaches of finding and printing inlined frames. However, this
example illustrates the approach an author might use.
This example comprises of three sections.
class InlineFrameFilter(): def __init__(self): self.name = "InlinedFrameFilter" self.priority = 100 self.enabled = True gdb.frame_filters[self.name] = self def filter(self, frame_iter): return ElidingInlineIterator(frame_iter)
This frame filter is very similar to the other examples. The only
difference is this frame filter is wrapping the iterator provided to
it (frame_iter
) with a custom iterator called
ElidingInlineIterator
. This again defers actions to when
GDB prints the backtrace, as the iterator is not traversed
until printing.
The iterator for this example is as follows. It is in this section of the example where decisions are made on the content of the backtrace.
class ElidingInlineIterator: def __init__(self, ii): self.input_iterator = ii def __iter__(self): return self def next(self): frame = next(self.input_iterator) if frame.inferior_frame().type() != gdb.INLINE_FRAME: return frame try: eliding_frame = next(self.input_iterator) except StopIteration: return frame return ElidingFrameDecorator(eliding_frame, [frame])
This iterator implements the Python iterator protocol. When the
next
function is called (when GDB prints each frame),
the iterator checks if this frame decorator, frame
, is wrapping
an inlined frame. If it is not, it returns the existing frame decorator
untouched. If it is wrapping an inlined frame, it assumes that the
inlined frame was contained within the next oldest frame,
eliding_frame
, which it fetches. It then creates and returns a
frame decorator, ElidingFrameDecorator
, which contains both the
elided frame, and the eliding frame.
class ElidingInlineDecorator(FrameDecorator): def __init__(self, frame, elided_frames): super(ElidingInlineDecorator, self).__init__(frame) self.frame = frame self.elided_frames = elided_frames def elided(self): return iter(self.elided_frames)
This frame decorator overrides one function and returns the inlined
frame in the elided
method. As before it lets
FrameDecorator
do the rest of the work involved in printing
this frame. This produces the following output.
#0 0x004004e0 in bar () at inline.c:11 #2 0x00400529 in main () at inline.c:25 #1 0x00400529 in max (b=6, a=12) at inline.c:15
In that output, max
which has been inlined into main
is
printed hierarchically. Another approach would be to combine the
function
method, and the elided
method to both print a
marker in the inlined frame, and also show the hierarchical
relationship.
Next: Unwinding Frames in Python, Previous: Frame Decorator API, Up: Python API [Contents][Index]