Loading code into an active GDB session
Sometimes one is in the middle of a debugging session and one would like to add some new code. For example, you may want to whip up a function that prints an object a certain way and it's just a temporary hack and it's just easier to do in C/C++. Or you may want some kind of complex test to use as the condition for the breakpoint, and you don't want to relink the program.
For situations such as these the following is an option to try. It involves calling dlopen from GDB to load the new code into the running inferior. ["inferior" is GDB parlance for the program you are debugging.] Once the code is loaded it is accessible from GDB.
Here is a simple and somewhat contrived example. [A better example is most welcome. :-)] This example is specific to Linux (and presumably other *nix Systems). If your system is not one of these perhaps this example can be adapted for it.
Alas, while it would be nice if this example Just Worked out of the box, it uses dlopen and thus requires libdl.so linked into your program. Heads up.
Here is the program we are debugging.
birthdays.h:
#include <map> #include <vector> #include <string> // A table mapping birthdays 1-31 to names of people having // birthdays on that day. typedef std::map<int, std::vector<std::string> > birthdays_type;
birthdays.cc:
#include <iostream> #include <cstdlib> #include "birthdays.h" using namespace std; birthdays_type birthdays; static void init () { birthdays[8].push_back ("Ann"); birthdays[8].push_back ("Claire"); birthdays[16].push_back ("John"); } static void usage () { cerr << "Usage: birthdays number(1-31)\n"; exit (1); } static void print (const string& s) { cout << s << "\n"; } static void print_matching (int day) { const vector<string>& people = birthdays[day]; for (string p : people) print (p); } int main (int argc, char *argv[]) { if (argc != 2) usage (); int day = atoi (argv[1]); init (); print_matching (day); return 0; }
Suppose we're in the middle of a GDB session and we want to stop when we're examining the entry for "Claire".
bash$ gdb a.out (gdb) start 8 Temporary breakpoint 1 at 0x401447: file birthdays.cc, line 45. Starting program: /home/dje/src/play/birthdays.x64 8 Temporary breakpoint 1, main (argc=2, argv=0x7fffffffe7f8) at birthdays.cc:45 45 if (argc != 2)
Since it's stored as a string class doing this test is perhaps not complex but certainly more effort than just a simple strcmp.
So we have the following helper function:
int is_claire (string *s) { return *s == "Claire"; }
Now we just need to add it to our session.
First, let's set up some helper convenience variables:
(gdb) set $dlopen = (void*(*)(char *, int)) dlopen (gdb) set $dlsym = (void*(*)(void*, char *)) dlsym (gdb) set $dlclose = (int(*)(void*)) dlclose
That may not work however. libdl.so exports these symbols as versioned symbols, e.g., dlopen@@GLIBC_2.2.5. So if the above doesn't work try one of these:
(gdb) set $dlopen = (void*(*)(char *, int)) 'dlopen@@GLIBC_2.2.5' (gdb) set $dlsym = (void*(*)(void*, char *)) 'dlsym@@GLIBC_2.2.5' (gdb) set $dlclose = (int(*)(void*)) 'dlclose@@GLIBC_2.2.5'
or
(gdb) set $dlopen = (void*(*)(char *, int)) __dlopen (gdb) set $dlsym = (void*(*)(void*, char *)) __dlsym (gdb) set $dlclose = (int(*)(void*)) __dlclose
The __ versions are "for internal use only", but there is Lefler's Law #36: You gotta go with what works.
With those convenience variables in place we can do this:
(gdb) !g++ -shared -fpic helper.cc -o helper.so (gdb) $mylib = $dlopen ("./helper.so", 1) # 1 is RTLD_LAZY (gdb) b print if is_claire (&s) (gdb) c Continuing. Ann Breakpoint 2, print (s="Claire") at birthdays.cc:30 30 cout << s << "\n"; (gdb)
We are now stopped at the desired point.
Important things to remember
We're calling dlopen at a possibly random point in the program. As long as the thread is not stopped inside the dynamic linker this should be ok.
dlopen needs to be available, so at a minimum your program must be linked with -ldl.