动态链接和加载(2)

动态链接与加载原理

若干要素

  1. 编译成位置无关代码
  2. 对外部函数的调用是查表的
  3. 在运行(加载)的时候填表

我们就发明了GOT(Global Offset Table)

也就是table

有个有趣的问题

1
extern void foo(); 

编译器遇到函数调用,应该翻译成哪种指令?

  • 如果是同一个动态链接库 call foo (因为如果是一个库的,链接的时候相对地址已经确定下来了)
  • 如果是另外一个动态链接库 call TABLE(foo)

这就需要PLT (Procedure Linkage Table)

  • 函数太多,每个都标记区分太难看
  • 编译器总是生成一个直接的call

    来自另一个动态链接库 call foo@plt

更多的细节

对于一个动态链接的二进制文件,execve后的第一条指令在哪里?

  • what are the first a few steps executed after execve() of a ELF dynamic link binary? (Chatgpt)

第一条指令在/lib64/ld-linux-x86-64.so.2 _start函数
也就是说,刚刚执行玩execve后,我们的pc指针指向了ld.so中的代码。

  1. 首先会加载这个ELF文件,把相关的段加载进地址空间中。
  2. 内核会根据查看程序头表(program header)中的PT_INTERP的判断是否需要动态链接。如果需要,内核就会把动态链接器(通常来说是ld-linux.so)加载进进程的地址空间中。
  3. 设置栈的初始状态。
  4. 把控制交给动态链接器:内核把pc指针设置为动态链接器的入口地址。
  5. 动态链接器的初始化,解析符号,重定位等。
  6. 把控制权交给程序(通常来说是_start函数)
  • How can I compile an ELF binary that use an alternative dynamic loader than the default ld.so?(能否替换这个加载器)

    gcc -o hello hello.c -Wl, –dynamic-linker=/path/to/my_ld.so
    readelf -l hello | grep “program interpreter” 查看可执行文件的动态链接器

示例代码
ld.S —— 将来链接成动态链接库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <sys/syscall.h>

.globl _start
_start:
movq $SYS_write, %rax // write(
movq $1, %rdi // fd=1,
lea st(%rip), %rsi // buf=st,
movq $(ed - st), %rdx // count=ed-st
syscall // );

movq $SYS_exit, %rax // exit(
movq $1, %rdi // status=1
syscall // );

st:
.ascii "\033[01;31mThis is a loader.\033[0m\n"
ed:

hello.c

1
2
3
4
5
#include <stdio.h>

int main() {
printf("Hello\n");
}

Makefile

1
2
3
4
ld.so: ld.S hello.c
gcc -fPIC -shared -c ld.S
ld -shared ld.o -o ld.so
gcc hello.c -Wl,--dynamic-linker=$(PWD)/ld.so
  • 当我们输入make指令并执行 ./a.out 时,我们看到,我们并没有输出Hello,而是输出了This is a loader. 这也就说明了我们的动态链接器确实换掉了,由于hello.c是动态链接的,所以内核会加载我们自己的动态链接器并执行。

动态链接和加载(2)
http://example.com/2023/08/02/操作系统/jyy操作系统/动态链接和加载(2)/
作者
LiuZhaocheng
发布于
2023年8月2日
许可协议