链接器相关问题

从.c文件到 可执行 文件需要经历的过程

static linking

静态链接器的输入是一组可重定向文件,输出一个全链接的可执行目标文件。

目标文件有三种形式:

1. 可重定向目标文件:包含二进制代码和数据,可以在编译时和其他可重定向目标文件一起,得到可执行文件。

2. 可执行目标文件:包含二进制代码和数据,可以直接拷贝进内存进行执行。

3. 共享目标文件:一种特殊的重定向文件,可以被加载进内存,并在加载和运行期间进行动态链接。

编译器和汇编器产生可重定向目标文件(包括共享目标文件)

链接器产生可执行文件。

链接器的主要任务:

1. 符号解析

2. 重定向

ELF(Executable and Linkable format)

ELF header: 16 bytes 序列 描述字长和系统中产生字节的顺序(大端还是小端)。其他的信息还有header的大小,目标文件的类型,机器类型,section header table的偏移及其中包含的entries的数量和大小。

对于每个section, section header table 包含一个固定大小的entry, 其中包含相应section位置和大小的信息。

下面给出一段代码和相应的ELF.

#include<stdio.h>
#include "vector.h"

int x[2] = {1,2};
int y[2] = {3,4};
int z[2];

int main(){
    addvec(x,y,z,2);
    printf("z = [%d,%d]\n",z[0],z[1]);
    return 0;
}

.text: 编译程序的机器码

.rodata: 只读数据。switch的跳转表之类。

.data: 初始化的全局变量和静态变量。

.bss: 未初始化的全局数据,没有实际的空间分配,只起占位作用。

.symtab: 符号表,包含函数和全局变量的信息。

.rel.text: .text中需要在重定向时修改的位置信息。例如,调用外部函数或引用的指令,都需要在重定向时写入相应函数的地址。

rel.data: 例如,一个初始化的全局变量引用一个外部的地址或外部的函数,都需要在重定向时修改。

.debug: 一个进行调试时使用的表,存放局部变量和typedef, 全局变量,程序中引用的变量,原始的c代码的文件。

.line: 原始C代码到机器码的映射。

符号表和符号

连接器的上下文中包含三种符号:

在模块中定义,可以被其他模块使用的全局符号。包含nonstatic C function 和不带static的全局变量

在模块中引用,但是在外部模块中定义的符号。通常是带extern关键字的函数或变量。

符号解析

符号解析分两种,一种是局部符号解析,只需在对应的模块中进行;另一种是全局符号的解析。例如上面函数用到的printf()就属于全局符号。这些符号往往是undef的类型。因为编译器在产生符号表的过程中如果遇到相应模块中未定义的符号,就会假设该符号在别的模块中定义,并把解析的任务交给链接器进行。

在解析全局符号的过程中,可能会遇到符号在多个目标文件中定义的问题,链接器的处理遵循下面的准则:

rule 1: multiple strong symbols are not allowed.

rule 2: given a strong symbol and multiple weak symbols, choose the strong symbol.

rule 3: given multiple weak symbols, choose any of the weak symbols.

下面举例阐明rule 1:

//fool.c
int main(){
    return 0;
}
//bar1.c
int main(){
    return 0;
}

编译结果如下:

下面是rule2的例子:

//foo2.c
#include<stdio.h>
void foo(void);
int x = 15213;
int main(){
    f();
    printf("x = %d\n",x);
    return 0;
}
//bar2.c
int x;
void f(){
    x = 15212;
}

运行结果如下:

rule 3举例:

//foo3.c
#include<stdio.h>
void f(void);
int x = 15213;
int y = 15212;
int main(){
    f();
    printf("x = 0x%x y = 0x%x \n",x,y);
    return 0;
}
//bar3.c
double x;
void f(){
    x = -0.0;
}

运行结果如下:

解释:在foo3.c符号表中,x 和 y是.data section中连续的两个变量。在调用f()的时候,由于有rule3, 所以操作的变量是foo3.c中的x, 而非bar3.c中的x. 因此,复制会导致将x 和 y的数据域覆盖。

静态链接库

静态链接库的实现如下:

将每一个函数都分别编译成各自名字命名的.o文件,将所有的.o文件打包,放入一个archive的文件中,就是所谓的静态链接库.a形式。

archive中包含各个函数的信息,以便调用时能快速找到相应的模块。

使用静态库进行解析的过程

在编译完main函数后,符号表中会出现很多undef的符号,记为U. D 表示已经定义的符号, E表示一组可重定向的文件, 是链接时需要merge的静态库文件。

执行的顺序如下:

按照给出的静态库的顺序扫描;

扫描一个静态库,看有没有匹配U中的符号,如果匹配, 则将该符号放入D中,修改相关信息。将链接的该块的无法解析的符号加入U中。

这样存在的最大问题是循环依赖。因此链接的顺序是很重要的。

时间: 2024-11-05 16:01:55

