gdb: Writing a Frame Filter

 
 23.2.2.11 Writing a Frame Filter
 ................................
 
 There are three basic elements that a frame filter must implement: it
 must correctly implement the documented interface (SeeFrame 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.  SeePython 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 (SeeFrame 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.
 
 Eliding Frames
 --------------
 
 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.