程序的链接
链接
- 预处理、编译、汇编三个阶段对一个模块(一个.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
- 包含的代码和数据可以被直接复制到内存并被执行
- 代码和数据地址为虚拟地址空间中的地址
• 文件格式与可重定位文件稍有不同:
- ELF头中字段e_entry给出执行程序时第一条指令的地址,而在可重定位文件中,此字段 为0
- 多一个程序头表,也称段头表 (segment header table)
- 多一个.init节,用于定义 _init函数,该函数用来进行可执行目标文件开始执行时的初始化工作
- 少两个.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)在指令中填入新地址