Excepciones en manejadores de señales

Cuando se aborta un programa por una excepción de C++ no manejada, a veces es posible usar un depurador para encontrar el lugar en el que la excepción se lanzó. Esto es más difícil de lo normal si un manejador de señales lanzó la excepción.

Esta sección describe principalmente qué puede esperar en un sistema Linux, cuando usa el depurador gdb.

Primero, mire el ejemplo simple en el que se lanza una excepción desde una función normal (sin manejador de señales).

// without_signal.cc
#include <gtkmm.h>

bool throwSomething()
{
  throw "Something";
  return true;
}

int main(int argc, char** argv)
{
  throwSomething();
  Glib::RefPtr<Gtk::Application> app =
    Gtk::Application::create(argc, argv, "org.gtkmm.without_signal");
  return app->run();
}

Aquí hay un extracto de una sesión de gdb. Sólo se muestran las partes más interesantes de la salida.

> gdb without_signal
(gdb) run
terminate called after throwing an instance of 'char const*'

Program received signal SIGABRT, Aborted.
(gdb) backtrace
#7  0x08048864 in throwSomething () at without_signal.cc:6
#8  0x0804887d in main (argc=1, argv=0xbfffecd4) at without_signal.cc:12
Puede ver que la excepción se lanzó desde without_signal.cc, en la línea 6 (throw "Something";).

Ahora vea qué pasa cuando una excepción se lanza desde un manejador de señales. Aquí está el código fuente.

// with_signal.cc
#include <gtkmm.h>

bool throwSomething()
{
  throw "Something";
  return true;
}

int main(int argc, char** argv)
{
  Glib::signal_timeout().connect(sigc::ptr_fun(throwSomething), 500);
  Glib::RefPtr<Gtk::Application> app =
    Gtk::Application::create(argc, argv, "org.gtkmm.with_signal");
  app->hold();
  return app->run();
}

Y aquí hay un extracto de una sesión de gdb.

> gdb with_signal
(gdb) run
(with_signal:2703): glibmm-ERROR **:
unhandled exception (type unknown) in signal handler

Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb) backtrace
#2  0x0063c6ab in glibmm_unexpected_exception () at exceptionhandler.cc:77
#3  Glib::exception_handlers_invoke () at exceptionhandler.cc:150
#4  0x0063d370 in glibmm_source_callback (data=0x804d620) at main.cc:212
#13 0x002e1b31 in Gtk::Application::run (this=0x804f300) at application.cc:178
#14 0x08048ccc in main (argc=1, argv=0xbfffecd4) at with_signal.cc:16
La excepción se atrapa en glibmm, y el programa termina con una llamada a g_error(). Otras excepciones pueden resultar en un comportamiento diferente, pero en cualquier caso la excepción desde el manejador de señales se atrapa en glibmm o gtkmm, y gdb no puede ver dónde se lanzó.

Para ver dónde se lanza la excepción, puede usar el comando catch throw de gdb.

> gdb with_signal
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) run
Catchpoint 1 (exception thrown), 0x00714ff0 in __cxa_throw ()
(gdb) backtrace
#0  0x00714ff0 in __cxa_throw () from /usr/lib/i386-linux-gnu/libstdc++.so.6
#1  0x08048bd4 in throwSomething () at with_signal.cc:6
(gdb) continue
Continuing.
(with_signal:2375): glibmm-ERROR **
unhandled exception (type unknown) in signal handler

Program received signal SIGTRAP, Trace/breakpoint trap.

Si se atrapan muchas excepciones antes de la que interesa, este método puede ser tedioso. Puede automatizarse con los siguientes comandos de gdb.

(gdb) catch throw
(gdb) commands
(gdb)   backtrace
(gdb)   continue
(gdb)   end
(gdb) set pagination off
(gdb) run
Estos comandos imprimirán una traza inversa de cada throw y continuará. La traza inversa del último (o posiblemente del penúltimo) throw antes de que el programa se detenga, es la interesante.