在Linux上dynamic链接到libc时调用`atexit`

如果我有下面用C编写的程序(用Debian 8.7上的GCC编译),我可以像预期的那样调用atexit()

 #include <stdlib.h> void exit_handler(void) { return; } int main () { atexit(exit_handler); return 0; } 

当我编译和运行它时:

 $ gcc test.c $ ./a.out 

输出什么,就像你所期望的一样。 实际上,当我运行ldd ,我得到:

 $ ldd a.out linux-vdso.so.1 (0x00007fffbe592000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe07d3a8000) /lib64/ld-linux-x86-64.so.2 (0x00007fe07d753000) 

但是, libc似乎没有任何符号为atexit ,amd只有__cxa_atexit__cxa_threaded_atexit_impl

 $ nm --dynamic /lib/x86_64-linux-gnu/libc.so.6 | grep 'atexit' 0000000000037d90 T __cxa_atexit 0000000000037fa0 T __cxa_thread_atexit_impl 

正如你期望的那样,如果我尝试dynamic链接到libc ,我实际上不能调用atexit() ,例如在以下连接到libc Racket程序中,并尝试查找atexit

 #lang racket (require ffi/unsafe) (get-ffi-obj 'atexit (ffi-lib "libc" '("6")) (_fun (_fun -> _void) -> _int)) 

给出输出:

 $ racket findatexit.rkt ffi-obj: couldn't get "atexit" from "libc.so.6" (/lib/x86_64-linux-gnu/libc.so.6: undefined symbol: atexit) 

我想知道的是:

  1. 如果libc在Linux上没有用于atexit符号,为什么我仍然可以从C程序中调用它?
  2. 有什么办法可以在Linux上dynamic调用atexit或类似的函数吗?