链接器相关问题的相关文章

笔记:LNK2001不代表链接器真的需要链接相关符号

环境:VS2008 我们都知道,链接器在生成可执行程序时,会忽略那些没有用到的符号.但是昨天遇到一个链接问题,看起来与这条基本策略并不相符.首先看一个静态链接库的结构: lib | |---------------------| a.cpp b.cpp | | |-------| |-----------| fun1 fun2 fun3 fun4 | ↑___________| ↓ GetModuleFileNameEx(psapi.lib) 这个库里只存在两个依赖:b.cpp中的fun3依赖于

链接器

作者:左少华 时间:2015-05-24 转载请注明出处: http://blog.csdn.net/shaohuazuo/article/details/45957971 连接器的功能 链接器是将各种代码和数据部分收集起来并合成一个单一文件的过程, 这个文件可以被加载到存储器中执行. 链接器的执行时机 可以执行于编译时,也就是在源代码被翻译成机器代码的时候. 可以执行于加载时,也就是程序被加载器加载到存储器,并执行时. 可以执行于运行时,由应用程序来执行. 链接是通过链接器程序自动执行的. 为

C编译器、链接器、加载器详解

摘自http://blog.csdn.net/zzxian/article/details/16820035 C编译器.链接器.加载器详解 一.概述 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标文件.操作系统的启动代码和用到的库文件进行组织形成最终生成可加载.可执行代码的过程. 过程图解如下: 预处理器:将.c 文件转化成 .i文件,使用的gcc命令是

C++链接器工具错误:LNK2001, LNK2019(转载)

这是归属于链接器工具错误 这一类. 无法解析的外部符号“symbol” 代码引用了链接器无法在库和对象文件中找到的内容(如函数.变量或标签). 可能的原因 代码请求的内容不存在(例如,符号拼写错误或使用错误的大小写). 代码请求的内容错误(使用的是混合版本的库,一些库来自产品的一个版本,而其他则来自另一个版本). 该错误信息之后为致命错误 LNK1120. 具体原因 代码问题 如果 LNK2001 诊断文本报告 __check_commonlanguageruntime_version 是无法解

C++编译器与链接器工作原理

http://blog.csdn.net/success041000/article/details/6714195 1. 几个概念 1)编译:把源文件中的源代码翻译成机器语言,保存到目标文件中.如果编译通过,就会把CPP转换成OBJ文件. 2)编译单元:根据C++标准,每一个CPP文件就是一个编译单元.每个编译单元之间是相互独立并且互相不可知. 3)目标文件:编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据. 还有一些其他信息,如未解决符号表,导出符号表和地址重定向表等.目标文

链接器简介

1.可执行程序产生流程 2.链接器 链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程.这个文件可以被加载到存储器并执行. 链接可执行与编译时,也可执行与加载时,甚至执行与运行时. 链接器的主要工作就是: (1)符号解析.目标文件定义和引用的符号,将每个符号引用刚好和一个符号定义联系起来. (2)重定位.编译器和汇编器生成从地址0开始的代码和数据节.链接器将每个符号定义与一个存储器位置联系起来, 然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节. 链接器可分

C++之编译器与链接器工作原理

http://www.cnblogs.com/kunhu/p/3629636.html 原文来自:http://blog.sina.com.cn/s/blog_5f8817250100i3oz.html 这里并没不是讨论大学课程中所学的<编译原理>,只是写一些我自己对C++编译器及链接器的工作原理的理解和看法吧,以我的水平,还达不到讲解编译原理(这个很复杂,大学时几乎没学明白). 要明白的几个概念: 1.编译:编译器对源文件进行编译,就是把源文件中的文本形式存在的源代码翻译成机器语言形式的目标

自己动手写编译器、链接器内容简介

<自己动手写编译器.链接器> 本书讲述了一个真实编译器的开发过程,源语言是以C语言为蓝本,进行适当简化定义的一门新语言,称之为SC语言(简化的C语言),目标语言是大家熟悉的Intel x86机器语言.在本书中,读者将看到从SC语言定义,到SCC编译器开发的完整过程.本书介绍的SCC编译器,没有借助Lex与Yacc这些编译器自动生成工具,纯手工编写而成,更便于学习和理解.为了生成可以直接运行EXE文件,本书还实现了一个链接器.读完本书读者将知道一门全新的语言如何定义,一个真实的编译器.链接器如何

链接器下——链接器实战

链接器实战 1目标 模拟嵌入式开发,编写一个"体积受限"的可执行程序,通过makefile 完成编译, 运行后打印"Hello BT" 2分析过程 3解决方案 1.通过内嵌汇编自定义打印函数和退出函数(INT 80H)2.通过链接脚本自定入口函数(不依赖任何库和gcc内置功能)3.删除可执行程序中的无用信息(无用段信息.调试信息.等) 4打印函数设计 void print(const char* s, int l) { asm volatile ( "mo