Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add functions in gdb at runtime

Tags:

c++

gdb

I'm trying to debug some STL based C++ code in gdb. The code has something like

int myfunc()
{
   std::map<int,int> m;
   ...
}

Now in gdb, inside myfunc using "print m" gives something very ugly. What I've seen recommended is compiling something like

void printmap( std::map<int,int> m )
{
   for( std::map<int,int>::iterator it = ... )
   {
      printf("%d : %d", it->first, it->second );
   }
}

Then in gdb doing

(gdb) call printmap( m )

This seems like a good way to handle the issue... but can I put printmap into a seperate object file (or even dynamic library) that I then load into gdb at runtime rather than compiling it into my binary - as recompiling the binary every time I want to look at another STL variable is not fun .. while compiling and loading a single .o file for the print routine may be acceptable.


UPDATE:

Prompted by Nikolais suggestion I'm looking at dlopen/dlsym.

So I haven't yet got this working but it feels like I'm getting closer.

In printit.cpp

#include <stdio.h>

extern "C" void printit()
{
  printf("OMG Fuzzies");
}

Compile to a .so using

g++ -Wall -g -fPIC -c printit.cpp
g++ -shared -Wl,-undefined,dynamic_lookup -o printit.so printit.o

Start my test application and load the .so using dlopen ( 2 = RTLD_NOW ) then try to get the symbol for a debugging function using dlsym.

(gdb) break main
(gdb) run
(gdb) print (void*) dlopen("printit.so", 2 )
$1 = (void *) 0x100270
(gdb) print (void*) dlsym( 0x100270, "_printit" )
$2 = (void *) 0x0

So close but for some reason I cant get that symbol... ( I cant even get it if I put the dlopen/dlsym calls in my executable) I'm guessing I'm either compiling the lib wrong or using dlsym incorrectly.

If I can get the symbol I'm assuming I can call the function using something like

(gdb) print (( void(*)() )(0x....))()

I'm compiling this on OS X 10.4, which might be causing some of my .so woes... any pointers would be appreciated.


Found out how to get all this working. Have posted as a solution below.

like image 344
Michael Anderson Avatar asked Apr 09 '10 02:04

Michael Anderson


2 Answers

So my solution is to load a shared object containing my debugging routines at run time, using dlopen. Turns out it is even simpler than I thought when you get all the compile flags right.

On OS X this means you compile your application and debugging object like this:

all : application.x debug_helper.so

application.x : application.cpp
    g++ -g application.cpp -o application.x -fPIC

debug_helper.so : debug_helper.o
    g++ -dynamiclib -o debug_helper.so debug_helper.o

debug_helper.o : debug_helper.cpp
    g++ -Wall -g -fPIC -c debug_helper.cpp

The -fPIC on the application is critical, as is the -dynamiclib (rather than trying the linux -shared flag )

An example debug_helper.cpp might look like this

#include <map>
#include <stdio.h>

extern "C" 
void printMap( const std::map<int,int> &m )
{
  printf("Map of size %d\n", int(m.size()) );
  for( std::map<int,int>::const_iterator it = m.begin(); it!=m.end(); ++it )
  {
    printf("%d : %d \n", it->first, it->second );
  }
  fflush(stdout);
}

Don't know why I chose to use stdio rather than iostream stuff... I guess you can use either. (just don't forget to flush the streams...)

Now my application file looks like this:

#include <map>

int main()
{
  std::map<int,int> m;
  m[1]=2;
  m[2]=5;
  m[3]=10;
  m[4]=17;
}

And here's an example debugging session (some output removed)

Start the application and break at an interesting point

(gdb) break main
(gdb) run
Reading symbols for shared libraries +++. done   
Breakpoint 1, main () at test.cpp:5
5     std::map<int,int> m;

Load in the debug helper library

(gdb) print (void*) dlopen("./debug_helper.so",2)
Reading symbols for shared libraries . done
$1 = (void *) 0x100270
(gdb) n
6     m[1]=2;

GDB is smart and catches all the new symbols for us so we don't need to use dlsym etc. We can just call the functions directly.

(gdb) call printMap(m)
Map of size 0
(gdb) n
(gdb) n
(gdb) n
9     m[4]=17;
(gdb) call printMap(m)
Map of size 3
1 : 2 
2 : 5 
3 : 10 

Let's add some more info to printMap. First, unload the library.

(gdb) print (int) dlclose($1)
$2 = 0

Edit the source to add in the sum of the entries. Recompile and then load the new library back into gdb (without restarting the executable or gdb )

(gdb) print (void*) dlopen("./debug_helper.so",2)
Reading symbols for shared libraries . done
$3 = (void *) 0x100270

Use the modified function

(gdb) call printMap(m)
Map of size 3
1 : 2 
2 : 5 
3 : 10 
SUM = 17

I think this does everything that I need.

like image 58
Michael Anderson Avatar answered Oct 07 '22 09:10

Michael Anderson


What you are asking for is not directly possible as far as I know. There's a close alternative though (who said that about one more level of indirection? :)

Build a separate dynamic library with all your printer routines, then add lazy load print wrappers to your program. By that I mean something along the lines:

/// this is in your program, lazy print wrapper
void print_map( const std::map<int,int>& m ) // NOTE THE CONST REFERENCE
{
    static bool loaded = false;
    static void* print_lib = 0;
    static void (*print_func_ptr)( const std::map<int,int>& ) = 0;

    if ( !loaded )
    {
        // dlopen dynamic lib, check for errors, assign to print_lib
        // dlsym the library function by name, assign to print_func_ptr
        loaded = true;
    }

    print_func_ptr( m );
}

Then you can call print_map in the gdb session and the library would load automagically. Note that code above accepts the map by const reference. The function you put in the question would make a copy of its argument.

Also take a look here for some ways to make gdb produce better output for STL containers.

like image 34
Nikolai Fetissov Avatar answered Oct 07 '22 08:10

Nikolai Fetissov