From bd85980cc49a61bce25d964d3456f821027ad35c Mon Sep 17 00:00:00 2001 From: zmj1316 Date: Sat, 15 Oct 2016 11:32:23 +0800 Subject: [PATCH] =?UTF-8?q?13.2=20=E6=A0=A1=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Misc/linkers.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Misc/linkers.md b/Misc/linkers.md index e05dabc..ea7dd0f 100644 --- a/Misc/linkers.md +++ b/Misc/linkers.md @@ -11,11 +11,11 @@ If we open the `Linker` page on Wikipedia, we will see following definition: >In computer science, a linker or link editor is a computer program that takes one or more object files generated by a compiler and combines them into a single executable file, library file, or another object file. ->在计算机科学中,链接器(英语:Linker),又译为链接器、连结器,是一个程序,能够将一个或多个由编译器或汇编器生成的目标文件链接为一个可执行文件、库或另一个目标文件。 +>在计算机科学中,链接器(英语:Linker),是一个程序,能够将一个或多个由编译器或汇编器生成的目标文件链接为一个可执行文件、库或另一个目标文件。 If you've written at least one program on C in your life, you will have seen files with the `*.o` extension. These files are [object files](https://en.wikipedia.org/wiki/Object_file). Object files are blocks of machine code and data with placeholder addresses that reference data and functions in other object files or libraries, as well as a list of its own functions and data. The main purpose of the linker is collect/handle the code and data of each object file, turning it into the final executable file or library. In this post we will try to go through all aspects of this process. Let's start. -如果你曾经用 C 写过至少一个程序,那你就已经见过以 `*.o` 扩展名结尾的文件了。这些文件是[目标文件](https://en.wikipedia.org/wiki/Object_file)。目标文件是一块块的机器码和数据,其数据包含了引用其他目标文件或库的数据和函数的占位符地址,也包括其自身的函数和数据列表。链接器的主要目的就是收集/处理每一个目标文件的代码和数据,将它们转成最终的可执行文件或者库。在这篇文章里,我们会试着研究一遍这个流程的各个方面。开始吧。 +如果你曾经用 C 写过至少一个程序,那你就已经见过以 `*.o` 扩展名结尾的文件了。这些文件是[目标文件](https://en.wikipedia.org/wiki/Object_file)。目标文件是一块块的机器码和数据,其数据包含了引用其他目标文件或库的数据和函数的占位符地址,也包括其自身的函数列表和数据。链接器的主要目的就是收集/处理每一个目标文件的代码和数据,将其转成最终的可执行文件或者库。在这篇文章里,我们将会试着研究一遍这个流程的各个方面。让我们开始吧。 链接流程 --------------- @@ -108,9 +108,9 @@ The `nm` util allows us to see the list of symbols from the given object file. I * `main` - the main function; * `printf` - the function from the [glibc](https://en.wikipedia.org/wiki/GNU_C_Library) library. `main.c` does not know anything about it for now either. -* `factorial` - 在 `lib.c` 文件中定义的阶乘函数。因为我们只编译了 `main.c`,所以其不知道任何有关 `lib.c` 文件的事; +* `factorial` - 在 `lib.c` 文件中定义的阶乘函数。因为我们只编译了 `main.c`,所以其不知道任何有关 `lib.c` 文件的信息; * `main` - 主函数; -* `printf` - 来自 [glibc](https://en.wikipedia.org/wiki/GNU_C_Library) 库的函数。 `main.c` 同样不知道任何与其相关的事。 +* `printf` - 来自 [glibc](https://en.wikipedia.org/wiki/GNU_C_Library) 库的函数。 `main.c` 同样不知道任何与其相关的信息。 What can we understand from the output of `nm` so far? The `main.o` object file contains the local symbol `main` at address `0000000000000000` (it will be filled with correct address after is is linked), and two unresolved symbols. We can see all of this information in the disassembly output of the `main.o` object file: @@ -141,7 +141,7 @@ Disassembly of section .text: Here we are interested only in the two `callq` operations. The two `callq` operations contain `linker stubs`, or the function name and offset from it to the next instruction. These stubs will be updated to the real addresses of the functions. We can see these functions' names with in the following `objdump` output: -这里我们只关注两个 `callq` 操作。这两个 `callq` 操作包含了 `链接器存根`,或者函数的名称和其相对当前的下一条指令的偏移。这些存根将会被更新到函数的真实地址。我们可以在下面的 `objdump` 输出看到这些函数的名字: +这里我们只关注两个 `callq` 操作。这两个 `callq` 操作包含了 `链接器存根`,或者说函数的名称和其相对当前的下一条指令的偏移。这些存根将会被更新到函数的真实地址。我们可以在下面的 `objdump` 输出看到这些函数的名字: ``` $ objdump -S -r main.o @@ -215,7 +215,7 @@ factorial: file format elf64-x86-64 As we can see in the previous output, the address of the `main` function is `0x0000000000400506`. Why it does not start from `0x0`? You may already know that standard C programs are linked with the `glibc` C standard library (assuming the `-nostdlib` was not passed to the `gcc`). The compiled code for a program includes constructor functions to initialize data in the program when the program is started. These functions need to be called before the program is started, or in another words before the `main` function is called. To make the initialization and termination functions work, the compiler must output something in the assembler code to cause those functions to be called at the appropriate time. Execution of this program will start from the code placed in the special `.init` section. We can see this in the beginning of the objdump output: -在前面的输出中我们可以看到, `main` 函数的地址是 `0x0000000000400506`。为什么它不是从 `0x0` 开始的呢?你可能已经知道标准 C 程序是使用 `glibc` 的 C 标准库链接的(假设参数 `-nostdlib` 没有被传给 `gcc` )。编译后的程序代码包含了用于在程序启动时初始化程序中数据的构造函数。这些函数需要在程序启动前被调用,或者说在 `main` 函数之前被调用。为了让初始化和终止函数起作用,编译器必须在汇编代码中输出一些让这些函数在正确时间被调用的代码。执行这个程序将会启动位于特殊的 `.init` 段的代码。我们可以从以下的 objdump 输出中看出: +在前面的输出中我们可以看到, `main` 函数的地址是 `0x0000000000400506`。为什么它不是从 `0x0` 开始的呢?你可能已经知道标准 C 程序是使用 `glibc` 的 C 标准库链接的(假设参数 `-nostdlib` 没有被传给 `gcc` )。编译后的程序代码包含了用于在程序启动时初始化程序中数据的构造函数。这些函数需要在程序启动前被调用,或者说在 `main` 函数之前被调用。为了让初始化和终止函数起作用,编译器必须在汇编代码中输出一些让这些函数在正确的时机被调用的代码。执行这个程序将会启动位于特殊的 `.init` 段的代码。我们可以从以下的 objdump 输出中看出: ``` objdump -S factorial | less @@ -240,7 +240,7 @@ $ readelf -d factorial | grep \(INIT\) So, the address of the `main` function is `0000000000400506` and is offset from the `.init` section. As we can see from the output, the address of the `factorial` function is `0x0000000000400537` and binary code for the call of the `factorial` function now is `e8 18 00 00 00`. We already know that `e8` is opcode for the `call` instruction, the next `18 00 00 00` (note that address represented as little endian for `x86_64`, so it is `00 00 00 18`) is the offset from the `callq` to the `factorial` function: -所以, `main` 函数的地址是 `0000000000400506` ,为相对于 `.init` 段的偏移地址。我们可以从输出中看出,`factorial` 函数的地址是 `0x0000000000400537` ,并且现在调用 `factorial` 函数的二进制代码是 `e8 18 00 00 00`。我们已经知道 `e8` 是 `call` 指令的操作码,接下来的 `18 00 00 00` (注意 `x86_64`中地址是小头存储的,所以是 `00 00 00 18` )是从 `callq` 到 `factorial` 函数的偏移。 +所以, `main` 函数的地址是 `0000000000400506` ,这是相对于 `.init` 段的偏移地址。我们可以从输出中看出,`factorial` 函数的地址是 `0x0000000000400537` ,并且现在调用 `factorial` 函数的二进制代码是 `e8 18 00 00 00`。我们已经知道 `e8` 是 `call` 指令的操作码,接下来的 `18 00 00 00` (注意 `x86_64`中地址是小头存储的,所以是 `00 00 00 18` )是从 `callq` 到 `factorial` 函数的偏移。 ```python >>> hex(0x40051a + 0x18 + 0x5) == hex(0x400537) @@ -257,7 +257,7 @@ What we have seen in this section is the `relocation` process. This process assi Ok, now that we know a little about linkers and relocation it is time to learn more about linkers by linking our object files. -好了,现在我们知道了一点关于链接器和重定位的知识,是时候通过链接我们的目标文件来来学习更多关于链接器的知识了。 +好了,现在我们知道了一点关于链接器和重定位的知识,是时候通过链接我们的目标文件来学习更多关于链接器的知识了。 GNU 链接器 ----------------- @@ -325,7 +325,7 @@ Here we can see two problems: First of all let's try to understand what is this `_start` entry symbol that appears to be required for our program to run? When I started to learn programming I learned that the `main` function is the entry point of the program. I think you learned this too :) But it actually isn't the entry point, it's `_start` instead. The `_start` symbol is defined in the `crt1.o` object file. We can find it with the following command: -首先,让我们尝试理解好像是我们程序运行所需要的 `_start` 入口符号是什么。当我开始学习编程时,我知道了 `main` 函数是程序的入口点。我认为你们也是如此认为的 :) 但实际上这不是入口点,`_start` 才是。 `_start` 符号被 `crt1.0` 所定义。我们可以用如下指令发现它: +首先,让我们尝试理解看起来是我们程序运行所需要的 `_start` 入口符号是什么。当我开始学习编程时,我知道了 `main` 函数是程序的入口点。我认为你们也是如此认为的 :) 但实际上这不是入口点,`_start` 符号才是。 `_start` 符号被定义在 `crt1.0` 文件。我们可以用如下指令找到它: ``` $ objdump -S /usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crt1.o @@ -387,7 +387,7 @@ Here we pass address of the entry point to the `.init` and `.fini` section that define the function prologs/epilogs for the .init and .fini sections (with the `_init` and `_fini` symbols respectively). -定义了 .init 和 .fini 段的开端和尾声(分别为符号 `_init` 和 `_fini` )。 +定义了 .init 和 .fini 段的函数序言和尾声(分别为符号 `_init` 和 `_fini` )。 The `crtn.o` object file contains these `.init` and `.fini` sections: @@ -409,7 +409,7 @@ Disassembly of section .fini: And the `crti.o` object file contains the `_init` and `_fini` symbols. Let's try to link again with these two object files: -且 `crti.o` 目标文件包含了符号 `_init` 和 `_fini`。让我们再次尝试链接这两个目标文件: + `crti.o` 目标文件则包含了 `_init` 和 `_fini` 符号。让我们再次尝试链接这两个目标文件: ``` $ ld \ @@ -442,7 +442,7 @@ bash: ./factorial: No such file or directory What's the problem here? Let's look on the executable file with the [readelf](https://sourceware.org/binutils/docs/binutils/readelf.html) util: -这里除了什么问题?让我们用 [readelf](https://sourceware.org/binutils/docs/binutils/readelf.html) 工具看看这个可执行文件: +这里出了什么问题?让我们用 [readelf](https://sourceware.org/binutils/docs/binutils/readelf.html) 工具看看这个可执行文件: ``` $ readelf -l factorial @@ -535,11 +535,11 @@ and after this we link object files of our program with the needed system object As I already wrote and as you can see in the manual of the `GNU linker`, it has big set of the command line options. We've seen a couple of options in this post: `-o ` - that tells `ld` to produce an output file called `output` as the result of linking, `-l` that adds the archive or object file specified by the name, `-dynamic-linker` that specifies the name of the dynamic linker. Of course `ld` supports much more command line options, let's look at some of them. -正如我之前所说,你也可以从 `GNU linker` 的指南看到,其拥有大量的命令行选项。我们已经在这篇文章见到一些: `-o ` - 告诉 `ld` 将链接结果输出成一个叫做 `output` 的文件,`-l` - 通过文件名添加指定存档或者目标文件,`-dynamic-linker` 通过名字指定动态链接器。当然, `ld` 支持更多选项,让我们看看其中的一些。 +正如我之前所说,你也可以从 `GNU linker` 的指南看到,其拥有大量的命令行选项。我们已经在这篇文章见到一些: `-o ` - 告诉 `ld` 将链接结果输出成一个叫做 `output` 的文件,`-l` - 通过文件名添加指定存档或者目标文件,`-dynamic-linker` 通过名字指定动态链接器。当然, `ld` 支持更多选项,让我们看看其中的一部分。 The first useful command line option is `@file`. In this case the `file` specifies filename where command line options will be read. For example we can create file with the name `linker.ld`, put there our command line arguments from the previous example and execute it with: -第一个实用的选项是 `@file` 。在这里 `file` 指定了命令行选项将读取的文件名。比如我们可以创建一个叫做 `linker.ld` 的文件,把我们上一个例子里面的命令行参数放进去然后执行: +第一个实用的选项是 `@file` 。在这里 `file` 指定了从哪个文件读取命令行选项。比如我们可以创建一个叫做 `linker.ld` 的文件,把我们上一个例子里面的命令行参数放进去然后执行: ``` $ ld @linker.ld @@ -547,7 +547,7 @@ $ ld @linker.ld The next command line option is `-b` or `--format`. This command line option specifies format of the input object files `ELF`, `DJGPP/COFF` and etc. There is a command line option for the same purpose but for the output file: `--oformat=output-format`. -下一个命令行选项是 `-b` 或 `--format`。这个命令行选项指定了输入的目标文件的格式是 `ELF`, `DJGPP/COFF` 等。针对输出文件也有相同功能的选项 `--oformat=output-format` 。 +下一个命令行选项是 `-b` 或 `--format`。这个命令行选项指定了输入的目标文件的格式如 `ELF`, `DJGPP/COFF` 等等。针对输出文件也有相同功能的选项 `--oformat=output-format` 。 The next command line option is `--defsym`. Full format of this command line option is the `--defsym=symbol=expression`. It allows to create global symbol in the output file containing the absolute address given by expression. We can find following case where this command line option can be useful: in the Linux kernel source code and more precisely in the Makefile that is related to the kernel decompression for the ARM architecture - [arch/arm/boot/compressed/Makefile](https://github.com/torvalds/linux/blob/master/arch/arm/boot/compressed/Makefile), we can find following definition: @@ -599,7 +599,7 @@ Of course the `GNU linker` support standard command line options: `--help` and ` As I wrote previously, `ld` has support for its own language. It accepts Linker Command Language files written in a superset of AT&T's Link Editor Command Language syntax, to provide explicit and total control over the linking process. Let's look on its details. -如我之前所说, `ld` 支持它自己的语言。它接受由一种 AT&T 链接器控制语法的超集编写的链接器控制语言文件,以提供对链接过程明确且完全的控制。接下来让我们关注其中细节。 +如我之前所说, `ld` 支持它自己的语言。它接受由一种 AT&T 链接器控制语法的超集编写的链接器控制语言文件,以提供对链接过程明确且完整的控制。接下来让我们关注其中细节。 With the linker language we can control: