创建动态链接库

编译并使用动态链接库

  1. 创建共享库源文件 math.c

    int add(int a, int b) {
        return a + b;
    }
    
  2. 创建共享库头文件 math.h

    int add(int a, int b);
    
  3. 编译动态链接库:

    gcc -shared -fPIC math.c -o libmath.so
    
    • -shared:共享对象(shared object),是 Linux 下对动态库的另一种称谓
    • -fPIC:Position Independent Code,
  4. 编辑主程序源文件 main.c

    #include <stdio.h>
    #include "math.h"
    
    int main() {
        printf("add(1, 2) returned %d\n", add(1, 2))
    }
    
  5. 编译主程序:

    gcc main.c -lmath -L. -o main
    
    • -lmath-l 选项用来指定动态链接库,这里指定了 math 库(前缀 lib 和后缀 .so 被省略)
    • -L.-L 选项用来指定查找动态链接库的位置,这里指定了当前目录 .
  6. 执行主程序:

    LD_LIBRARY_PATH="$(pwd)" ./main
    
    • LD_LIBRARY_PATH:指定运行程序时查找动态链接库的路径

查看内存映射情况

可以在主程序中加一个死循环让程序保持运行:

#include <stdio.h>
#include "math.h"

int main() {
   printf("add(1, 2) returned %d\n", add(1, 2))
}

然后在后台启动程序:

./main >/dev/null &

查看内存映射:

$ cat /proc/1863281/maps
556d6ef54000-556d6ef55000 r--p 00000000 103:03 137297963                 /home/user/main
556d6ef55000-556d6ef56000 r-xp 00001000 103:03 137297963                 /home/user/main
556d6ef56000-556d6ef57000 r--p 00002000 103:03 137297963                 /home/user/main
556d6ef57000-556d6ef58000 r--p 00002000 103:03 137297963                 /home/user/main
556d6ef58000-556d6ef59000 rw-p 00003000 103:03 137297963                 /home/user/main
556da1a35000-556da1a56000 rw-p 00000000 00:00 0                          [heap]
7f88057a4000-7f88057a7000 rw-p 00000000 00:00 0
7f88057a7000-7f88057cf000 r--p 00000000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f88057cf000-7f8805964000 r-xp 00028000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f8805964000-7f88059bc000 r--p 001bd000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f88059bc000-7f88059bd000 ---p 00215000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f88059bd000-7f88059c1000 r--p 00215000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f88059c1000-7f88059c3000 rw-p 00219000 103:02 23604638                  /usr/lib/x86_64-linux-gnu/libc.so.6
7f88059c3000-7f88059d0000 rw-p 00000000 00:00 0
7f88059e3000-7f88059e4000 r--p 00000000 103:03 137297952                 /home/user/libmath.so
7f88059e4000-7f88059e5000 r-xp 00001000 103:03 137297952                 /home/user/libmath.so
7f88059e5000-7f88059e6000 r--p 00002000 103:03 137297952                 /home/user/libmath.so
7f88059e6000-7f88059e7000 r--p 00002000 103:03 137297952                 /home/user/libmath.so
7f88059e7000-7f88059e8000 rw-p 00003000 103:03 137297952                 /home/user/libmath.so
7f88059e8000-7f88059ea000 rw-p 00000000 00:00 0
7f88059ea000-7f88059ec000 r--p 00000000 103:02 23604552                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f88059ec000-7f8805a16000 r-xp 00002000 103:02 23604552                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f8805a16000-7f8805a21000 r--p 0002c000 103:02 23604552                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f8805a22000-7f8805a24000 r--p 00037000 103:02 23604552                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7f8805a24000-7f8805a26000 rw-p 00039000 103:02 23604552                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffdbe414000-7ffdbe435000 rw-p 00000000 00:00 0                          [stack]
7ffdbe5ee000-7ffdbe5f2000 r--p 00000000 00:00 0                          [vvar]
7ffdbe5f2000-7ffdbe5f4000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

可以看到程序加载了 libc.so.6libmath.so 以及 ld-linux-x86-64.so.2 三个共享链接库。

