程序构建过程的第二个阶段就是链接,链接过程输入的是目标文件的集合。每个目标文件可以被看作单个源代码文件的二进制存储版本,需要为程序内存映射提供各种各样的节(代码.text 初始化数据.data 未初始化数据.bss 和只读数据.rdata),链接器的最终任务是将独立的节组合成最终的程序内存映射节,与此同时解析所有的引用。
链接过程包括一系列阶段(重定位、解析引用),接下来我们介绍这些阶段。
链接过程的第一个阶段仅仅进行拼接,其过程是将分散在单独目标文件中不同类型的节拼接到程序内存映射节中。
如图,为了完成任务,需要将之前预留的空间,也就是节中从0开始的地址范围转换成最终程序内存映射中更具体的地址范围。
现在我们来看链接过程中最难的部分,将节的地址范围线性地转换成程序内存映射地址范围。相比来说,更艰巨的任务在于为不同的部分的代码建立关联,使得程序成为一个整体。
function.h代码:
#pragma once
#define FIRST_OPTION
#ifdef FIRST_OPTION
#define MULTIPLIER (3.0)
#else
#define MULTIPLIER (2.0)#endif
float add_and_multiply(float x,float y);
function.c
//#include "function.h"
int nCompletionStatus = 0;
float add(float x,float y)
{
float z = x + y;
return z;
}
float add_and_multiply(float x,float y)
{
float z = add(x,y);
z *= 3;
return z;
}
main.c
#include "function.h"
extern int nCompletionStatus;
int main(int argc,char* argv[])
{
float x = 1.0;
float y = 5.0;
float z;
z= add_and_multiply(x,y);
nCompletionStatus =1;
return 0;
}
在上例代码中
该问题如图描述:
main.o
为了解决这类问题,我们需要在链接阶段就对这些引用进行解析,此时链接器需要:
程序内存映射图
gcc -c function.c main.c
gcc function.o main.o -o demoApp
反汇编main.o文件
objdump -D -M intel main.o
划红线的是跳转自身,是因为链接器不知道函数的地址。先用伪地址代替。
反汇编demoApp
objdump -D -M intel demoApp
画红线的位置分别是 add_and_multiply 地址为11aa 和nCompletionStatus的地址。
执行下面命令查看,看到nCompletionStatus地址为4014.
objdump -x -j .bss demoapp