(我应该注意到, atexit似乎是OS X上的一个标志,所以它在这里看起来很不寻常。

编辑:

在@Jonathan的build议下,我也跑了:

 $ gcc -c test.c $ nm test.o U atexit 0000000000000000 T exit_handler 0000000000000007 T main 

这似乎表明, atexit符号是在那里,但它并没有出现在ldd显示的任何库。

我在Centos 7虚拟机上做了一些尝试,我觉得我发现了它 – 但它是非常明显的!

找到了!

/usr/lib64/libc_nonshared.a

 $ nm /usr/lib64/libc_nonshared.a | grep -i atexit atexit.oS: 0000000000000000 T atexit U __cxa_atexit $ 

为什么要看图书馆? 好问题 – 还有一个很长的故事。 你坐着舒服吗? 那我就开始吧

采取步骤去那里

  1. 使用问题中的test.c代码。
  2. 使用gcc -v test.c编译它:

     $ gcc -v test.c Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux Thread model: posix gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/cc1 -quiet -v test.c -quiet -dumpbase test.c -mtune=generic -march=x86-64 -auxbase test -version -o /tmp/ccPHTer7.s GNU C (GCC) version 4.8.5 20150623 (Red Hat 4.8.5-11) (x86_64-redhat-linux) compiled by GNU C version 4.8.5 20150623 (Red Hat 4.8.5-11), GMP version 6.0.0, MPFR version 3.1.1, MPC version 1.0.1 GGC heuristics: --param ggc-min-expand=96 --param ggc-min-heapsize=124992 ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include-fixed" ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/include" #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include /usr/local/include /usr/include End of search list. GNU C (GCC) version 4.8.5 20150623 (Red Hat 4.8.5-11) (x86_64-redhat-linux) compiled by GNU C version 4.8.5 20150623 (Red Hat 4.8.5-11), GMP version 6.0.0, MPFR version 3.1.1, MPC version 1.0.1 GGC heuristics: --param ggc-min-expand=96 --param ggc-min-heapsize=124992 Compiler executable checksum: 356f86e67978d665416e07d560c8ba0d COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' as -v --64 -o /tmp/cc5WHEA4.o /tmp/ccPHTer7.s GNU assembler version 2.25.1 (x86_64-redhat-linux) using BFD version version 2.25.1-22.base.el7 COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/ LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/cc5WHEA4.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o $ 
  3. 有趣的部分是最后的collect2命令行。 每行写一个参数,即:

     /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/cc5WHEA4.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o 
  4. 所以,有一堆cr*.o文件,再加上三个库: -lc-lgcc-lgcc_s以查找以及一堆目录: -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64-L/lib/../lib64-L/usr/lib/../lib64-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/cc5WHEA4.o是从test.c创建的目标文件。

  5. 将一些清理代码应用到路径名称,然后使用ls来帮助查找这些库生成一个文件列表,以便进一步检查:

     /lib64/ld-linux-x86-64.so.2 /usr/lib64/crt1.o /usr/lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib64/crtn.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcc.a /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcc_s.so /usr/lib64/libgcc_s.so.1 /lib64/libgcc_s.so.1 /usr/lib64/libgcc_s.so.1 /usr/lib64/libc.so /usr/lib64/libc.so.6 /lib64/libc.so /lib64/libc.so.6 /usr/lib64/libc.so /usr/lib64/libc.so.6 
  6. 该文件列表被保存在文件yy (unimaginative name)中,然后用于:

     $ nm -o $(<yy) | tee nm.log | grep -i atexit nm: _trampoline.o: no symbols nm: __main.o: no symbols nm: _ctors.o: no symbols nm: /usr/lib/gcc/x86_64-redhat-linux/4.8.5/libgcc_s.so: no symbols nm: /usr/lib64/libgcc_s.so.1: no symbols nm: /lib64/libgcc_s.so.1: no symbols nm: /usr/lib64/libgcc_s.so.1: no symbols nm: /usr/lib64/libc.so: File format not recognized /usr/lib64/libc.so.6:00000000003bcc00 b added_atexit_handler.9157 /usr/lib64/libc.so.6:0000000000038c90 T __cxa_atexit /usr/lib64/libc.so.6:0000000000038c90 t __cxa_atexit_internal /usr/lib64/libc.so.6:00000000003b6838 d __elf_set___libc_atexit_element__IO_cleanup__ /usr/lib64/libc.so.6:0000000000038c40 t __internal_atexit /usr/lib64/libc.so.6:00000000003b6838 d __start___libc_atexit /usr/lib64/libc.so.6:00000000003b6840 d __stop___libc_atexit nm: /lib64/libc.so: File format not recognized /lib64/libc.so.6:00000000003bcc00 b added_atexit_handler.9157 /lib64/libc.so.6:0000000000038c90 T __cxa_atexit /lib64/libc.so.6:0000000000038c90 t __cxa_atexit_internal /lib64/libc.so.6:00000000003b6838 d __elf_set___libc_atexit_element__IO_cleanup__ /lib64/libc.so.6:0000000000038c40 t __internal_atexit nm: /usr/lib64/libc.so: File format not recognized /lib64/libc.so.6:00000000003b6838 d __start___libc_atexit /lib64/libc.so.6:00000000003b6840 d __stop___libc_atexit /usr/lib64/libc.so.6:00000000003bcc00 b added_atexit_handler.9157 /usr/lib64/libc.so.6:0000000000038c90 T __cxa_atexit /usr/lib64/libc.so.6:0000000000038c90 t __cxa_atexit_internal /usr/lib64/libc.so.6:00000000003b6838 d __elf_set___libc_atexit_element__IO_cleanup__ /usr/lib64/libc.so.6:0000000000038c40 t __internal_atexit /usr/lib64/libc.so.6:00000000003b6838 d __start___libc_atexit /usr/lib64/libc.so.6:00000000003b6840 d __stop___libc_atexit $ 
  7. 没有证据表明在那里有一个简单的atexit功能。 它隐藏的地方,那些'文件格式不被识别'的信息是什么?

     $ file /usr/lib64/libc.so /usr/lib64/libc.so: ASCII text $ 
  8. ASCII文本? 什么?

     $ cat /usr/lib64/libc.so /* GNU ld script Use the shared library, but some functions are only in the static library, so try that secondarily. */ OUTPUT_FORMAT(elf64-x86-64) GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) ) $ 
  9. 好; /usr/lib64/libc_nonshared.a什么?

     $ nm /usr/lib64/libc_nonshared.a | grep -i atexit atexit.oS: 0000000000000000 T atexit U __cxa_atexit $ 

    答对了! 找到了!

所以,GCC使用的collect2连接器似乎可以加载没有列在命令行中的文件,其中一个文件是/usr/lib64/libc_nonshared.a ,而且这个库里有atexit() 。 因此,你应该能够调用atexit()因为它被静态链接到可执行文件中,除非在这里隐藏了更多的黑魔法,我还没有意识到。