7 加载并执行二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 5  touch a.c
6 gcc -E -P a.c
7 gcc -S -masm=intel a.c
8 cat a.s
9 gcc -c a.c
10 file a.o
11 gcc a.c
12 file a.out
13 ls
14 ./a.out
15 readelf --syms a.out
16 strip --strip-all a.out
17 file a.out
18 objdump -sj .rodata a.o
19 objdump -M intel -d a.o
20 readelf --relocs a.o
21 ls
22 objdump -M intel -d ./a.out.stripped

加载并执行二进制文件

加载二进制文件是一个复杂的过程,涉及操作系统的大量工作。同样重要的是,内存中二进制文件的表示不一定与磁盘上二进制文件的表示一一对应。

图1-2显示了如何在Linux操作系统上 加载ELF二进制文件,如刚编译的二进制文件。从高层次上讲,这与在Windows操作系统上加载PE二进制文件非常相似。

image-20220923104102656

运行二进制文件时,

  • 操作系统首先要为运行的程序创建一个进程,其中包括虚拟地址空间。
  • 随后,操作系统将解释器映射到进程的虚拟内存中。这是一个用户层程序,它知道如何加载二进制文件并执行必要的重定位。在Linux操作系统中,解释器通常是一个名为ldlinux.so的共享库。在Windows操作系统中,解释器的功能作为ntdll.dll的一部分实现
  • 加载解释器后,内核将控制权转移给它,然后解释器开始它在用户空间的工作。
  • 解释器将二进制文件加载到其虚拟地址空间(与解释器相同的加载空间)中。
  • 然后,解析并找出二进制文件所使用的动态库。解释器将它们映射到虚拟地址空间(使用mmap或同等函数),最后在二进制代码节中执行所有必要的重定位,以填充正确的地址引用动态库。实际上,通常会将动态库中对函数的引用的解析过程推迟。换句话说,解释器不是在加载时立即解析这些引用,而是仅在首次调用引用时才解析引用。这就是所谓的延迟绑定。
  • 重定位完成后,解释器将会查找二进制文件的入口点并将控制权转移给入口点,从而开始正常执行二进制文件。

Linux ELF二进制文件带有一个名为.interp的特殊节,该节指定用于加载二进制文件的解释器路径。

image-20220923104534576