一般来说一个动态链接库是不可以直接执行的,但是也有一些例外,比如linux下libpthread,以此为例看一下动态链接库是如何做到可执行的。 至于这有什么用处,可能也就是方便输出一些版本信息编译信息吧。
centos8.5下2.28版本libpthread 1 2 3 4 5 6 7 8 9 10 [huyu@centos852111 2]$ ll /usr/lib64/libpthread.* lrwxrwxrwx. 1 root root 27 Aug 25 2021 /usr/lib64/libpthread.so -> ../../lib64/libpthread.so.0 lrwxrwxrwx. 1 root root 18 Aug 25 2021 /usr/lib64/libpthread.so.0 -> libpthread-2.28.so [huyu@centos852111 2]$ /usr/lib64/libpthread.so Native POSIX Threads Library Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Forced unwind support included.
动态链接库例子 先做一个普通的使用动态链接库的例子
libtest.c 1 2 3 4 5 6 7 #include <stdio.h> #include <stdlib.h> extern int lib_func (const char *p) { printf ("in libfun, param %s\n" , p); return 2 ; }
main.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h> #include <dlfcn.h> // 动态加载库相关头文件 int main () { void *handle; int (*lib_func)(const char *); int n; handle = dlopen("./libtest.so" , RTLD_NOW); if (!handle) { fprintf (stderr , "加载动态库失败: %s\n" , dlerror()); return 1 ; } dlerror(); lib_func = dlsym(handle, "lib_func" ); if (lib_func == NULL ) { fprintf (stderr , "查找符号失败: %s\n" , dlerror()); dlclose(handle); return 1 ; } printf ("准备调用动态库函数...\n" ); n = lib_func("main" ); printf ("lib_func return %d\n" , n); dlclose(handle); return 0 ; }
Makefile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 .PHONY : allall: libtest.so main libtest.so: libtest.o gcc -Wall -g -shared $^ -o $@ main: main.o gcc -Wall -g -ldl $^ -o $@ %.o: %.c gcc -Wall -g -fPIC $^ -c -o $@ clean: $(RM) libtest.so main *.o
执行一下,没有问题 1 2 3 4 5 6 7 8 9 10 [huyu@centos852111 2]$ make gcc -Wall -g -fPIC libtest.c -c -o libtest.o gcc -Wall -g -shared libtest.o -o libtest.so gcc -Wall -g -fPIC main.c -c -o main.o gcc -Wall -g -ldl main.o -o main [huyu@centos852111 2]$ ./main 准备调用动态库函数... in libfun, param main lib_func return 2 [huyu@centos852111 2]$
libpthread中做了什么 找一个libpthread 2.28版本源码,比如https://github.com/bminor/glibc/tree/glibc-2.28/nptl
找到字符串”Native POSIX Threads Library”https://github.com/bminor/glibc/blob/glibc-2.28/nptl/version.c
nptl/version.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <unistd.h> #include <sysdep.h> static const char banner[] ="Native POSIX Threads Library\n\ Copyright (C) 2018 Free Software Foundation, Inc.\n\ This is free software; see the source for copying conditions.\n\ There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n\ PARTICULAR PURPOSE.\n" "Forced unwind support included.\n" ; __attribute__ ((noreturn)) void __nptl_main (void ) { __libc_write (STDOUT_FILENO, banner, sizeof banner - 1 ); _exit (0 ); }
可以看到__nptl_main
函数将banner字符串输出到标准输出,这个函数名字看起来就是找对了。
下一步看__nptl_main
函数是如何被调用的
nptl/Makefile 1 2 3 4 5 6 7 8 LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst LDFLAGS-pthread.so += -e __nptl_main $(objpfx) pt-interp.os: $(common-objpfx)runtime-linker.h
可以看到Makefile中通过链接参数-e __nptl_main
设置程序运行起始位置,可以参考man ld
还提到pt_interp.c是为了将运行时链接器路径设置给libpthread.so,那么看一下这个文件
nptl/pt-interp.c
elf/interp.c 1 2 3 4 #include <runtime-linker.h> const char __invoke_dynamic_linker__[] __attribute__ ((section (".interp" ))) = RUNTIME_LINKER;
头文件runtime-linker.h和RUNTIME_LINKER分别是什么呢?
文件Makerules中有这样一段
Makerules 1 2 3 4 5 6 7 8 9 ifeq (yes,$(build-shared))$(common-objpfx)runtime-linker.h: $(common-objpfx)runtime-linker.stamp; @: $(common-objpfx)runtime-linker.stamp: $(common-objpfx)config.make $(make-target-directory) echo ' > ${@:stamp=T} $(move-if-change) ${@:stamp=T} ${@:stamp=h} touch $@ endif
看起来runtime-linker.h是编译时生成的,里面定义了宏RUNTIME_LINKER
RUNTIME_LINKER具体定义不找了,直接看一下/usr/lib64/libpthread-2.28.so中的链接器路径吧
1 2 3 [huyu@centos852111 executable_so]$ file /usr/lib64/libpthread-2.28.so /usr/lib64/libpthread-2.28.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7a60d9380cc2284fe162f9900b28f4dbdb7029fd, for GNU/Linux 3.2.0, not stripped [huyu@centos852111 executable_so]$
看起来路径就是/lib64/ld-linux-x86-64.so.2
修改后的例子 修改后的libtest.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> #include <stdlib.h> #ifdef __i386__ const char service_interp[] __attribute__((section(".interp" ))) = "/lib/ld-linux.so.2" ;#elif defined(__x86_64__) const char service_interp[] __attribute__((section(".interp" ))) = "/lib64/ld-linux-x86-64.so.2" ;#else #endif extern int lib_func (const char *p) { printf ("in libfun, param %s\n" , p); return 2 ; } int _my_main() { int n; printf ("in so %s\n" , __FUNCTION__); n = lib_func(p: "test1" ); printf ("lib_func return %d\n" , n); exit (111 ); }
修改后的Makefile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 .PHONY: all all: libtest.so main libtest.so: libtest.o gcc -Wall -g -shared $^ -o $@ -Wl,-e,_my_main main: main.o gcc -Wall -g -ldl $^ -o $@ %.o: %.c gcc -Wall -g -fPIC $^ -c -o $@ clean: $(RM) libtest.so main *.o
执行一下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [huyu@centos852111 executable_so]$ make clean rm -f libtest.so main *.o [huyu@centos852111 executable_so]$ [huyu@centos852111 executable_so]$ make gcc -Wall -g -fPIC libtest.c -c -o libtest.o gcc -Wall -g -shared libtest.o -o libtest.so -Wl,-e,_my_main gcc -Wall -g -fPIC main.c -c -o main.o gcc -Wall -g -ldl main.o -o main [huyu@centos852111 executable_so]$ [huyu@centos852111 executable_so]$ ./main 准备调用动态库函数... in libfun, param main lib_func return 2 [huyu@centos852111 executable_so]$ [huyu@centos852111 executable_so]$ ./libtest.so in so _my_main in libfun, param test1 lib_func return 2 [huyu@centos852111 executable_so]$ echo $? 111 [huyu@centos852111 executable_so]$
看起来libtest.so作为动态链接库和作为可执行程序都满足了。
这种情况下,由于手动指定了程序入口,就不会有正常c程序的预处理和退出处理,需要自己调用exit函数了。至于如果不调用exit会发生什么,试一下就知道了。