不清楚链接器的工作

时间:2009-12-31 18:51:56

标签: compiler-construction linker

我在Windows上使用C语言。此问题以前是What happens to identifiers in a program?的一部分。我打破它减少没有。问题这是一个独立的查询(不依赖于前一个问题)

如果没有要链接的东西(即..我没有使用任何库。我知道它没有任何用处。)链接器会更改汇编程序的目标代码输出吗?如果是这样,它会改变什么?

我听说LINKER也做了一些内存映射的操作。我不明白怎么做。该程序没有运行,它刚刚处于制造阶段。链接器如何映射到内存?它看起来怎么样? LINKER的功能是什么?

当人们提到“搬迁”,“地址绑定”时。我真的不明白他们的意思。它是什么&它的目的是什么?

有些调试器显示如下信息: 调用堆栈:0xfffef32,0xf3234fe等。它在运行时对吗?或者是链接器所谓的“内存映射”的内存地址?

当人们提到symbolssymbol table之类的内容时。它们是指标识符(变量名,常量名,函数名)吗?

我在互联网上搜索信息但找不到任何有用的信息。可能我不知道该搜索什么。我不想读这本书的大书。但是如果有任何文章,教程清楚概念。这也会有所帮助。

我是一名新手程序员。因此,您可以用简单但技术性的术语来解释。

4 个答案:

答案 0 :(得分:4)

编译源文件时,通常会将编译器/汇编器划分为多个部分。作为一个假设的例子,假设使用了以下部分:

  • .text - 包含所有可执行代码
  • .const - 包含常量数据
  • .data - 包含读/写初始化数据
  • .bss - 包含读/写未初始化数据

在单个源文件中,编译器/汇编器将适当的内容分配给适当的部分,并在从零开始的部分中给出使用偏移的符号。

例如:

int i;
const j = 3;
int k = 4;
int l;
int main()
{
return 1;
}

这可能会产生以下符号表:

Symbol Section Offset
i      .bss    0
j      .const  0
k      .data   0
l      .bss    4
main   .text   0

在目标文件中,除符号表外,还可以保留每个部分中的数据。在这个例子中,.text部分将包含“返回1”的目标代码,const部分将包含3,数据部分将包含4. .bss部分不需要在目标文件中,因为变量尚未初始化。

链接器可能做的第一件事就是连接输入对象文件的所有部分并相应地调整符号偏移量。

现在我们进入所谓的“重定位”或“地址绑定”。假设在假设系统中,可执行代码从地址0x1000开始。我们还要说程序的数据部分想要在可执行代码之后的偶数页边界处开始。链接器将指定0x1000作为连接的.text部分的基础并调整所有符号。然后.const,.data和.bss部分的基础类似于将它们放在内存中的适当位置。

有时在一个部分中有符号引用。这些引用必须由链接器更新,以反映所引用符号的最终位置。目标文件可能包含“重定位记录”,看起来像

section offset symbol
.text   0x1234 foo

链接器将转到每个部分中的每个偏移量并更新其中的值以反映最终符号值。

完成所有这些操作后,生成的“绝对”对象文件可以加载到内存中(当然是在适当的位置!)并执行。

答案 1 :(得分:1)

我将与C合作进行此次讨论。

C程序很少引用至少某些库函数;因此,即使您的代码只在一个模块(文件)中,通常也会引用库函数。在程序的编译形式中,这些引用位于外部引用表中,即文本名称与程序中要引用这些外部地址的位置一起出现的表。

链接器的工作是将您的程序与单个文件中的任何其他模块连接在一起,然后将一个模块中的外部定义与另一个模块中的外部引用相匹配,即修补与应用程序的所有交叉引用,以便调用命中正确的地址。

即使您没有引用任何外部模块,链接也可能需要将代码中的一些相对引用转换为绝对引用;即一旦它“知道”你的代码将在文件中的哪个位置,它就可以为事物分配正确的最终地址。

答案 2 :(得分:1)

不是答案,只是一个建议:购买"Linkers and Loaders",阅读几次。这非常有帮助。

答案 3 :(得分:1)

  

如果没有要链接的东西(即..我没有使用任何库。我知道它没有任何用处。)链接器会更改汇编程序的目标代码输出吗?如果是这样,它会改变什么?

它始终链接一些初始化代码。你可以尝试这个,写一个空程序并链接它,然后使用objdump -d来反汇编它。

  

我听说LINKER也做了一些内存映射的操作。我不明白怎么做。该程序没有运行,它刚刚处于制造阶段。链接器如何映射到内存?它看起来怎么样? LINKER的功能是什么?

每个系统都有一个可执行程序必须遵循的内存布局。它指定程序的不同部分的位置(至少代码,初始化数据,数据初始化为零)。链接器必须根据这些规则生成可执行文件,这些规则在系统之间变化,例如, Windows和Linux。在嵌入式系统上,它变得更加有趣,程序通常位于只读存储器(Flash)中,数据存储在RAM中,根据微控制器的类型,不同类型的存储器有固定的地址范围。

  

当人们提到“搬迁”,“地址绑定”时。我真的不明白他们的意思。它是什么&它的目的是什么?

绑定通常意味着为名称赋值,在本例中为函数或全局变量的符号的地址。

对于重定位,通常将多个目标文件链接在一起,并且每个目标文件将其地址指定为相对于其开头的偏移量。将它们放在一起时,每个都有自己的地址范围,链接器通过将偏移量映射到地址范围来计算符号的地址。这称为重定位。

  

一些调试器显示如下信息:调用堆栈:0xfffef32,0xf3234fe等。它在运行时是否正确?或者是链接器所谓的“内存映射”的内存地址?

0xfffef32将是堆栈上的典型地址,因为堆栈通常放在内存的顶部并向下增长。堆栈用于返回地址,局部变量和实际函数参数。它们是本地的并存储在相对于堆栈指针的地址处,因此它们通常不由链接器处理,而是编译器已经知道要使用的偏移量并将它们放入汇编代码中。

  

当人们提到符号或符号表之类的东西时。它们是指标识符(变量名,常量名,函数名)吗?

符号表是将符号映射到值(数字,偏移量,地址)的表。您的标识符有一些符号,但其他用途也有更多符号。您的标识符可能会被或多或少地修改为符号,主要是为了防止名称冲突(例如“_”的前缀)。

链接器有一个选项--print-map来打印符号表。如果使用gcc进行链接,可以使用-Wl, - print-map。

如果你喜欢这种低级技术,你应该看看嵌入式编程,即编程用于各种电子设备的微控制器。对于像Windows这样的桌面系统,您通常不需要查看此类详细信息。