当前位置 博文首页 > astrotycoon:源码级调试glibc

    astrotycoon:源码级调试glibc

    作者:[db:作者] 时间:2021-08-17 15:42

    写在前面

    在以前的一篇《使用GDB调试C库》中提到过调试C库的问题,一开始的办法是使用ubuntu提供的libc6-dbg来调试,后来觉得这个办法并不完美,所以文章后续给出了使用源码编译glibc的办法,觉得还不够详细,因此这篇文章重新来叙述这个过程,力争详细并且简单明了。

    注意事项:

    (1)确保系统剩余磁盘不小于3个G,你不会想到编译调试版本的C库需要这么大的磁盘空间。

    (2)确保很多工具已经安装,例如安装过程中提示我需要gawk,则sudo apt-get install gawk安装即可。


    第一步:下载源码并解压

    astrol@astrol:~$ wget -c http://ftp.gnu.org/gnu/glibc/glibc-2.23.tar.gz

    astrol@astrol:~$ tar zxf glibc-2.23.tar.gz


    第二步:安装准备工作以及configure

    在目录glibc-2.23下的INSTALL安装说明中提到,C库不能在源码目录下直接编译安装,所以我在home目录下新建立了一个目录用于编译C库,目录为~/libc。又建立了一个~/lib目录用于最后的C库安装目录。

    astrol@astrol:~$ mkdir libc lib

    astrol@astrol:~$ cd libc

    astrol@astrol:~/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)修改之前的编译命令为

    astrol@astrol:~/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就肯定没有问题了。


    第三步:编译源码

    astrol@astrol:~/libc$ make

    编译的过程是很漫长的,也是最容易出错的,good luck!!!


    第四步:安装编译好的C库

    到里这里,恭喜你编译成功过了。

    astrol@astrol:~/libc$ du -sh
    3.1G ? ?.

    看到没,足足有3个多G,可怕!!!

    最后make install,就将编译好的库安装到我指定的~/lib中。

    astrol@astrol:~/libc$ make install

    进入~/lib,ls,咦,怎么没有生成的库呢,仔细一看,原来所有的库都在子目录lib下,啊,生成的库还真多。

    astrol@astrol:~$ cd lib
    astrol@astrol:~/lib$ ls
    bin ?etc ?include ?lib ?libexec ?sbin ?share ?var
    astrol@astrol:~/lib$ cd lib
    astrol@astrol:~/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程序来做测试。

    astrol@astrol:~$ mkdir libc_test

    astrol@astrol:~$ cd libc_test/

    astrol@astrol:~/libc_test$ vim hello.c

    astrol@astrol:~/libc_test$ gcc -o hello hello.c

    astrol@astrol:~/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库和动态链接器都是系统原生的版本,怎么验证?如下:

    astrol@astrol:~/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)

    astrol@astrol:~/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来查看

    astrol@astrol:~/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
    astrol@astrol:~/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指定的路径优先级最高。

    astrol@astrol:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=~/lib/lib

    astrol@astrol:~/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库的顺序:

    astrol@astrol:~/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之波浪号扩展》),所以还是使用具体的路径吧!!!

    astrol@astrol:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=/home/astrol/lib/lib
    astrol@astrol:~/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就可以了!这样做的最终效果就是把程序运行时的动态链接器告诉系统,让它加载调用!

    astrol@astrol:~/libc_test$ gcc -o hello hello.c -Wl,-rpath=/home/astrol/lib/lib -Wl,--dynamic-linker=/home/astrol/lib/lib/ld-2.23.so
    astrol@astrol:~/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文件验证下:

    astrol@astrol:~/libc_test$ ./hello &
    [1] 27645
    astrol@astrol:~/libc_test$ hello world


    astrol@astrol:~/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
    astrol@astrol:~/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信息打印,可以更好的看到调试信息。

    astrol@astrol:~/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
    astrol@astrol:~/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 <puts@plt>
    ? ?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 <sleep@plt>
    ? ?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 puts@plt ()
    (gdb) disassemble /m
    Dump of assembler code for function puts@plt:
    => 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来编译自己的程序》

    cs
    下一篇:没有了