程序的链接

链接

  • 预处理、编译、汇编三个阶段对一个模块(一个.c文件)进行处理,得到对应的可重定位目标文件(.o文件)
  • 链接过程将多个可重定位目标文件合并以生成可执行目标文件
  • 链接命令
    $gcc -static -o myproc main.o sum.o
    (static表示静态链接,若未指定-o选项,则生成的可执行目标文件名默认为a.out)

三种文件格式

采用ELF文件格式
以以下c程序为例

/* main.c */
/* $begin main */
#include<stdio.h>
int sum(int *a, int n);

int array[2] = {1, 2};

int main() 
{
    int val = sum(array, 2);
    printf("val=%d",val);
    return val;
}
/* $end main */
/* sum.c */
/* $begin sum */
int sum(int *a, int n)
{
    int i, s = 0;
    
    for (i = 0; i < n; i++) { 
        s += a[i];
    }
    return s;
}        
/* $end sum */

分别执行
$cpp main.c main.i
$gcc -S main.i -o main.s
$as main.s -o main.o
即可得到可重定位目标文件mian.o

可重定位目标文件(.o文件)

  • 其代码和数据可和其他可重定位文件合并为可执行文件
    •每个.o 文件由对应的.c文件生成
    •每个.o文件代码和数据地址都从0开始

  • 文件格式如下:

