用gdb调试程序时,一般的函数都可以step进去,可是C库函数却直接跳过了。
网上找了些资料,记录一下!
1.安装C库的debug版本
[plain] view plain copy PRint?sudo apt-get install libc6-dbg安装完后,在/usr/lib目录下会多出一个debug目录,里面有安装的debug版c库的动态链接文件
2.编译程序,使用debug版本C库
例如程序test.c,使用如下命令编译。
[html] view%20plain copy print?gcc -g -Wall test.c -o test -Wl,-rpath=/usr/lib/debug可以使用ldd%20test来查看是否使用了debug版c库。我们可以比较前后的信息
使用debug版C库输出的信息:
[plain] view%20plain copy print?linux-gate.so.1 => (0x00b65000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00c94000) /lib/ld-linux.so.2 (0x00872000) 未使用debug版C库输出的信息:[plain] view%20plain copy print?linux-gate.so.1 => (0x00fa9000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x001af000) /lib/ld-linux.so.2 (0x0054a000)可以看出成功了!
3.调试
[plain] view%20plain copy print?gdb test
进入gdb后在相应位置下断点,运行到该位置后,使用s,发现能进入c库,但是找不到c库源码,呵呵
原来还要下载对应版本的c库源码。如何查看c库版本呢?%20使用如下命令:
[plain] view%20plain copy print?ll /usr/lib/debug/i386-linux-gnu/libc*知道了对应的版本后,去glibc官网去下载吧:http://ftp.gnu.org/gnu/glibc/
有了源码,在gdb中用directory命令指定对应文件所在目录,调试时即可看到源码。
参考链接: http://blog.csdn.net/summerhust/article/details/5966751
时间:%202015-11-12%2010:38:00
最近在研究动态链接原理这块,想通过GDB跟踪动态链接器(ld-Linux.so.2)是如何工作的,发现Ubuntu提供的/usr/lib/debug并不能很好的工作,跟踪进去后,发现源码不是对不上,就是错误的,所以萌发了自己编译C库的想法,以下是我的操作记录,欢迎指正。
开始前,需要确保你的磁盘剩余空间不小于3G空间,你不会想到编译调试版本的C库需要这么大的磁盘空间。
首先下载源码,我的系统是Ubuntu%2015.10,使用sudo%20apt-get%20source%20libc6-dbg下载的C库版本是glibc-2.21。我的源码目录是~/libc-dbg/glibc-2.2.1。
在INSTALL编译安装说明中说,C库不能在源码目录安装,所以我在home目录下新建立了一个目录用于编译C库,目录为~/libc。又建立了一个~/lib目录用于最后的C库安装目录。
好,进入~/libc,输入../libc-dbg/glibc-2.21/configure%20--prefix=/home/astrol/lib%20CFLAGS="-O1%20-g3%20-ggdb"%20CXXFLAGS="-O1%20-g3%20-ggdb"%20--disable-werror
注意,我为了调试,所以加了-g3%20-ggdb调试选项,-Ox是必须得,因为C库必须要指定,还有最后的--disable-werror也是必须得,否则会将编译过程中的很多警告信息归为错误,那么就没法继续编译了。这里我只是根据我自身的要求加的几个选项,你也可以根据自己的需求自行添加,参考../libc-dbg/glibc-2.21/configure%20--help的提示帮助。
根据上面命令的结果提示,看能否通过,如果不行就尽量想办法满足它,比如在configure过程中提示我系统需要gawk,那么我就sudo%20apt-get%20install%20gawk来满足它就OK了。
到了这里,就开始编译吧,键入make,接下来就等吧,要很久的。
最后make%20install,就将编译好的库安装到我指定的~/lib中。
进入~/lib,哬,文件还真多,咦,怎么没有生成的库呢,仔细一看,原来所有的库都在子目录lib下:
这些都是带有符号信息的动态库。 好了,我们写个hello world看如何使用它们。
gcc -g -o hello hello.c
然后ldd hello,输出如下
linux-gate.so.1 => (0xb7732000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7563000) /lib/ld-linux.so.2 (0x80080000)
看来这样编译不行,根本没用上我编译好的哪些库,改变编译参数 gcc -Wl,-rpath,/home/astrol/lib/lib -Wl,--dynamic-linker,/home/astrol/lib/lib/ld-linux.so.2 -g -o hello hello.c
或者gcc -Wl,-rpath=/home/astrol/lib/lib -Wl,--dynamic-linker=/home/astrol/lib/lib/ld-linux.so.2 -g -o hello hello.c,其实都是一样的。
再ldd hello,输出如下:
linux-gate.so.1 => (0xb7732000) libc.so.6 => /home/astrol/lib/lib/libc.so.6 (0xb758a000) /home/astrol/lib/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x8001d000)
看来OK了,我们再使用readelf确认下,使用readelf --program-headers hello输出:
Elf file type is EXEC (Executable file)Entry point 0x8048340There are 9 program headers, starting at offset 52Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4 INTERP 0x000154 0x08048154 0x08048154 0x00023 0x00023 R 0x1 [Requesting program interpreter: /home/astrol/lib/lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x005f4 0x005f4 R E 0x1000 LOAD 0x000f00 0x08049f00 0x08049f00 0x00120 0x00124 RW 0x1000
看来都可以了。
现在使用gdb调试我们的hello。gdb hello -q进入调试。使用set verbose on打开gdb信息打印,可以更好的看到调试信息。
astrol@astrol:~/test$ gdb hello -qReading symbols from hello...done.(gdb) set verbose on(gdb) startTemporary breakpoint 1 at 0x804843c: file hello.c, line 5.Starting program: /home/astrol/test/helloReading symbols from /home/astrol/lib/lib/ld-linux.so.2...done.Reading symbols from system-supplied DSO at 0xb7fdd000...(no debugging symbols found)...done.Reading in symbols for dl-debug.c...done.Reading in symbols for rtld.c...done.Reading symbols from /home/astrol/lib/lib/libc.so.6...done.Temporary breakpoint 1, main () at hello.c:55 printf("hello world/n");(gdb)
gdb成功加载了两个库和它们的符号信息。那么接下来的调试就能很好的继续了。这里我演示下printf的工作过程,观察下PLT的大致工作过程。
(gdb) disassemble /mDump of assembler code for function main:4 { 0x0804842b <+0>: lea 0x4(%esp),%ecx 0x0804842f <+4>: and $0xfffffff0,%esp 0x08048432 <+7>: pushl -0x4(%ecx) 0x08048435 <+10>: push %ebp 0x08048436 <+11>: mov %esp,%ebp 0x08048438 <+13>: push %ecx 0x08048439 <+14>: sub $0x4,%esp5 printf("hello world/n");=> 0x0804843c <+17>: sub $0xc,%esp 0x0804843f <+20>: push $0x80484e0 0x08048444 <+25>: call 0x8048300 <puts@plt> 0x08048449 <+30>: add $0x10,%esp6 return 0; 0x0804844c <+33>: mov $0x0,%eax7 } 0x08048451 <+38>: mov -0x4(%ebp),%ecx 0x08048454 <+41>: leave 0x08048455 <+42>: lea -0x4(%ecx),%esp 0x08048458 <+45>: retEnd of assembler dump.
地址0x8048300就是puts的PLT入口处。跟踪进去
(gdb) disassemble /m 0x8048300Dump of assembler code for function puts@plt: 0x08048300 <+0>: jmp *0x804a00c 0x08048306 <+6>: push $0x0 0x0804830b <+11>: jmp 0x80482f0End of assembler dump.
继续跟进,最后jmp到0x80482f0,可以通过x命令看到0x80482f0处的指令如下:
(gdb) x/3i $eip=> 0x80482f0: pushl 0x804a004 0x80482f6: jmp *0x804a008 0x80482fc: add %al,(%eax)
继续jmp到*0x804a008,这就是_dl_runtime_resolve函数的地址,它是最终进入_dl_fixup函数的“跳板”。继续跟进,看最后进入_dl_fixup函数后效果如何。
最终进入_dl_fixup函数后,发现是很正常的,gdb能很好的进行源码级调试,不会出现Ubuntu提供的/usr/lib/debug出现的哪些情况了,即行号和源码是一一对应的。
好了,本文就到此结束吧。
新闻热点
疑难解答