Configuring GDB ImageWatch to visualize custom buffer types

sample_window

gdb-imagewatch is a tool that can be used in conjunction with GDB to visualize in-memory buffers during debug sessions. In the most typical scenario, it’s used to visualize the contents of an OpenCV image being processed by the debugged application. To help with the debugging task, it has many features inspired by the Visual Studio ImageWatch plugin, such as pixel value visualization, support for multiple buffer types and dumping buffers to files; while also containing other helpful features such as buffer rotation.

Since the gdb-imagewatch plugin was created with extensibility in mind, it is very easy to adapt it so that it can support as many buffer types as needed by the programmer. In this post, I will show how to adapt the plugin so that it can display the contents of a custom C++ Image class.

Let’s begin with the sample code to be debugged:

// Compile this code with a C++11 compliant compiler; on gcc, use the
// -std=c++11 flag.
#include <iostream>

class Image {
public:
  Image(int width, int height, int channels) :
  width_(width), height_(height), channels_(channels) {
    contents_ = new unsigned char[width*height*channels];
  }

  // Forbid duplicating references to the same pointer
  Image(const Image&) = delete;
  Image& operator=(const Image&) = delete;

  // Swap pointers in case of move semantics
  Image(Image&& other) {
    *this = std::move(other);
  }
  Image& operator=(Image&& other) {
    width_ = other.width_;
    height_ = other.height_;
    channels_ = other.channels_;
    contents_ = other.contents_;

    other.contents_ = nullptr;

    return *this;
  }

  unsigned char& operator()(int row, int col, int channel) {
      return contents_[(row*width_ + col) * channels_ +
                        channel];
  }

  ~Image() {
    delete[] contents_;
  }

  int width() {
    return width_;
  }

  int height() {
    return width_;
  }

  int channels() {
    return width_;
  }

  unsigned char* contents() {
    return contents_;
  }

// The visibility of the fields is irrelevant to the plugin
private:
  unsigned char* contents_;
  int width_;
  int height_;
  int channels_;
};

int main() {
  Image gradient(50, 50, 1);
  for(int row = 0 ; row < gradient.height(); ++row) {
      for(int col = 0; col < gradient.width(); ++col) {
          gradient(row, col, 0) = col;
      }
  }

  return 0;
}

This sample defines a very simple Image class that behaves similarly to a single_ptr, and has no Region of Interest (ROI) functionality. In order to support it, we need to modify the script resources/gdbiwtype.py. There are two functions in this file that need to be adapted in order for our new Image class to be recognized by gdb-imagewatch.

The first one is is_symbol_observable(symbol). When GDB pauses (e.g. when it hits a breakpoint), it will pass control to gdb-imagewatch, which in turn will inspect for all visible symbols (variables) for that particular context. The parameter symbol is a GDB object with information of that symbol (like name and type), and the function is_symbol_observable must return True if the received symbol belongs to a buffer type (and False otherwise). For the Image class presented in this example, the function must check if the type of the given symbol is either an Image or a pointer to one.

The second function is get_buffer_info(picked_obj). Given that is_symbol_observable decided that any particular symbol was a buffer, the gdb-imagewatch plugin must also receive the relevant, buffer specific data for that symbol. This data can be obtained by fetching the values of the Image class fields from picked_obj, which can be done by dealing with picked_obj as if it were a dict. The function get_buffer_info(picked_obj) must return a tuple containing six elements, in the following order:

  • buffer: Pointer to beginning of buffer data
  • width: Buffer width. Must be int.
  • height: Buffer height. Must be int.
  • channels: Number of channels. Must be int.
  • type: Numeric type of each element contained in the buffer. Must be one of the following: GIW_TYPES_UINT8, GIW_TYPES_UINT16, GIW_TYPES_INT16, GIW_TYPES_INT32, GIW_TYPES_FLOAT32, GIW_TYPES_UINT32
  • step: Number of pixels that exist between two vertically adjacent pixels. Also known as stride. Must be int.

For the sample Image class described above, this is what the script resources/gdbiwtype.py should look like:

GIW_TYPES_UINT8 = 0
GIW_TYPES_UINT16 = 2
GIW_TYPES_INT16 = 3
GIW_TYPES_INT32 = 4
GIW_TYPES_FLOAT32 = 5
GIW_TYPES_UINT32 = 6

##
# Default values created for OpenCV Mat structures. Change
# it according  to your needs.
def get_buffer_info(picked_obj):
  import gdb

  char_type = gdb.lookup_type("char")
  char_pointer_type = char_type.pointer()
  buffer = picked_obj['contents_'].cast(char_pointer_type)
  if buffer==0x0:
    raise Exception('Received null buffer!')

  width = int(picked_obj['width_'])
  height = int(picked_obj['height_'])

  channels = int(picked_obj['channels_'])
  step = int(width/channels)

  # Our Image class only has one buffer type: unsigned char
  type = GIW_TYPES_UINT8

  return (buffer, width, height, channels, type, step)

##
# Returns true if the given symbol is of observable type
# (the type of the buffer you are working with). Make sure
# to check for pointers of your type as well
def is_symbol_observable(symbol):
  symbol_type = str(symbol.type)
  return symbol_type == 'Image' or \
         symbol_type == 'Image *'

After making any changes to resources/gdbiwtype.py, you must go to the folder where you built gdb-imagewatch and run make install again, so that the production scripts are updated.

Final thoughts

There’s nothing preventing you from making more sophisticated changes to the resources/gdbiwtype.py script. For instance, there may be multiple buffer types that you’d like to be able to inspect, and thus is_symbol_observable(symbol) could check for all of them, while get_buffer_info(picked_obj) could return different fields depending on picked_obj.type.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s