另外,你会注意到 libc.so.6ld-linux-x86-64.so.2 后面都有一个数字,这个数字是 ABI(Application Binary Interface)的主版本号。当 ABI 发生不兼容的变化时,这个主版本号就会增加。目前主流 Linux 系统使用的都是版本 6。

libc.so.6 实际上是一个符号链接,指向具体的实现文件:

libc.so.6 -> libc-2.31.so

这里的 2.31 是实际的库文件版本号,而 6 是 ABI 版本号。

全局偏移表

为了使用动态链接库,程序中要用到库函数的地方在一开始是一个空指针。当库载入内存中后,库函数的地址会被写入全局偏移表(Global Offset Table, GOT)。我们可以在库文件的 Section 部分找到 GOT:

$ readelf -S ./libmath.so
There are 25 section headers, starting at offset 0x34d8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .note.gnu.pr[...] NOTE             00000000000002a8  000002a8
       0000000000000020  0000000000000000   A       0     0     8
  [ 2] .note.gnu.bu[...] NOTE             00000000000002c8  000002c8
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .gnu.hash         GNU_HASH         00000000000002f0  000002f0
       0000000000000024  0000000000000000   A       4     0     8
  [ 4] .dynsym           DYNSYM           0000000000000318  00000318
       0000000000000090  0000000000000018   A       5     1     8
  [ 5] .dynstr           STRTAB           00000000000003a8  000003a8
       0000000000000059  0000000000000000   A       0     0     1
  [ 6] .rela.dyn         RELA             0000000000000408  00000408
       00000000000000a8  0000000000000018   A       4     0     8
  [ 7] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [ 8] .plt              PROGBITS         0000000000001020  00001020
       0000000000000010  0000000000000010  AX       0     0     16
  [ 9] .plt.got          PROGBITS         0000000000001030  00001030
       0000000000000010  0000000000000010  AX       0     0     16
  [10] .text             PROGBITS         0000000000001040  00001040
       00000000000000d1  0000000000000000  AX       0     0     16
  [11] .fini             PROGBITS         0000000000001114  00001114
       000000000000000d  0000000000000000  AX       0     0     4
  [12] .eh_frame_hdr     PROGBITS         0000000000002000  00002000
       0000000000000024  0000000000000000   A       0     0     4
  [13] .eh_frame         PROGBITS         0000000000002028  00002028
       000000000000007c  0000000000000000   A       0     0     8
  [14] .init_array       INIT_ARRAY       0000000000003e80  00002e80
       0000000000000008  0000000000000008  WA       0     0     8
  [15] .fini_array       FINI_ARRAY       0000000000003e88  00002e88
       0000000000000008  0000000000000008  WA       0     0     8
  [16] .dynamic          DYNAMIC          0000000000003e90  00002e90
       0000000000000150  0000000000000010  WA       5     0     8
  [17] .got              PROGBITS         0000000000003fe0  00002fe0
       0000000000000020  0000000000000008  WA       0     0     8
  [18] .got.plt          PROGBITS         0000000000004000  00003000
       0000000000000018  0000000000000008  WA       0     0     8
  [19] .data             PROGBITS         0000000000004018  00003018
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .bss              NOBITS           0000000000004020  00003020
       0000000000000008  0000000000000000  WA       0     0     1
  [21] .comment          PROGBITS         0000000000000000  00003020
       000000000000002b  0000000000000001  MS       0     0     1
  [22] .symtab           SYMTAB           0000000000000000  00003050
       0000000000000258  0000000000000018          23    20     8
  [23] .strtab           STRTAB           0000000000000000  000032a8
       0000000000000159  0000000000000000           0     0     1
  [24] .shstrtab         STRTAB           0000000000000000  00003401
       00000000000000d6  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), l (large), p (processor specific)

采用这种方法实现的动态链接库也叫做地址无关代码(Position Independent Code, PIC)

参考:动态链接库(dll)是如何工作的?| 小红书

posted @ 2025-03-05 02:54  Undefined443  阅读(29)  评论(0)    收藏  举报