Configuring GDB ImageWatch to visualize custom buffer types


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 {
  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_ +

  ~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
  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/giwscripts/ 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_metadata(obj_name, picked_obj, debugger_bridge). 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_metadata(obj_name, picked_obj, debugger_bridge) must return a dict containing the elements below, in the following order:

  • display_name: Name of the buffer as it must appear in the ImageWatch window. Can be customized to also show its typename, for instance.
  • 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_FLOAT64
  • row_stride: Number of pixels that exist between two vertically adjacent pixels. Also known as row step. Must be int.
  • pixel_layout: String describing how internal channels should be ordered for display purposes. The default value for buffers of 3 and 4 channels is 'bgra', and 'rgba' for images of 1 and 2 channels. This string must contain exactly four characters, and each one must be one of 'r', 'g', 'b' or 'a'. Repeated channels, such as 'rrgg' are also valid.

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

# -*- coding: utf-8 -*-

This module is concerned with the analysis of each
variable found by the debugger, as well as identifying
and describing the buffers that should be plotted in the
ImageWatch window.

from giwscripts import symbols

def get_buffer_metadata(obj_name,
  buffer = debugger_bridge.get_casted_pointer('char',
  if buffer == 0x0:
    raise Exception('Received null buffer!')

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

  channels = int(picked_obj['channels_'])
  row_stride = int(width/channels)
  pixel_layout = 'rgba'

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

  return {
    'display_name': str(picked_obj.type) + ' ' +
    'pointer': buffer,
    'width': width,
    'height': height,
    'channels': channels,
    'type': type_value,
    'row_stride': row_stride,
    'pixel_layout': pixel_layout

def is_symbol_observable(symbol):
  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
  symbol_type = str(symbol.type)
  # In practice, you should use regular expressions to
  # handle all type modifiers gracefully
  return symbol_type == 'Image' or \
         symbol_type == 'Image *'

After making any changes to resources/giwscripts/, you must rebuild/reinstall the plugin in order to the changes to make effect.

Final thoughts

There’s nothing preventing you from making more sophisticated changes to the resources/giwscripts/ script. For instance, there may be multiple buffer types that you’d like to be able to inspect simultaneously, 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.


Leave a Reply

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

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

Google photo

You are commenting using your Google 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 )

Connecting to %s