ELF头包括16字节标识信息、文件类型(.o, exec, .so)、机器类型(如x86-64、节头表的偏移、节头表的表项大小以及表项个数
.text节编译后的代码部分
.rodata节只读数据,如printf 格式串、switch 跳转表等
.data节已初始化的全局变量
.bss节未初始化全局变量,仅是占位符,不占 据任何实际磁盘空间。区分初始化和非 初始化是为了空间效率
.symtab节存放函数和全局变量(符号表)信息,它不包括局部变量
.rel.text节text节的重定位信息,用于重新修改代码段的指令中的地址信息
.rel.data节.data节的重定位信息,用于对被模块使 用或定义的全局变量进行重定位的信息
.debug节调试用符号表(gcc -g)
.line节原始c程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到。
.strtab节包含symtab和debug节中符号及节名
节头部表每个节的节名、偏移和大小

查看ELF头

执行指令readelf -h main.o 可查看main.o的二进制存储的ELF头信息

ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          Intel 80386
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          636 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         40 (字节)
  节头数量:         13
  字符串表索引节头: 10

  • Magic,魔数:通过对魔数的判断可以确定文件的格式和类型。如:ELF 的可执行文件格式的头 4 个字节为0x7F、e、l、f。
  • 类型:这里是32位的ELF格式
  • 数据:负数用二进制补码形式表示,小端法存放
  • 版本:当前ELF文件头版本号,此处为1
  • OS/ABI:指出操作系统类型
  • ABI版本
  • 类型:指出ELF文件的类型(共有三种类型:可重定位目标文件,可执行文件,共享库文件),此处为REL(可重定位目标文件)
  • 系统架构:此处为80386
  • 版本: 当前目标文件的版本号
  • 入口点地址:程序的虚拟地址入口,因为此处是一个可重定位文件,还未链接生成可执行文件,故入口地址为0.
  • 程序头起点:可重定问目标文件没有程序头部表。
  • Start of section headers: 节头部表的文件偏移,即节头部表的起始地址
  • 标志:一个与处理器相关联的标志
  • 本头的大小:ELF文件头的字节数。 52 (字节)
  • 程序头大小:此文件中无程序头,0 (字节)
  • Number of program headers: 0
  • 节头大小:节头部表中每个section的大小。40 (字节)
  • 节头数量:节头表中section的数量。 13
  • 字符串表索引节头:.strtab在节头表中的索引 。10

查看节头表信息

$readelf -S main.o

共有 13 个节头,从偏移量 0x27c 开始:

节头:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000044 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 0001f4 000020 08   I 11   1  4
  [ 3] .data             PROGBITS        00000000 000078 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 000080 000000 00  WA  0   0  1
  [ 5] .rodata           PROGBITS        00000000 000080 000007 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 000087 000036 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 0000bd 000000 00      0   0  1
  [ 8] .eh_frame         PROGBITS        00000000 0000c0 000044 00   A  0   0  4
  [ 9] .rel.eh_frame     REL             00000000 000214 000008 08   I 11   8  4
  [10] .shstrtab         STRTAB          00000000 00021c 00005f 00      0   0  1
  [11] .symtab           SYMTAB          00000000 000104 0000d0 10     12   9  4
  [12] .strtab           STRTAB          00000000 0001d4 00001e 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

  • bss节只有装入内存时才会分配。
  • 可重定位目标文件中,每个可装入节的起始地址总是0
  • off:每个节的起始地址
  • size:每个节的大小

在可重定位文件main.o中的结构如下:
在这里插入图片描述

查看符号表中的信息

$readelf -s main.o

Symbol table '.symtab' contains 13 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 
     7: 00000000     0 SECTION LOCAL  DEFAULT    8 
     8: 00000000     0 SECTION LOCAL  DEFAULT    6 
     9: 00000000     8 OBJECT  GLOBAL DEFAULT    3 array
    10: 00000000    68 FUNC    GLOBAL DEFAULT    1 main
    11: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND sum
    12: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

可执行文件

  • 可执行目标文件(默认为a.out)
    • 命令:$ gcc -static -o myproc main.o sum.o
    • 包含的代码和数据可以被直接复制到内存并被执行
    • 代码和数据地址为虚拟地址空间中的地址

• 文件格式与可重定位文件稍有不同:

  1. ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段 为0
  2. 多一个程序头表,也称段头表 (segment header table)
  3. 多一个.init节,用于定义 _init函数,该函数用来进行可执行目标文件开始执行时的初始化工作
  4. 少两个.rel节(无需重定位)
    在这里插入图片描述

查看ELF头

ELF 头:
  Magic:   7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - GNU
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Intel 80386
  版本:                              0x1
  入口点地址:               0x8048736
  程序头起点:          52 (bytes into file)
  Start of section headers:          727040 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       32 (字节)
  Number of program headers:         6
  节头大小:         40 (字节)
  节头数量:         33
  字符串表索引节头: 30
  • 装入内存时,ELF头、程序头表、.init节、.rodata节会被装入只读代码段
  • .data节和.bss节会被装入读写数据段
  • 程序头表:描述可执行文件中的节与虚拟空间中的存储段之间的映射关系
  • 一个表项40B,存储虚拟地址空间中一个连续的段或一个特殊的节的信息。

从得到的信息可以看到:此时,入口点地址不再是0,而是而是执行代码的第一条指令的地址;程序头的起点和大小不再是0;节头大小、数量和strtab在节头表中的索引均更新了。

查看程序头表

$ readelf -l myproc

Elf 文件类型为 EXEC (可执行文件)
入口点 0x8048736
共有 6 个程序头,开始于偏移量 52

程序头:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0xa0cd1 0xa0cd1 R E 0x1000
  LOAD           0x0a0f5c 0x080e9f5c 0x080e9f5c 0x01024 0x01e48 RW  0x1000
  NOTE           0x0000f4 0x080480f4 0x080480f4 0x00044 0x00044 R   0x4
  TLS            0x0a0f5c 0x080e9f5c 0x080e9f5c 0x00010 0x00028 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x0a0f5c 0x080e9f5c 0x080e9f5c 0x000a4 0x000a4 R   0x1

 Section to Segment mapping:
  段节...
   00     .note.ABI-tag .note.gnu.build-id .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_subfreeres .stapsdt.base __libc_atexit __libc_thread_subfreeres .eh_frame .gcc_except_table 
   01     .tdata .init_array .fini_array .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 
   02     .note.ABI-tag .note.gnu.build-id 
   03     .tdata .tbss 
   04     
   05     .tdata .init_array .fini_array .jcr .data.rel.ro .got 
  • 第一可装入段:第0x00000~0x73880字节(包括ELF头、程序头表、.init、 .text和.rodata节),映射到虚拟地址0x8048000开始长度为0xa0cd1字节的区域 ,按0x1000=212=4KB对齐,具有只读/执行权限(Flg=RE),是只读代码段。
  • 第二可装入段:第0x0a1060开始长度为0xf20字节的.data节,映射到虚拟地址 0x80e9f5c开始长度为0x01024字节的存储区域,在0xf1024=4132B存储区中,前 0x108=3872B用.data节内容初始化,后面4132-38724=260B对应.bss节,初始化为0 ,按0x1000=4KB对齐,具有可读可写权限(Flg=RW),是可读写数据段。

共享的目标文件(.so文件)

-特殊的可重定位目标文件,能在装入或运行时被装入到内 存并自动被链接,称为共享库文件

  • Windows 中称其为 Dynamic Link Libraries (DLLs)

链接的过程

1、确定符号引用关系(符号解析)

  • 找到程序中有定义和引用的符号(包括变量和函数等)
  • 编译器将定义的符号存放在一个符号表(symbol table)中.
  • 链接器将每个符号的引用都与一个确定的符号定义建立关联

2、重定位

1)合并相关.o文件
2)确定每个符号的地址
3)在指令中填入新地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值