csapp:链接

从c源代码变为可执行文件的四个步骤

  1. 预处理:得到.i文件。预处理之后还是一个可读文本文件,里面不存在宏定义。所以预处理做的事情有:(1)删除#define并展开所定义的宏 (2)处理预编译指令如#ifdef (3)删掉所有注释 (4)插入头文件到#include处等。
  2. 编译:得到.s文件。变成汇编文件,还是可读文本文件
  3. 汇编:得到.o文件。变成可重定向目标文件,不可读二进制代码
  4. 链接:前三个阶段将.c文件变成了可重定位目标文件.o,链接是将多个可重定位目标文件合并,变成可执行目标文件。

链接要完成的两个任务

  1. 符号解析。符号:函数、全局变量、静态变量。符号解析就是将每一个符号定义与符号引用关联起来。
  2. 重定位:编译器和链接器生成地址从0开始的代码与数据节。链接的符号解析就是为了将符号定义与内存位置关联起来,从而重定位这些节,然后修改所有对这些节的引用,使得它们指向这个内存地址。

链接的好处

  1. 可模块化:可将源程序分为多个文件,可以构建公共函数库。
  2. 效率高:时间上,分开编译,修改时只需要重新编译那一个模块即可。空间上,不需要将整个公共库的代码加载进内存,只要加载调用函数代码。

链接本质:合并相同的"节"。

目标文件三种形式

  • 可重定位目标文件.o。所有变量和函数地址从0开始。
  • 可执行目标文件。无后缀名或默认为a.out。已经有确定的地址,为虚拟地址中的地址。
  • 共享目标文件.so。特殊的可执行目标文件,能在运行时被装入到内存并自动被链接。

ELF

标准的目标文件格式:可执行可链接格式。

最小单位为节,有.text、.data、.bss等节

段中包含多个访问状态相同的节,如可读段

两种视图:

  • 链接视图:可重定位目标文件
  • 执行试图:可执行目标文件

可重定位目标文件格式

 

下面例子中elf文件的查看结果以main.c与func_of_static.c两个函数获得:

/* main.c */
#include<stdio.h>

int array[2] = {1, 2};
int f();
int g();
int h();

int main() 
{
    int val_f = f();
	int val_g = g();
	int val_h = h();
	printf("%d %d %d\n",val_f,val_g,val_h); 
    return 0;
}
/* func_of_static.c*/
static int x = 15;

int f() {
    static int x = 17;
    return x++;
}

int g() {
    static int x = 19;
    return x += 14;
}

int h() {
    return x += 27;
}

ELF头

  • ELF头文件的头几个字节为魔数,用来标识文件的类型,如是可重定位的目标文件还是可执行的目标文件。
  • 定义了机器的信息、魔数、节头表的文件偏移、起始位置、节头表中条目的大小和数量、程序的入口地址等
  • 看可重定位目标文件ELF头:readelf -h main.o

example:

对于main.c,查看ELF文件头如下:

先用gcc 加-c参数编译生成.o文件。

readelf -h main.o

从elf头开始,我们可以看到魔数。看出文件类型为可重定位文件。所以该elf文件没有程序头,入口地址为0.

节头表

  • readelf -S test.o
  • 节头表定义了各节的节名、起始地址(可重定位目标文件中为0)、偏移、访问属性等

example

我们再用-S看一下func_of_static.o的节头表

readelf -S func_of_static.o

从图中我们可以清晰地看出每一个节的大小何偏移量以及节头表的偏移量,因为是可重定位文件,所以地址都是从0开始。

根据这节头表,我们就可以画出可重定位目标文件func_of_static.o的结构:

符号表

  • .symtab节
  • readelf -s main.o
  • 记录符号在对应节中 偏移量(value)、符号对应目标字节数(size)、符号类型、对应目标所在节等信息。

example

注意在func_of_static.c代码中定义了三次静态变量x,但它们表示不同的变量,可以看到符号表中name项将变量重新命了名。

可执行目标文件格式

  • 多一个程序表头(段头表):程序头表描述可执行文件中的节与虚拟 空间中的存储段之间的映射关系,一个表项(32B)说明虚拟地址空间中 一个连续的段或一个特殊的节
  • 多一个.init节,定义了_init()函数,用于可执行文件执行时的初始化。
  • 少两个.rel节,因为无需重定位

程序表头(段头表)

  • readelf -l main
  • 描述可执行文件中的节与虚拟空间中的存储段之间的映射关系
  • 一个表项说明虚拟地址空间中一个连续的段或一个特殊的节

example

以main文件为例

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章