In my personal experience, in most cases, GDB debugging can be used to debug the behavior of a specific dynamic library independently. This allows for convenient problem localization directly on the device where the anomaly occurs. This article introduces how to use GDB to locate issues for a single dynamic library on a production device.
Test Program
Here, we need to prepare two programs: one example.c to serve as the main caller, and another sum.c to implement the sum interface, with the code being sufficiently concise.
The code for sum.c is as follows:
int sum(int x, int y)
{
int s = x + y;
return s;
}
The code for example.c is as follows:
#include <stdio.h>
#include "sum.h"
static int y = 2;
int main()
{
int x = 1;
printf("sum=%d \n", sum(x, y));
}
Debugging Dynamic Libraries
First, compile the shared object:
gcc sum.c -shared -fPIC -o libsum.so
Then compile the binary:
gcc example.c -lsum -L./ -o example
The above compilations are done without the -g flag. Therefore, GDB can directly see the symbol status of libsum.so as follows:
# export LD_LIBRARY_PATH=./
# gdb ./example
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x0000fffff7fcd0c0 0x0000fffff7fe5468 Yes (*) /lib/ld-linux-aarch64.so.1
0x0000fffff7fba470 0x0000fffff7fba57c Yes (*) ./libsum.so
0x0000fffff7e26880 0x0000fffff7f15db8 Yes /lib/aarch64-linux-gnu/libc.so.6
No linux-vdso.so.1
It can be seen that libsum.so has no debugging information.
In this case, if we want to debug the content of the sum function in libsum.so, we can only analyze it through registers and disassembly.
A smarter approach is to enable it to load symbols. The simplest way is to compile libsum.so with symbols, as follows:
gcc sum.c -shared -fPIC -g -o libsum.so
If developing natively on a Debian-based operating system, you can automatically or manually load the .debug_info section, or install the corresponding dbg package. This will not be elaborated here.
Now, check the binary loading status as follows:
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x0000fffff7fcd0c0 0x0000fffff7fe5468 Yes (*) /lib/ld-linux-aarch64.so.1
0x0000fffff7fba470 0x0000fffff7fba57c Yes ./libsum.so
0x0000fffff7e26880 0x0000fffff7f15db8 Yes /lib/aarch64-linux-gnu/libc.so.6
No linux-vdso.so.1
It can be seen that libsum.so now contains debugging information and can be debugged directly.
(gdb) b sum
Breakpoint 1 at 0xfffff7fba560: file sum.c, line 3.
(gdb) r
Starting program: /root/gdb/example
Breakpoint 1, sum (x=1, y=2) at sum.c:3
3 int s = x + y;
(gdb) set x=4
(gdb) set y=6
(gdb) c
Continuing.
sum=10
[Inferior 1 (process 994034) exited normally]
The mapping relationship of the dynamic library libsum.so can also be viewed directly in GDB without needing to check /proc, as follows:
(gdb) info proc map
process 1005668
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0xaaaaaaaaa000 0xaaaaaaaab000 0x1000 0x0 /root/gdb/example
0xaaaaaaaba000 0xaaaaaaabb000 0x1000 0x0 /root/gdb/example
0xaaaaaaabb000 0xaaaaaaabc000 0x1000 0x1000 /root/gdb/example
0xfffff7e06000 0xfffff7f60000 0x15a000 0x0 /usr/lib/aarch64-linux-gnu/libc-2.31.so
0xfffff7f60000 0xfffff7f70000 0x10000 0x15a000 /usr/lib/aarch64-linux-gnu/libc-2.31.so
0xfffff7f70000 0xfffff7f74000 0x4000 0x15a000 /usr/lib/aarch64-linux-gnu/libc-2.31.so
0xfffff7f74000 0xfffff7f76000 0x2000 0x15e000 /usr/lib/aarch64-linux-gnu/libc-2.31.so
0xfffff7f76000 0xfffff7f79000 0x3000 0x0
0xfffff7fba000 0xfffff7fbb000 0x1000 0x0 /root/gdb/libsum.so
0xfffff7fbb000 0xfffff7fca000 0xf000 0x1000 /root/gdb/libsum.so
0xfffff7fca000 0xfffff7fcb000 0x1000 0x0 /root/gdb/libsum.so
0xfffff7fcb000 0xfffff7fcc000 0x1000 0x1000 /root/gdb/libsum.so
0xfffff7fcc000 0xfffff7fed000 0x21000 0x0 /usr/lib/aarch64-linux-gnu/ld-2.31.so
0xfffff7ff7000 0xfffff7ffb000 0x4000 0x0
0xfffff7ffb000 0xfffff7ffc000 0x1000 0x0 [vvar]
0xfffff7ffc000 0xfffff7ffd000 0x1000 0x0 [vdso]
0xfffff7ffd000 0xfffff7ffe000 0x1000 0x21000 /usr/lib/aarch64-linux-gnu/ld-2.31.so
0xfffff7ffe000 0xfffff8000000 0x2000 0x22000 /usr/lib/aarch64-linux-gnu/ld-2.31.so
0xfffffffdf000 0x1000000000000 0x21000 0x0 [stack]
Conclusion
It can be observed that if one is aware of the previous information regarding DWARF debugging information, debugging a dynamic library becomes exceptionally simple.
In practical work experience, it is impractical to add the -g flag to all code in the released binary, which would be a rather foolish action. The normal approach is to conduct preliminary peripheral analysis based on the symptoms of the problem to draw a rough conclusion about which modules the issue may arise from. Since different modules correspond to different so implementations, it is sufficient to load symbols for the suspected so files, allowing for confident debugging of the so. This technique is simple yet very useful for debugging large projects.