可执行目标文件

可执行目标文件

我们已经知道连接器是如何将多个目标模块合并成一个可执行目标文件的。
此时,我们的 C 程序已经从一组 ASCII 文件文本,转化为一个二进制文件,而且这个二进制文件中包含加载程序到存储器并运行它所需的所有信息。

ELF 可执行目标文件格式

这里写图片描述
我们可以发现,可执行目标文件类似于可重定位目标文件的格式。
在 ELF 头部描述文件的总体格式,包括程序的入口点(程序运行时要执行的第一条指令的地址)。

init 段定义了一个 _init 函数,程序的初始化代码会调用它。

ELF 可执行文件被设计的很容易加载到存储器,可执行文件的连续的片被映射到连续的存储器段。段头部表描述了这种映射关系。
在这里我们给出一个简单的程序

#include <stdio.h>

int main( void ){
    int a = 10;
    int b = 20;
    int c = a + b;
    printf("%d\n", c);
    return 0;
}

编译之后我们通过 readelf -S 查看可执行文件的段信息

There are 30 section headers, starting at offset 0x9f8:
Section Headers:
  [Nr] Name              Type       Address          Offset    Size              EntSize     Flags   Link  Info  Align
  [ 0]                   NULL      0000000000000000  00000000  0000000000000000  0000000000000000    0      0      0
  [ 1] .interp           PROGBITS  0000000000400200  00000200  000000000000001c  0000000000000000 A  0      0      1
  [ 2] .note.ABI-tag     NOTE      000000000040021c  0000021c  0000000000000020  0000000000000000 A  0      0      4
  [ 3] .note.gnu.build-i NOTE      000000000040023c  0000023c  0000000000000024  0000000000000000 A  0      0      4
  [ 4] .gnu.hash         GNU_HASH  0000000000400260  00000260  000000000000001c  0000000000000000 A  5      0      8
  [ 5] .dynsym           DYNSYM    0000000000400280  00000280  0000000000000060  0000000000000018 A  6      1      8
  [ 6] .dynstr           STRTAB    00000000004002e0  000002e0  000000000000003f  0000000000000000 A  0      0      1
  [ 7] .gnu.version      VERSYM    0000000000400320  00000320  0000000000000008  0000000000000002 A  5      0      2
  [ 8] .gnu.version_r    VERNEED   0000000000400328  00000328  0000000000000020  0000000000000000 A  6      1      8
  [ 9] .rela.dyn         RELA      0000000000400348  00000348  0000000000000018  0000000000000018 A  5      0      8
  [10] .rela.plt         RELA      0000000000400360  00000360  0000000000000030  0000000000000018 A  5     12      8
  [11] .init             PROGBITS  0000000000400390  00000390  0000000000000018  0000000000000000 AX 0      0      4
  [12] .plt              PROGBITS  00000000004003a8  000003a8  0000000000000030  0000000000000010 AX 0      0      4
  [13] .text             PROGBITS  00000000004003e0  000003e0  0000000000000208  0000000000000000 AX 0      0      16
  [14] .fini             PROGBITS  00000000004005e8  000005e8  000000000000000e  0000000000000000 AX 0      0      4
  [15] .rodata           PROGBITS  00000000004005f8  000005f8  0000000000000014  0000000000000000 A  0      0      8
  [16] .eh_frame_hdr     PROGBITS  000000000040060c  0000060c  0000000000000024  0000000000000000 A  0      0      4 
  [17] .eh_frame         PROGBITS  0000000000400630  00000630  000000000000007c  0000000000000000 A  0      0      8
  [18] .ctors            PROGBITS  00000000006006b0  000006b0  0000000000000010  0000000000000000 WA 0      0      8
  [19] .dtors            PROGBITS  00000000006006c0  000006c0  0000000000000010  0000000000000000 WA 0      0      8
  [20] .jcr              PROGBITS  00000000006006d0  000006d0  0000000000000008  0000000000000000 WA 0      0      8
  [21] .dynamic          DYNAMIC   00000000006006d8  000006d8  0000000000000190  0000000000000010 WA 6      0      8
  [22] .got              PROGBITS  0000000000600868  00000868  0000000000000008  0000000000000008 WA 0      0      8
  [23] .got.plt          PROGBITS  0000000000600870  00000870  0000000000000028  0000000000000008 WA 0      0      8
  [24] .data             PROGBITS  0000000000600898  00000898  0000000000000004  0000000000000000 WA 0      0      4
  [25] .bss              NOBITS    00000000006008a0  0000089c  0000000000000010  0000000000000000 WA 0      0      8
  [26] .comment          PROGBITS  0000000000000000  0000089c  0000000000000059  0000000000000001 MS 0      0      1
  [27] .shstrtab         STRTAB    0000000000000000  000008f5  00000000000000fe  0000000000000000    0      0      1
  [28] .symtab           SYMTAB    0000000000000000  00001178  0000000000000600  0000000000000018    29     46     8
  [29] .strtab           STRTAB    0000000000000000  00001778  00000000000001f2  0000000000000000    0      0      1

加载可执行目标文件

要运行一个可执行目标文件, ./+文件名 。
因为这个文件名不是一个内置的命令,所以会认为它是一个可执行目标文件。然后通过调用某个驻留在存储器中称为加载器的操作系统代码来运行它。

任何程序都可以通过调用加载器 execve 函数来调用加载器。
加载器将可执行目标文件中的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口来运行该程序。

这个将程序拷贝到存储器并运行的过程叫做加载。

每个程序都有一个运行时存储器映像,在 32 位 Linux 系统中,代码段总是从地址 0x08048000 处开始。

  • 数据段是在接下来的一个 4KB 对齐的地址处。
  • 运行时堆在读写段之后接下来的第一个 4KB 对齐的地址处。
  • 还有一个段是为共享库保留的。
  • 用户栈从最大的 合法用户地址开始,向下增长。
  • 从栈的上部开始的段是为操作系统驻留存储器的部分的代码和数据保留的。
    这里写图片描述

当加载器运行时,它将创建如图的存储器映像。在段头部表的指导下,将可执行文件的相关内容拷贝到代码和数据段。
随后,加载器跳转到程序入口点,也就是符号 _start 的地址。
在从 .text段和.init段中调用了初始化例程后。
启动代码调用 atexit 例程,这个程序附加了一系列在应用程序正常终止时应该调用的程序。
exit 函数运行 atexit 注册的函数,然后通过调用 _exit 将控制返回给操作系统。
接着 启动代码调用应用程序的 main 程序,它会开始执行我们的 C 代码,在应用程序返回之后,启动代码调用 _exit 程序,他将控制返回给操作系统。

启动例程的伪码

0x08048000 <_start>:
    call __libc_init_first
    call _init
    call atexit
    call main
    call _exit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值