C++, GDB y Python

Marzo 29, 2010

Descarga el fichero aquí.

Hace unos días, construyendo una versión de prueba del compilador GCC (4.5.0), me encuentro con que instala un misterioso fichero llamado libstdc++.so.6.0.14-gdb.py. GDB, ¡C++ y Python en el mismo fichero! Pero, ¿qué es esto y para qué sirve?

Resulta que en GDB 7.0 y posteriores se puede utilizar Python como lenguaje de scripting para crear nuevos comandos y añadir nuevas funcionalidades.
Naturalmente, esta funcionalidad es opcional y la decide quien compila el depurador. Puedes confirmar si tu depurador utiliza Python con:

$ ldd /usr/bin/gdb
	linux-gate.so.1 =>  (0x00d79000)
	libreadline.so.6 => /lib/libreadline.so.6 (0x00a4a000)
	libpython2.6.so.1.0 => /usr/lib/libpython2.6.so.1.0 (0x0043a000)
        ...

O también:

$ gdb
...
(gdb) python print "ok"
ok

Si en lugar de “ok” dice algo como “comando indefinido” es que no lo tienes.

Uno de los usos más interesantes de esta funcionalidad del depurador es la del pretty printing, es decir, mostrar en contenido de variables de ciertos tipos de forma un poco más elaborada a cómo lo hace por defecto.
Esto es más cierto cuando hablamos de C++. Veamos el siguiente ejemplo sencillo:

1
2
3
4
5
6
7
8
9
10
11
12
13
//fichero test.cpp
#include <string>
#include <vector>
 
int main()
{
    std::string s("hola");
    std::vector<std::string> v;
    v.push_back(s);
    v.push_back("mundo");
    v.push_back("!");
    return 0;
}

Lo compilamos con:

$ g++ -o test test.cpp -O0 -g -Wall

Y ahora lo depuramos con los siguientes comandos:

$ gdb ./test
...
(gdb) br 12
Punto de interrupción 1 at 0x80489fe: file test.cpp, line 12.
(gdb) run
Starting program: /home/rodrigo/projects/test 
Breakpoint 1, main () at test.cpp:12
12	    return 0;
(gdb) info local
s = {
  static npos = 4294967295, 
  _M_dataplus = {
    <std::allocator<char>> = {
      <__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, 
    members of std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider: 
    _M_p = 0x804c014 "hola"
  }
v = {
  <std::_Vector_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >> = {
    _M_impl = {
      <std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >> = {
        <__gnu_cxx::new_allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >> = {<No data fields>}, <No data fields>}, 
      members of std::_Vector_base<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::_Vector_impl: 
      _M_start = 0x804c070, 
      _M_finish = 0x804c07c, 
      _M_end_of_storage = 0x804c080
    }
  }, <No data fields>}
}

El comando br inserta un punto de parada (breakpoint en la última línea del programa, y luego el comando info local muestra las variables locales (con print podemos ver variables individuales).
Lo cierto es que el contenido de las variables no es muy fácil de interpretar. Con un poco de práctica y guiñando mucho los ojos podemos ver que la cadena s probablemente contiene “hola”, y el vector v contiene 0×804c07c – 0×804c070 bytes de datos, que son…

(gdb) print (0x804c07c - 0x804c070) / sizeof(std::string)
$1 = 3

…3 elementos. Ver esos elementos es aún más engorroso.

Ciertamente, la experiencia de usuario es “mejorable. Y aquí es donde interviene el Python. El fichero del que hablaba al principio es leído por el depurador al cargar la librería de C++ y registra una serie de funciones Python que serán llamadas para formatear el valor de los principales tipos de la librería estándar.
Así, ahora la misma sesión de depuración tendrá la siguiente pinta:

$ gdb ./test
...
(gdb) br 12
Punto de interrupción 1 at 0x80489fe: file test.cpp, line 12.
(gdb) run
Starting program: /home/rodrigo/projects/test 
Breakpoint 1, main () at test.cpp:12
12	    return 0;
(gdb) info local
s = "hola"
v = std::vector of length 3, capacity 4 = {"hola", "mundo", "!"}

¡Ahora sí que se entiende!
De la misma manera se embellecen los otros tipos contenedores (deque, map, set, etc.) y los iteradores correspondientes.

Instalación

Para que GDB encuentre el script de Python y lo cargue automáticamente debe estar en el mismo directorio de la librería de C++ y llamarse igual que ella pero añadiendo el sufijo -gdb.py. Es importante notar que se debe usar como base el nombre real de la librería, nunca el de alguno de los múltiples enlaces simbólicos que suelen apuntarle. En mi máquina, por ejemplo, la librería se llama /usr/lib/libstdc++.so.6.0.13, por lo que el script debe llamarse /usr/lib/libstdc++.so.6.0.13-gdb.py.

Puedes descargar el fichero aquí. Naturalmente, habiendo salido de la librería estándar de C++ de GNU (ligeramente modificado por mí para hacerlo más compatible con el GCC 4.4), se publica bajo la licencia GPL.

Artículos relacionados:

  1. Resolviendo un Sudoku con Python El otro día estaba haciendo un Sudoku con lápiz y...
  2. APOD como fondo de escritorio GNOME Descarga el programa: apod.zip. APOD (de Astronomic Picture of the...

3 Responses to “C++, GDB y Python”

  1. Como siempre posteando cosas interesantes.
    Saludos :)

  2. Muchísimas gracias!
    Me ha resultado extremadamente útil!

    Te añado a favoritos.

  3. De nada!
    Siempre es bueno saber que resulta útil a alguien.

Deja un comentario