Linux debug shared library loading

How to make gdb print symbols in shared libraries loaded with dlopen?

I want to debug a process running on Linux 2.6 using GDB. attach PID (where PID is the process ID), print main , print sin , print gzopen and print dlopen work (i.e. they find the respective symbols). But print myfoo doesn’t work, where myfoo is a function loaded by the process from an .so file using dlopen . Here is what I get:

(gdb) print main $3 = 0x805ba90 (gdb) print sin $4 = > 0xb7701230 (gdb) print gzopen $5 = > 0xb720df50 (gdb) print dlopen $6 = > 0xb77248e0 (gdb) print myfoo No symbol "myfoo" in current context. 

How do I get GDB to find myfoo ? The function myfoo does indeed exist, because in the program I managed to get its address using dlsym (after dlopen ), and I managed to call it. Only after that I attached GDB to the process. It turned out that there was a mydir/mylib.so: No such file or directory error message printed by the attach $PID command of GDB. Apparently GDB was started in the wrong directory. Doing the proper cd before starting GDB fixed the problem, and print myfoo started working. I’d like to automate this: I want GDB figure out where my .so files (loaded with dlopen ) are. An approximation I can think of is examining /proc/$PID/maps (on Linux), finding possible directories, and adding all of them to the GDB library search path before starting GDB. Extending LD_LIBRARY_PATH and doing a set solib-search-path /tmp/parent didn’t work ( ls -l /tmp/parent/mydir/myfoo.so does work), GDB still reported the No such file or directory . How do I tell GDB where to look for mydir/myfoo.so ? My other question is how do I get the list of possible directories? On Linux, /proc/$PID/maps contains them — but what about other operating systems like FreeBSD and the Mac OS X?

Источник

Few GDB Commands – Debug Core, Disassemble, Load Shared Library

GDB is an essential tool for programmers to debug their code. This article explains how you can use gdb to debug a program with the core file, how to display assembly language instructions of your program, and how to load shared library programs for debugging.

Debug a Program with Core File

A core file or core dump is a file that records the memory image of a running process and its status. It is used in post-mortem debugging of a program that crashed while it ran outside a debugger.

$ gdb executable_name core_file_name (gdb)

The above command will load the core file for the executable and prompts a gdb shell. You can use gdb backtrace or other commands to examine what really happened. Note that the core_file will be ignored, if the executable is running under gdb.

Читайте также:  Gamepad xbox one on linux

You can use the disassemble command to print the assembly instruction of a function. You can also specify 2 address ranges, and the instructions between them will be disassembled and printed in gdb console.

(gdb) disassemble main Dump of assembler code for function main: 0x00000000004004ac : push %rbp 0x00000000004004ad : mov %rsp,%rbp 0x00000000004004b0 : mov $0x0,%eax 0x00000000004004b5 : pop %rbp 0x00000000004004b6 : retq End of assembler dump.

Load Shared library Symbols

Many a times, programmers will use shared libraries in their code. Sometimes, we might want to look into the shared library itself to understand what’s going on. Here I’ll show an example using GLib Library and how to obtains the debugging information for it.

By default, all distributions will strip the libraries to some extent. The complete debugging information will be stored in a separate package which they name like “package-1.0-dbg”, and only if needed user can install. When you install the “package-1.0-dbg”, by default gdb will load all the debugging information, but to understand the concept here we will see how to manually load the symbol file.

#include #include struct a < int a; int b; >; void *print( struct a *obj,int as) < printf("%d:%d\n",obj->a,obj->b); > int main() < struct a *obj; obj = (struct a*)malloc(sizeof(struct a)); obj->a=3; obj->b=4; GList *list=NULL; list = g_list_append(list,obj); g_list_foreach(list,(GFunc)print,NULL); >
$ cc -g -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include/ -lglib-2.0 glib_test.c
(gdb) b 1 Breakpoint 1 at 0x4007db: file a.c, line 1. (gdb) run . (gdb) info sharedlibrary From To Syms Read Shared Object Library 0x00007ffff7dddaf0 0x00007ffff7df5c83 Yes (*) /lib64/ld-linux-x86-64.so.2 0x00007ffff7b016c0 0x00007ffff7b6e5cc Yes (*) /lib/x86_64-linux-gnu/libglib-2.0.so.0 0x00007ffff7779b80 0x00007ffff7890bcc Yes (*) /lib/x86_64-linux-gnu/libc.so.6 0x00007ffff751f9a0 0x00007ffff7546158 Yes (*) /lib/x86_64-linux-gnu/libpcre.so.3 0x00007ffff7307690 0x00007ffff7312c78 Yes (*) /lib/x86_64-linux-gnu/libpthread.so.0 0x00007ffff70fc190 0x00007ffff70ff4f8 Yes (*) /lib/x86_64-linux-gnu/librt.so.1 (*): Shared library is missing debugging information.

From the above information, note that the library libglib-2.0.so.0 is having symbols, but the debuuging information like file_name, line_no etc… are missing. Download the debug information for the package from respective distribution (libglib2.0-0-dbg in Debian – Wheezy).

(gdb) add-symbol-file /home/lakshmanan/libglib-2.0.so.0.3200.4 0x00007ffff7b016c0 add symbol table from file "/home/lakshmanan/libglib-2.0.so.0.3200.4" at .text_addr = 0x7ffff7b016c0 (y or n) y Reading symbols from /home/lakshmanan/libglib-2.0.so.0.3200.4. done.

