写在前面
在以前的一篇《使用GDB调试C库》中提到过调试C库的问题,一开始的办法是使用ubuntu提供的libc6-dbg来调试,后来觉得这个办法并不完美,所以文章后续给出了使用源码编译glibc的办法,觉得还不够详细,因此这篇文章重新来叙述这个过程,力争详细并且简单明了。
注意事项:
(1)确保系统剩余磁盘不小于3个G,你不会想到编译调试版本的C库需要这么大的磁盘空间。
(2)确保很多工具已经安装,例如安装过程中提示我需要gawk,则sudo apt-get install gawk安装即可。
第一步:下载源码并解压
[email protected]:~$ wget -c http://ftp.gnu.org/gnu/glibc/glibc-2.23.tar.gz
[email protected]:~$ tar zxf glibc-2.23.tar.gz
第二步:安装准备工作以及configure
在目录glibc-2.23下的INSTALL安装说明中提到,C库不能在源码目录下直接编译安装,所以我在home目录下新建立了一个目录用于编译C库,目录为~/libc。又建立了一个~/lib目录用于最后的C库安装目录。
[email protected]:~$ mkdir libc lib
[email protected]:~$ cd libc
[email protected]:~/libc$ ../glibc-2.23/configure --prefix=/home/astrol/lib CFLAGS="-O1 -g3 -ggdb" CXXFLAGS="-O1 -g3 -ggdb" --disable-werror
注意,我为了调试,所以加了-g3 -ggdb调试选项,-Ox是必须得,因为C库必须要指定,否则会报libc/config.h:4:3: error: #error "glibc cannot be compiled without optimization"错误(具体原因可以参考这里)。还有最后的--disable-werror也是必须的,否则会将编译过程中的很多警告信息归为错误,那么就没法继续编译了。这里我只是根据我自身的要求加的几个选项,你也可以根据自己的需求自行添加,参考../glibc-2.23/configure --help的提示帮助。
补充时间:2019-6-11 21:19:05
很据以上给出的链接,我们知道GLIBC不可避免使用优化的原因是动态链接器在自重定位之前是不能调用函数的,必须inline所有的函数调用。因此我们能不能只在动态链接器代码这里优化, 其它code不需要优化呢? 很抱歉,GLIBC的Makefile太过复杂,我没有找到修改的方法。
可能很多人会问,我不关心动态链接器优不优化可以了吧? 我们可以试试。
找到提示error: #error "glibc cannot be compiled without optimization"错误的地方,将其注释掉。然后使用../glibc-2.23/configure --prefix=/home/astrol/lib CFLAGS="-g3 -ggdb" CXXFLAGS="-g3 -ggdb" --disable-werror && make去编译。
很可惜,如果这样做,编译过程中会提示很多未定义的符号,这是为什么呢? 这是因为有些函数必须要inline调用,否则链接时因为缺少符号信息而出错。
这里我给出一个比较笨拙但是却是有效的办法。
(1)修改之前的编译命令为
[email protected]:~/libc$ make -j24 2>&1 | tee build_glibc.log
主要是保存编译过程中的所有输出信息到文件build_glibc.log中。
(2)找到你想调试的那个文件,这里我们已dlopen.c这个文件为例。在build_glibc.log中我们搜索dlopen.c,我们可以找到两处。
第一处:
gcc dlopen.c -c -std=gnu11 -fgnu89-inline -O1 -Wall -Wundef -Wwrite-strings -fmerge-all-constants -fno-stack-protector -frounding-math -g3 -ggdb -Wstrict-prototypes -Wold-style-definition -U_FORTIFY_SOURCE -I../include -I/home/astrol/libc/libc/dlfcn -I/home/astrol/libc/libc -I../sysdeps/unix/sysv/linux/x86_64/64 -I../sysdeps/unix/sysv/linux/x86_64 -I../sysdeps/unix/sysv/linux/x86 -I../sysdeps/x86/nptl -I../sysdeps/unix/sysv/linux/wordsize-64 -I../sysdeps/x86_64/nptl -I../sysdeps/unix/sysv/linux/include -I../sysdeps/unix/sysv/linux -I../sysdeps/nptl -I../sysdeps/pthread -I../sysdeps/gnu -I../sysdeps/unix/inet -I../sysdeps/unix/sysv -I../sysdeps/unix/x86_64 -I../sysdeps/unix -I../sysdeps/posix -I../sysdeps/x86_64/64 -I../sysdeps/x86_64/fpu/multiarch -I../sysdeps/x86_64/fpu -I../sysdeps/x86/fpu/include -I../sysdeps/x86/fpu -I../sysdeps/x86_64/multiarch -I../sysdeps/x86_64 -I../sysdeps/x86 -I../sysdeps/ieee754/float128 -I../sysdeps/ieee754/ldbl-96/include -I../sysdeps/ieee754/ldbl-96 -I../sysdeps/ieee754/dbl-64/wordsize-64 -I../sysdeps/ieee754/dbl-64 -I../sysdeps/ieee754/flt-32 -I../sysdeps/wordsize-64 -I../sysdeps/ieee754 -I../sysdeps/generic -I.. -I../libio -I. -D_LIBC_REENTRANT -include /home/astrol/libc/libc/libc-modules.h -DMODULE_NAME=libdl -include ../include/libc-symbols.h -DPIC -DTOP_NAMESPACE=glibc -o /home/astrol/libc/libc/dlfcn/dlopen.o -MD -MP -MF /home/astrol/libc/libc/dlfcn/dlopen.o.dt -MT /home/astrol/libc/libc/dlfcn/dlopen.o
第二处:
gcc dlopen.c -c -std=gnu11 -fgnu89-inline -O1 -Wall -Wundef -Wwrite-strings -fmerge-all-constants -fno-stack-protector -frounding-math -g3 -ggdb -Wstrict-prototypes -Wold-style-definition -fPIC -U_FORTIFY_SOURCE -I../include -I/home/astrol/libc/libc/dlfcn -I/home/astrol/libc/libc -I../sysdeps/unix/sysv/linux/x86_64/64 -I../sysdeps/unix/sysv/linux/x86_64 -I../sysdeps/unix/sysv/linux/x86 -I../sysdeps/x86/nptl -I../sysdeps/unix/sysv/linux/wordsize-64 -I../sysdeps/x86_64/nptl -I../sysdeps/unix/sysv/linux/include -I../sysdeps/unix/sysv/linux -I../sysdeps/nptl -I../sysdeps/pthread -I../sysdeps/gnu -I../sysdeps/unix/inet -I../sysdeps/unix/sysv -I../sysdeps/unix/x86_64 -I../sysdeps/unix -I../sysdeps/posix -I../sysdeps/x86_64/64 -I../sysdeps/x86_64/fpu/multiarch -I../sysdeps/x86_64/fpu -I../sysdeps/x86/fpu/include -I../sysdeps/x86/fpu -I../sysdeps/x86_64/multiarch -I../sysdeps/x86_64 -I../sysdeps/x86 -I../sysdeps/ieee754/float128 -I../sysdeps/ieee754/ldbl-96/include -I../sysdeps/ieee754/ldbl-96 -I../sysdeps/ieee754/dbl-64/wordsize-64 -I../sysdeps/ieee754/dbl-64 -I../sysdeps/ieee754/flt-32 -I../sysdeps/wordsize-64 -I../sysdeps/ieee754 -I../sysdeps/generic -I.. -I../libio -I. -D_LIBC_REENTRANT -include /home/astrol/libc/libc/libc-modules.h -DMODULE_NAME=libdl -include ../include/libc-symbols.h -DPIC -DSHARED -DTOP_NAMESPACE=glibc -o /home/astrol/libc/libc/dlfcn/dlopen.os -MD -MP -MF /home/astrol/libc/libc/dlfcn/dlopen.os.dt -MT /home/astrol/libc/libc/dlfcn/dlopen.os
仔细对比,我们发现生成dlopen.os时比生成dlopen.o时多一个-fPIC参数。这就解释了他们的不同:dlopen.o用于生成静态库,dlopen.os用于生成动态库。 我们关注动态库。
首先注释掉#error "glibc cannot be compiled without optimization"(include/libc-symbols.h),然后使用如下命令编译:
gcc dlopen.c -c -std=gnu11 -fgnu89-inline -Wall -Wundef -Wwrite-strings -fmerge-all-constants -fno-stack-protector -frounding-math -g3 -ggdb -Wstrict-prototypes -Wold-style-definition -U_FORTIFY_SOURCE -I../include -I/home/astrol/libc/libc/dlfcn -I/home/astrol/libc/libc -I../sysdeps/unix/sysv/linux/x86_64/64 -I../sysdeps/unix/sysv/linux/x86_64 -I../sysdeps/unix/sysv/linux/x86 -I../sysdeps/x86/nptl -I../sysdeps/unix/sysv/linux/wordsize-64 -I../sysdeps/x86_64/nptl -I../sysdeps/unix/sysv/linux/include -I../sysdeps/unix/sysv/linux -I../sysdeps/nptl -I../sysdeps/pthread -I../sysdeps/gnu -I../sysdeps/unix/inet -I../sysdeps/unix/sysv -I../sysdeps/unix/x86_64 -I../sysdeps/unix -I../sysdeps/posix -I../sysdeps/x86_64/64 -I../sysdeps/x86_64/fpu/multiarch -I../sysdeps/x86_64/fpu -I../sysdeps/x86/fpu/include -I../sysdeps/x86/fpu -I../sysdeps/x86_64/multiarch -I../sysdeps/x86_64 -I../sysdeps/x86 -I../sysdeps/ieee754/float128 -I../sysdeps/ieee754/ldbl-96/include -I../sysdeps/ieee754/ldbl-96 -I../sysdeps/ieee754/dbl-64/wordsize-64 -I../sysdeps/ieee754/dbl-64 -I../sysdeps/ieee754/flt-32 -I../sysdeps/wordsize-64 -I../sysdeps/ieee754 -I../sysdeps/generic -I.. -I../libio -I. -D_LIBC_REENTRANT -include /home/astrol/libc/libc/libc-modules.h -DMODULE_NAME=libdl -include ../include/libc-symbols.h -DPIC -DSHARED -DTOP_NAMESPACE=glibc -o /home/astrol/libc/libc/dlfcn/dlopen.os -MD -MP -MF /home/astrol/libc/libc/dlfcn/dlopen.os.dt -MT /home/astrol/libc/libc/dlfcn/dlopen.os
这条命令唯一修改的地方就是去除-O1,如果可以,建议大家增加两个-fno-schedule-insns -fno-schedule-insns2,这是有关去除指令顺序优化的编译选项。
如此编译,生成的dlopen.os就完全是可调试的了。
最后可以重新make。如果make没有发现dlopen.os发生了改变,那么我们可以直接删除libdl.so后然后make就肯定没有问题了。
第三步:编译源码
[email protected]:~/libc$ make
编译的过程是很漫长的,也是最容易出错的,good luck!!!
第四步:安装编译好的C库
到里这里,恭喜你编译成功过了。
[email protected]:~/libc$ du -sh
3.1G .
看到没,足足有3个多G,可怕!!!
最后make install,就将编译好的库安装到我指定的~/lib中。
[email protected]:~/libc$ make install
进入~/lib,ls,咦,怎么没有生成的库呢,仔细一看,原来所有的库都在子目录lib下,啊,生成的库还真多。
[email protected]:~$ cd lib
[email protected]:~/lib$ ls
bin etc include lib libexec sbin share var
[email protected]:~/lib$ cd lib
[email protected]:~/lib/lib$ ls
audit libBrokenLocale.so.1 libdl.so libnss_compat.so libnss_nisplus-2.23.so librpcsvc.a
crt1.o libc-2.23.so libdl.so.2 libnss_compat.so.2 libnss_nisplus.so librt-2.23.so
crti.o libc.a libg.a libnss_db-2.23.so libnss_nisplus.so.2 librt.a
crtn.o libcidn-2.23.so libieee.a libnss_db.so libnss_nis.so librt.so
gconv libcidn.so libm-2.23.so libnss_db.so.2 libnss_nis.so.2 librt.so.1
gcrt1.o libcidn.so.1 libm.a libnss_dns-2.23.so libpcprofile.so libSegFault.so
ld-2.23.so libc_nonshared.a libmcheck.a libnss_dns.so libpthread-2.23.so libthread_db-1.0.so
ld-linux.so.2 libcrypt-2.23.so libmemusage.so libnss_dns.so.2 libpthread.a libthread_db.so
libanl-2.23.so libcrypt.a libm.so libnss_files-2.23.so libpthread_nonshared.a libthread_db.so.1
libanl.a libcrypt.so libm.so.6 libnss_files.so libpthread.so libutil-2.23.so
libanl.so libcrypt.so.1 libnsl-2.23.so libnss_files.so.2 libpthread.so.0 libutil.a
libanl.so.1 libc.so libnsl.a libnss_hesiod-2.23.so libresolv-2.23.so libutil.so
libBrokenLocale-2.23.so libc.so.6 libnsl.so libnss_hesiod.so libresolv.a libutil.so.1
libBrokenLocale.a libdl-2.23.so libnsl.so.1 libnss_hesiod.so.2 libresolv.so Mcrt1.o
libBrokenLocale.so libdl.a libnss_compat-2.23.so libnss_nis-2.23.so libresolv.so.2 Scrt1.o
第五步:如何使用编译好的C库呢
现在C库的编译和安装都彻底完成了。接下来就是如何使用编译好的C库,并且GDB调试了。
其实,接下来的问题可以这样描述:系统中存在多版本的C库时,如何使我们的应用程序选择使用哪一个C库呢?
我们这里存在两个版本的C库,一个是系统原生的C库,不带调试符号信息,另一个就是我们刚刚编译好的C库了,拥有详细的调试信息。
我们使用经典的hello world程序来做测试。
[email protected]:~$ mkdir libc_test
[email protected]:~$ cd libc_test/
[email protected]:~/libc_test$ vim hello.c
[email protected]:~/libc_test$ gcc -o hello hello.c
[email protected]:~/libc_test$ ./hello &
hello world
测试源码如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
while (1) {
sleep(1);
}
return (0);
}
OK,一切正常。但是生成的hello可执行程序默认使用的C库和动态链接器都是系统原生的版本,怎么验证?如下:
[email protected]:~/libc_test$ ldd hello
linux-gate.so.1 => (0xb77e0000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7611000)
/lib/ld-linux.so.2 (0x800ab000)
[email protected]:~/libc_test$ readelf --program-headers hello
Elf file type is EXEC (Executable file)
Entry point 0x8048310
There are 9 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x005c4 0x005c4 R E 0x1000
LOAD 0x000f08 0x08049f08 0x08049f08 0x00114 0x00118 RW 0x1000
DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4
... ...
可以很清楚看到,当hello程序运行时加载的C库和链接器使用的都是系统原生的,也可以通过proc来查看
[email protected]:~/libc_test$ ps aux | grep hello
astrol 694 0.0 0.1 2200 532 pts/17 S 17:17 0:00 ./hello
astrol 6549 7.0 0.1 6848 764 pts/17 S+ 17:26 0:00 grep --color=auto hello
[email protected]:~/libc_test$ cat /proc/694/maps
08048000-08049000 r-xp 00000000 08:01 17 /home/astrol/libc_test/hello
08049000-0804a000 r--p 00000000 08:01 17 /home/astrol/libc_test/hello
0804a000-0804b000 rw-p 00001000 08:01 17 /home/astrol/libc_test/hello
08ec7000-08ee8000 rw-p 00000000 00:00 0 [heap]
b75fa000-b77a9000 r-xp 00000000 08:01 2622768 /lib/i386-linux-gnu/libc-2.23.so
b77a9000-b77aa000 ---p 001af000 08:01 2622768 /lib/i386-linux-gnu/libc-2.23.so
b77aa000-b77ac000 r--p 001af000 08:01 2622768 /lib/i386-linux-gnu/libc-2.23.so
b77ac000-b77ad000 rw-p 001b1000 08:01 2622768 /lib/i386-linux-gnu/libc-2.23.so
b77ad000-b77b0000 rw-p 00000000 00:00 0
b77c5000-b77c7000 rw-p 00000000 00:00 0
b77c7000-b77c9000 r--p 00000000 00:00 0 [vvar]
b77c9000-b77ca000 r-xp 00000000 00:00 0 [vdso]
b77ca000-b77ec000 r-xp 00000000 08:01 2622740 /lib/i386-linux-gnu/ld-2.23.so
b77ec000-b77ed000 rw-p 00000000 00:00 0
b77ed000-b77ee000 r--p 00022000 08:01 2622740 /lib/i386-linux-gnu/ld-2.23.so
b77ee000-b77ef000 rw-p 00023000 08:01 2622740 /lib/i386-linux-gnu/ld-2.23.so
bfc56000-bfc77000 rw-p 00000000 00:00 0 [stack]
该如何做,才能使生成的hello程序在运行时加载的C库和链接器是我们生成的呢?好,重点来了!!!
根据文章《 Linux运行时动态库搜索路径优先级》我们可以得出,可以通过-Wl,-rpath来加载我们编译出的C库,因为通过-Wl,-rpath指定的路径优先级最高。
[email protected]:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=~/lib/lib
[email protected]:~/libc_test$ readelf --dynamic hello
Dynamic section at offset 0xf0c contains 25 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000f (RPATH) Library rpath: [~/lib/lib]
0x0000000c (INIT) 0x80482d4
... ...
可以看到,~/lib/lib是被写入最终的可执行文件的。我们用环境变量LD_DEBUG来看下具体的搜索C库的顺序:
[email protected]:~/libc_test$ LD_DEBUG=libs ./hello
27568: find library=libc.so.6 [0]; searching
27568: search path=~/lib/lib/tls/i686/sse2/cmov:~/lib/lib/tls/i686/sse2:~/lib/lib/tls/i686/cmov:~/lib/lib/tls/i686:~/lib/lib/tls/sse2/cmov:~/lib/lib/tls/sse2:~/lib/lib/tls/cmov:~/lib/lib/tls:~/lib/lib/i686/sse2/cmov:~/lib/lib/i686/sse2:~/lib/lib/i686/cmov:~/lib/lib/i686:~/lib/lib/sse2/cmov:~/lib/lib/sse2:~/lib/lib/cmov:~/lib/lib (RPATH from file ./hello)
27568: trying file=~/lib/lib/tls/i686/sse2/cmov/libc.so.6
27568: trying file=~/lib/lib/tls/i686/sse2/libc.so.6
27568: trying file=~/lib/lib/tls/i686/cmov/libc.so.6
27568: trying file=~/lib/lib/tls/i686/libc.so.6
27568: trying file=~/lib/lib/tls/sse2/cmov/libc.so.6
27568: trying file=~/lib/lib/tls/sse2/libc.so.6
27568: trying file=~/lib/lib/tls/cmov/libc.so.6
27568: trying file=~/lib/lib/tls/libc.so.6
27568: trying file=~/lib/lib/i686/sse2/cmov/libc.so.6
27568: trying file=~/lib/lib/i686/sse2/libc.so.6
27568: trying file=~/lib/lib/i686/cmov/libc.so.6
27568: trying file=~/lib/lib/i686/libc.so.6
27568: trying file=~/lib/lib/sse2/cmov/libc.so.6
27568: trying file=~/lib/lib/sse2/libc.so.6
27568: trying file=~/lib/lib/cmov/libc.so.6
27568: trying file=~/lib/lib/libc.so.6
27568: search cache=/etc/ld.so.cache
27568: trying file=/lib/i386-linux-gnu/libc.so.6
27568:
27568:
27568: calling init: /lib/i386-linux-gnu/libc.so.6
27568:
27568:
27568: initialize program: ./hello
27568:
27568:
27568: transferring control: ./hello
27568:
可以很清楚的看到,系统先从~/lib/lib中搜索C库,接着是 配置文件/etc/ld.so.conf中指定的动态库搜索路径,最后是系统默认搜索路径。咦,不对,怎么最后还是使用的系统C库呢?
原来通过-Wl传递的参数并不会被扩展(确切的说叫波浪号扩展,那是shell的特性,可以参考《bash之波浪号扩展》),所以还是使用具体的路径吧!!!
[email protected]:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=/home/astrol/lib/lib
[email protected]:~/libc_test$ LD_DEBUG=libs ./hello
27582: find library=libc.so.6 [0]; searching
27582: search path=/home/astrol/lib/lib/tls/i686/sse2/cmov:/home/astrol/lib/lib/tls/i686/sse2:/home/astrol/lib/lib/tls/i686/cmov:/home/astrol/lib/lib/tls/i686:/home/astrol/lib/lib/tls/sse2/cmov:/home/astrol/lib/lib/tls/sse2:/home/astrol/lib/lib/tls/cmov:/home/astrol/lib/lib/tls:/home/astrol/lib/lib/i686/sse2/cmov:/home/astrol/lib/lib/i686/sse2:/home/astrol/lib/lib/i686/cmov:/home/astrol/lib/lib/i686:/home/astrol/lib/lib/sse2/cmov:/home/astrol/lib/lib/sse2:/home/astrol/lib/lib/cmov:/home/astrol/lib/lib (RPATH from file ./hello)
27582: trying file=/home/astrol/lib/lib/tls/i686/sse2/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/i686/sse2/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/i686/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/i686/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/sse2/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/sse2/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/tls/libc.so.6
27582: trying file=/home/astrol/lib/lib/i686/sse2/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/i686/sse2/libc.so.6
27582: trying file=/home/astrol/lib/lib/i686/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/i686/libc.so.6
27582: trying file=/home/astrol/lib/lib/sse2/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/sse2/libc.so.6
27582: trying file=/home/astrol/lib/lib/cmov/libc.so.6
27582: trying file=/home/astrol/lib/lib/libc.so.6
27582:
27582:
27582: calling init: /home/astrol/lib/lib/libc.so.6
27582:
27582:
27582: initialize program: ./hello
27582:
27582:
27582: transferring control: ./hello
终于可以成功加载我自己的C库了! 那么链接器呢,如何做? 简单,通过链接选项-Wl,--dynamic-linker=/home/astrol/lib/lib/ld-2.23.so就可以了!这样做的最终效果就是把程序运行时的动态链接器告诉系统,让它加载调用!
[email protected]:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=/home/astrol/lib/lib -Wl,--dynamic-linker=/home/astrol/lib/lib/ld-2.23.so
[email protected]:~/libc_test$ readelf --program-headers hello
Elf file type is EXEC (Executable file)
Entry point 0x8048360
There are 9 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00020 0x00020 R 0x1
[Requesting program interpreter: /home/astrol/lib/lib/ld-2.23.so]
LOAD 0x000000 0x08048000 0x08048000 0x0060c 0x0060c R E 0x1000
LOAD 0x000f00 0x08049f00 0x08049f00 0x00120 0x00124 RW 0x1000
DYNAMIC 0x000f0c 0x08049f0c 0x08049f0c 0x000f0 0x000f0 RW 0x4
NOTE 0x000174 0x08048174 0x08048174 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x00051c 0x0804851c 0x0804851c 0x0002c 0x0002c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000f00 0x08049f00 0x08049f00 0x00100 0x00100 R 0x1
运行程序,通过maps文件验证下:
[email protected]:~/libc_test$ ./hello &
[1] 27645
[email protected]:~/libc_test$ hello world
[email protected]:~/libc_test$ ps aux | grep hello
astrol 27645 0.2 0.0 2092 156 pts/17 S 18:38 0:00 ./hello
astrol 27647 0.0 0.1 6848 852 pts/17 S+ 18:38 0:00 grep --color=auto hello
[email protected]:~/libc_test$ cat /proc/27645/maps
08048000-08049000 r-xp 00000000 08:01 17 /home/astrol/libc_test/hello
08049000-0804a000 r--p 00000000 08:01 17 /home/astrol/libc_test/hello
0804a000-0804b000 rw-p 00001000 08:01 17 /home/astrol/libc_test/hello
087f3000-08814000 rw-p 00000000 00:00 0 [heap]
b75b0000-b75b1000 rw-p 00000000 00:00 0
b75b1000-b7748000 r-xp 00000000 08:01 1181945 /home/astrol/lib/lib/libc-2.23.so
b7748000-b7749000 ---p 00197000 08:01 1181945 /home/astrol/lib/lib/libc-2.23.so
b7749000-b774b000 r--p 00197000 08:01 1181945 /home/astrol/lib/lib/libc-2.23.so
b774b000-b774c000 rw-p 00199000 08:01 1181945 /home/astrol/lib/lib/libc-2.23.so
b774c000-b7750000 rw-p 00000000 00:00 0
b7750000-b7752000 r--p 00000000 00:00 0 [vvar]
b7752000-b7753000 r-xp 00000000 00:00 0 [vdso]
b7753000-b7773000 r-xp 00000000 08:01 1184346 /home/astrol/lib/lib/ld-2.23.so
b7773000-b7774000 r--p 0001f000 08:01 1184346 /home/astrol/lib/lib/ld-2.23.so
b7774000-b7775000 rw-p 00020000 08:01 1184346 /home/astrol/lib/lib/ld-2.23.so
bfd66000-bfd87000 rw-p 00000000 00:00 0 [stack]
第六步:使用GDB调试
现在使用gdb调试我们的hello。gdb hello -q进入调试。使用set verbose on打开gdb信息打印,可以更好的看到调试信息。
[email protected]:~/libc_test$ gcc -g3 -ggdb -o hello hello.c -Wl,-rpath=/home/astrol/lib/lib -Wl,--dynamic-linker=/home/astrol/lib/lib/ld-2.23.so
[email protected]:~/libc_test$ gdb hello -q
Reading symbols from hello...done.
(gdb) set verbose on
(gdb) start
Temporary breakpoint 1 at 0x804846c: file hello.c, line 6.
Starting program: /home/astrol/libc_test/hello
Reading symbols from /home/astrol/lib/lib/ld-2.23.so...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 (Reading in symbols for ../sysdeps/x86/libc-start.c...done.
argc=1, argv=0xbffff5f4) at hello.c:6
6 printf("hello world\n");
(gdb)
gdb成功加载了两个库和它们的符号信息。那么接下来的调试就能很好的继续了。这里我演示下printf的调用过程,观察下PLT的大致工作过程。
(gdb) disassemble /m
Dump of assembler code for function main:
5 {
0x0804845b <+0>: lea 0x4(%esp),%ecx
0x0804845f <+4>: and $0xfffffff0,%esp
0x08048462 <+7>: pushl -0x4(%ecx)
0x08048465 <+10>: push %ebp
0x08048466 <+11>: mov %esp,%ebp
0x08048468 <+13>: push %ecx
0x08048469 <+14>: sub $0x4,%esp
6 printf("hello world\n");
=> 0x0804846c <+17>: sub $0xc,%esp
0x0804846f <+20>: push $0x8048510
0x08048474 <+25>: call 0x8048330 <[email protected]>
0x08048479 <+30>: add $0x10,%esp
7
8 while (1) {
9 sleep(1);
0x0804847c <+33>: sub $0xc,%esp
0x0804847f <+36>: push $0x1
0x08048481 <+38>: call 0x8048320 <[email protected]>
0x08048486 <+43>: add $0x10,%esp
10 }
0x08048489 <+46>: jmp 0x804847c <main+33>
End of assembler dump.
(gdb)
地址0x8048330就是puts的PLT入口处。stepi跟踪进去!
(gdb) stepi
0x08048330 in [email protected] ()
(gdb) disassemble /m
Dump of assembler code for function [email protected]:
=> 0x08048330 <+0>: jmp *0x804a010
0x08048336 <+6>: push $0x8
0x0804833b <+11>: jmp 0x8048310
End of assembler dump.
(gdb)
继续跟进,最后jmp到0x8048310,可以通过x命令看到0x8048310处的指令如下:
(gdb) x/3i 0x8048310
0x8048310: pushl 0x804a004
0x8048316: jmp *0x804a008
0x804831c: add %al,(%eax)
继续jmp到*0x804a008,这就是_dl_runtime_resolve函数的地址,它是最终进入_dl_fixup函数的“跳板”。继续跟进,看最后进入_dl_fixup函数后效果如何。
最终进入_dl_fixup函数后,发现是很正常的,gdb能很好的进行源码级调试,不会出现Ubuntu提供的/usr/lib/debug出现的哪些情况了,即行号和源码是一一对应的。
本文结束![2016-9-25 19:10:58]
参考链接:
《Multiple glibc libraries on a single host》
《如何使用新的glibc来编译自己的程序》