制作动态链接库 前言:在执行./pikafish/pikafish-avx2的时候发现glib库版本不对,如下:
1 2 3 4 dku@dku:~/桌面/workspaces$ ./pikafish/pikafish-avx2 ./pikafish/pikafish-avx2: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./pikafish/pikafish-avx2) ./pikafish/pikafish-avx2: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by ./pikafish/pikafish-avx2)./pikafish/pikafish-avx2: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.30' not found (required by ./pikafish/pikafish-avx2)
于是想查看一下我的系统中的glibc版本,已知有如下几种方式来查看:
ldd –version
/lib/x86_64-linux-gnu/libc.so.6
通过ldd --version查看没有什么好说的,ldd本身是一个sh脚本,我在其他的笔记中详细分析过这个脚本代码。但是仔细看/lib/x86_64-linux-gnu/libc.so.6,他是一个so文件啊,知识有限一直以为so文件不能执行,于是我执行了一下发现,竟然输出了如下内容:
1 2 3 4 5 6 7 8 9 10 dku@dku:~/桌面/workspaces$ /lib/x86_64-linux-gnu/libc.so.6 GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.18) stable release version 2.31. Copyright (C) 2020 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. Compiled by GNU CC version 9.4.0. libc ABIs: UNIQUE IFUNC ABSOLUTE For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source /glibc/+bugs>.
查了下一般情况下动态链接库(.so)确实是不能直接执行的,但是libc.so.6 能够执行是因为专门指定的“入口地址”。可以通过readelf命令来查看其入口点地址,可以看到是入口点地址: 0x241c0。因此虽然它是 .so,但在内核眼中,它既满足共享库的格式(ELF Shared Object),又包含了执行代码。这在 Linux 中被称为 PIE (Position Independent Executable) 技术的变体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 readelf -h /lib/x86_64-linux-gnu/libc.so.6 dku@dku:~/桌面$ readelf -h /lib/x86_64-linux-gnu/libc.so.6 ELF 头: Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00 类别: ELF64 数据: 2 补码,小端序 (little endian) Version: 1 (current) OS/ABI: UNIX - GNU ABI 版本: 0 类型: DYN (共享目标文件) 系统架构: Advanced Micro Devices X86-64 版本: 0x1 入口点地址: 0x241c0 程序头起点: 64 (bytes into file) Start of section headers: 2025240 (bytes into file) 标志: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 14 Size of section headers: 64 (bytes) Number of section headers: 68 Section header string table index: 67
为了弄明白其中道理,刚好来了解一下动态链接库的制作过程,这也是非常非常基础但是一直没有学习的内容了。
制作一个可以运行的动态链接库文件(.so) 先来制作一个普通的so文件 创建一个简单的 C 语言文件 mylib.c
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> void hello_from_so () { printf ("你好!这是一个来自动态链接库的函数。\n" ); } int add (int a, int b) { return a + b; }
编译并生成 .so 文件
1 gcc -fPIC -shared -o libmylib.so mylib.c
其中:
-fPIC: 告诉编译器生成位置无关代码,这是动态库必需的。
-shared: 告诉链接器生成一个共享库文件,而不是可执行文件。
在程序中调用这个库
1 2 3 4 5 6 void hello_from_so () ; int main () { hello_from_so(); return 0 ; }
编译
1 2 gcc main.c -L. -lmylib -o test_app
运行
由于 Linux 默认只在 /lib 或 /usr/lib 等标准路径找库,保险起见LD_LIBRARY_PATH 指定当前目录,但是在我的系统中没有指定也没有任何问题,结果如下。
1 LD_LIBRARY_PATH=. ./test
1 2 dku@dku:~/桌面$ ./test 你好!这是一个来自动态链接库的函数。
制作一个可运行的so文件 本类以为制作一个可以运行的so文件只需要指定入口点就可以了,但是实际制作的时候发现总是不能运行,遇到各种各样的问题,我让ai帮我生成的C代码没有一个可以用的,最后还是纯汇编代码才成功运行。过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <unistd.h> #include <stdio.h> void hello_from_so () { printf ("你好!这是一个来自动态链接库的函数。\n" ); } int add (int a, int b) { return a + b; } void my_entry () { char msg[] = "这是一个可以直接运行的自定义库!\n" ; write(1 , msg, sizeof (msg)); _exit(0 ); }
编译
1 2 3 4 gcc -fPIC -shared -o libmylib_entry.so mylib_entry.c \ -Wl,-e,my_entry \ -Wl,--section-start=.interp=0x1000 \ -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2
理论上上述代码是没有问题的,并且通过以下命令也成功生成了libmylib_entry.so,但是一运行就出现段错误。这里将上述代码贴出来的主要目的是为了说明这样理论上是可以的,至于为什么不行可能与当前系统的各种环境比如栈对齐或C语言环境有关吧。
于是使用内联汇编重新生成了一份代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <unistd.h> void my_entry () { const char msg[] = "成功直接运行自定义 .so 文件!\n" ; __asm__ __volatile__ ( "mov $1, %%rax;" "mov $1, %%rdi;" "mov %0, %%rsi;" "mov %1, %%rdx;" "syscall;" "mov $60, %%rax;" "xor %%rdi, %%rdi;" "syscall;" : : "r" (msg), "r" ((long )sizeof (msg)) : "rax" , "rdi" , "rsi" , "rdx" ); }
重新编译(去掉 不稳定的 section-start)
1 2 3 gcc -fPIC -shared -o libmylib_entry.so mylib_entry.c \ -Wl,-e,my_entry \ -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2
结果运行又出现段错误,询问ai解释:直接运行 .so 经常崩溃是因为栈指针 (RSP) 未对齐或环境变量/辅助向量 (Auxiliary Vector) 未正确初始化,导致即使是简单的内联汇编也会在进入函数前因 push/mov 操作触发段错误
于是干脆生成一个纯汇编代码
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 #include <sys/syscall.h> .section .text .global my_entry my_entry: # 1. 直接发起 write 系统调用 mov $SYS_write, %rax # 系统调用号 (1 ) mov $1 , %rdi # 文件描述符 (stdout ) lea msg (%rip) , %rsi # 字符串地址 mov $len, %rdx # 字符串长度 syscall # 2. 直接发起 exit 系统调用 mov $SYS_exit, %rax # 系统调用号 (60 ) xor %rdi, %rdi # 退出码 0 syscall .section .rodata msg: .ascii "恭喜!自定义 .so 库直接运行成功!\n" len = . - msg # 关键:指定解释器路径到 .interp 段 .section .interp,"a" .asciz "/lib64/ld-linux-x86-64.so.2"
直接编译汇编文件,并告诉链接器入口位置
1 gcc -fPIC -shared -o libmylib_entry.so mylib_entry.S -Wl,-e,my_entry
1 2 dku@dku:~/桌面$ ./libmylib_entry.so 恭喜!自定义 .so 库直接运行成功!
终于成功了!
查看两个so文件的区别 查看有入口的so:libmylib_entry.so
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 36 37 38 39 40 41 42 43 dku@dku:~/桌面$ readelf -l libmylib_entry.so Elf 文件类型为 DYN (共享目标文件) Entry point 0x10f9 There are 10 program headers, starting at offset 64 程序头: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x0000000000000230 0x0000000000000230 R 0x8 INTERP 0x0000000000002030 0x0000000000002030 0x0000000000002030 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000458 0x0000000000000458 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000131 0x0000000000000131 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000054 0x0000000000000054 R 0x1000 LOAD 0x0000000000002e80 0x0000000000003e80 0x0000000000003e80 0x00000000000001a0 0x00000000000001a8 RW 0x1000 DYNAMIC 0x0000000000002e90 0x0000000000003e90 0x0000000000003e90 0x0000000000000150 0x0000000000000150 RW 0x8 NOTE 0x0000000000000270 0x0000000000000270 0x0000000000000270 0x0000000000000024 0x0000000000000024 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RWE 0x10 GNU_RELRO 0x0000000000002e80 0x0000000000003e80 0x0000000000003e80 0x0000000000000180 0x0000000000000180 R 0x1 Section to Segment mapping: 段节... 00 01 .interp 02 .note.gnu.build-id .gnu.hash .dynsym .dynstr .rela.dyn 03 .init .plt .plt.got .text .fini 04 .rodata .interp .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.gnu.build-id 08 09 .init_array .fini_array .dynamic .got
查看普通so:libmylib_entry.so
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 36 37 38 39 40 41 42 43 44 45 dku@dku:~/桌面t$ readelf -l libmylib.so Elf 文件类型为 DYN (共享目标文件) Entry point 0x1060 There are 11 program headers, starting at offset 64 程序头: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000550 0x0000000000000550 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000155 0x0000000000000155 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000124 0x0000000000000124 R 0x1000 LOAD 0x0000000000002e10 0x0000000000003e10 0x0000000000003e10 0x0000000000000218 0x0000000000000220 RW 0x1000 DYNAMIC 0x0000000000002e20 0x0000000000003e20 0x0000000000003e20 0x00000000000001c0 0x00000000000001c0 RW 0x8 NOTE 0x00000000000002a8 0x00000000000002a8 0x00000000000002a8 0x0000000000000020 0x0000000000000020 R 0x8 NOTE 0x00000000000002c8 0x00000000000002c8 0x00000000000002c8 0x0000000000000024 0x0000000000000024 R 0x4 GNU_PROPERTY 0x00000000000002a8 0x00000000000002a8 0x00000000000002a8 0x0000000000000020 0x0000000000000020 R 0x8 GNU_EH_FRAME 0x0000000000002038 0x0000000000002038 0x0000000000002038 0x0000000000000034 0x0000000000000034 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000002e10 0x0000000000003e10 0x0000000000003e10 0x00000000000001f0 0x00000000000001f0 R 0x1 Section to Segment mapping: 段节... 00 .note.gnu.property .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 01 .init .plt .plt.got .plt.sec .text .fini 02 .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.gnu.property 06 .note.gnu.build-id 07 .note.gnu.property 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got
在 libmylib_entry.so 中:INTERP 0x... [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] 而在普通的 libmylib.so 中,这个段是完全不存在的。 作用:当你尝试 ./ 运行一个文件时,Linux 内核会读取这个 INTERP 段。它告诉内核:“我不是一个独立的个体,请先加载这个 /lib64/ld-linux-x86-64.so.2 解释器来帮我跑起来。” 结论:没有这个段,内核不知道该如何处理动态库的符号寻址,所以会直接崩溃。
libmylib_entry.so 的入口是 0x10f9,这指向了汇编代码 my_entry。libmylib.so 虽然也有入口 0x1060,但那是 GCC 自动生成的默认初始化代码(通常是 _init)。因为没有 INTERP 指定解释器,内核在跳转到这个地址时,运行环境(寄存器、栈)都是错误的,导致一启动就触发 段错误。
观察 GNU_STACK:可执行库显示的是 RWE (Read, Write, Execute)。 普通库显示的是 RW,说明汇编版本获得了更宽松的执行权限。
完