The address given in the add-symbol-file command is, the “From” address printed by “info sharedlibrary” command. Now the debugging information is loaded.

. . (gdb) n g_list_foreach (list=0x0, func=0x4007cc , user_data=0x0) at /tmp/buildd/glib2.0-2.33.12+really2.32.4/./glib/glist.c:897

Sometimes the shared libraries won’t even have any symbols in it, and in those situations, the above method will be helpful.

Читайте также:  Как создать скрипт на линуксе

Источник

Loading, unloading & reloading shared libraries

Using dynamic linker functions to load, unload and reload our code into a process.

Recently I’ve been working on various projects for different Source games, all of which are loaded into the game process as a shared library. Everything works well, apart from when any changes need to be made to the code. Up until now I’ve been restarting the game every time I changed even a single line, simply because I wasn’t sure how to unload the library. Fortunately, this is no longer an issue.

Firstly, let’s think about how we might load our library into a target process. There are a few methods and tools I’ve tried, including linux-inject — which I couldn’t get to work at all, and hotpatch, which worked well but only for 64-bit processes. Putting our library in the LD_PRELOAD variable is another notable mention but we would rather load our code on-demand. That said, I’ve had a 100% success rate with both 32-bit and 64-bit processes using the GNU Debugger, or gdb for short.

Loading the library

Make sure that you have a constructor and destructor function in your library. As you might expect, the constructor function will be called when the library is loaded and the destructor will be called before it is unloaded from the process.

void __attribute__((constructor)) startup(); void __attribute__((destructor)) shutdown(); 

Run gdb with elevated permissions and attach to the target process. Once it’s finished loading you’ll be left with a command shell. Depending on your distribution, you might need to allow ptrace to be used on non-child processes.

$ sudo gdb -ex "attach $(pidof csgo_linux64)"

Export the functions we’re going to use into some variables to make things easier for ourselves. If you’re wondering where the arguments came from, they’re the function prototypes for dlopen and dlclose from the glibc dlfcn header.

(gdb) set $dlopen = (void*(*)(char*, int)) dlopen (gdb) set $dlclose = (int(*)(void*)) dlclose

Now we can finally load our library. To do this, we’ll use dlopen which will automatically invoke the constructor function. This will return a handle which we can use later when we’ve finished testing and need to unload our library.

(gdb) set $library = $dlopen("/home/aixxe/devel/rekoda-csgo/librekoda-csgo.so", 1)

It probably looks like nothing happened but if everything went well then our library is now loaded.

Attaching..

We’re all done for now so we can safely resume the target process and test our code.

Unloading the library

Now that we’ve finished testing it turns out we need to change something, we’re going to recompile our library and load it again. This is usually where I would restart the process but this is no longer necessary. Switch back to gdb and press CTRL+C to send an interrupt signal. This will suspend the process again and we should be back in the debugging shell.

Thread 1 "csgo_linux64" received signal SIGINT, Interrupt. (gdb) call $dlclose($library) (gdb) continue Continuing.

Detaching..

Now that the reference counter for our library has dropped to zero it should automatically be unloaded.

Читайте также:  Зайти на флешку linux

Reloading the library

We’ve made our changes to the code and recompiled. Let’s load it back into the game process now. We already have our gdb session from earlier running so we only need to call dlopen again.

(gdb) set $library = $dlopen("/home/aixxe/devel/rekoda-csgo/librekoda-csgo.so", 1)

Reattaching..

It’s that simple. Actually, this whole thing is a bit tedious in the long run, we should write some scripts to automate this as soon as possible. Here’s one I prepared earlier, this is just for loading.

if grep -q \"$(realpath $2)\" /proc/$(pidof $1)/maps; then exit fi gdb -n -q -batch -ex "attach $(pidof $1)" \ -ex "set \$dlopen = (void*(*)(char*, int)) dlopen" \ -ex "call \$dlopen(\"$(realpath $2)\", 1)" \ -ex "detach" \ -ex "quit"

And this one is for unloading. Both take the same arguments, the process name and the library filename.

if grep -q \"$(realpath $2)\" /proc/$(pidof $1)/maps; then gdb -n -q -batch -ex "attach $(pidof $1)" \ -ex "set \$dlopen = (void*(*)(char*, int)) dlopen" \ -ex "set \$dlclose = (int(*)(void*)) dlclose" \ -ex "set \$library = \$dlopen(\"$(realpath $2)\", 6)" \ -ex "call \$dlclose(\$library)" \ -ex "call \$dlclose(\$library)" \ -ex "detach" \ -ex "quit" fi

Note that dlclose is called twice in the unloading script. Since these are two separate scripts we can’t keep the handle we get from loading and use it in the unloading script. So when we call dlopen again in the unloading script we increment the reference count to two.

The dl library maintains reference counts for library handles, so a dynamic library is not deallocated until dlclose() has been called on it as many times as dlopen() has succeeded on it.

Therefore, we have to call dlclose twice in order to decrement the reference count to zero. That’s all! You can even add the scripts to your Makefile if you want to automatically reload when recompiling.

You may also want to add -static-libstdc++ to your linker options if you’re having issues with some features of the standard template library, or if the destructor is called as soon as you load your library.

TARGET = csgo_linux64 OUT := librekoda-csgo.so
detach: sudo ./detach.sh $(TARGET) $(OUT) attach: sudo ./attach.sh $(TARGET) $(OUT)

Источник

Оцените статью
Adblock
detector