Skip to content

[翻译] 简谈编译、汇编、链接和加载 – part VI

Tanslated from: http://www.tenouk.com/ModuleW.html W.10  运行时链接器和共享库的加载 使用共享库的程序启动时或这一个程序请求动态加载一个共享对象时会触发运行时链接器。因此符号的解析可以在下面两个过程中的任一个完成。 1. 加载时动态链接。程序被从磁盘上加载到内存中,其中未解析的引用被列出,加载器找到所有需要的外部符号,把所有对这些符号的引用更改为相对于程序基地址的内存地址。 2. 运行时动态链接。程序被从磁盘上加载到内存中,其中未解析的引用被原样保留。第一次对这些符号的访问是无效的,从而引发一个software trap。运行时动态链接器侦测到此次trap的原因并找到所需的外部符号。然后这个被访问的符号被加载到内存并被链接到程序中。 运行时链接器包含在C的运行时库中,它加载共享库(.so文件)时会完成几个任务。动态section提供信息给链接器,告诉它这个库文件链接的其它库文件。它同时提供需要哪些重定位(reloaction)操作以及需要解析的外部符号。运行时链接器会首先加载需要的其它库文件。然后为每个库文件执行重定位操作。有些重定位仅限于库文件本身,而有些则需要运行时链接器解析一个全局符号。对于后一种情况,运行时链接器会搜索一个库文件列表来寻找这个符号。在ELF文件中,会使用hash table来进行符号的查找,所以这个过程是很快的。一旦所有的重定位操作执行完毕,就开始调用在共享库文件的init部分中注册的初始化函数。一些C++的实现使用这个特性来调用全局contructor。   W.11  符号名称解析 当运行时链接器加载了共享库文件,还需要解析库文件中的符号。这里,符号解析的顺序和范围是很重要的。如果一个共享库调用的函数碰巧在几个库文件中都存在,那么搜索这个函数的顺序就至关重要了。这也是为什么操作系统为库文件加载定义了几个选项。所有具有全局范围的对象(可执行文件和库文件)会被保存到一个列表中(全局列表)。默认,任何全局对象的所有符号对加载进来的共享库都是可用的。全局列表开始时包含可执行文件以及程序启动时加载的库文件。   W.12 动态地址翻译 以内存管理的角度看,现代的具有多任务的操作系统通常会实现动态重定位,而非静态。所有程序在地址空间中的布局几乎是相同的。这个动态重定位(以处理器的角度,称为动态地址翻译)产生一下的假象: 1. 每个进程都可以使用以0开始的内存地址,即使其它进程已经在运行了,或者同一个程序被启动多次 2. 地址空间是受保护的 3. 甚至可以让进程认为它拥有比实际物理内存大得多的内存可用(虚拟内存) 动态重定位中,每次引用地址都会动态变化。虚拟内存地址(或者称为逻辑地址)由某个进程产生,而物理地址则是运行时在物理内存中的实际地址。地址的翻译通常由处理器内嵌的内存管理单元(MMU)完成。 虚拟地址是相对于进程而言的。每个进程都相信它的虚拟地址从0开始。进程完全不知道这个地址在物理内存中的实际地址。代码完全以虚拟地址的方式运行。 如果一个虚拟地址超出了进程的(实际)地址范围,MMU可以拒绝翻译此地址,如产生一个segmentation fault。这为所有进程提供了保护。 翻译过程中,我们还可以把进程地地址空间中的一部分在内存和磁盘间移动(称为swapping或paging),这就是为什么进程可以使用比实际物理内存还大的地址空间。 动态重定位可以以下图来表示:   More complete related information can be found…

Read more

[翻译] 简谈编译、汇编、链接和加载 – part V

W.8 运行时数据结构一个进程就是一个运行着的程序。这意味着操作系统已经把程序的可执行文件加载到内存中,安排好了程序对它的参数和环境变量的访问权限,开始执行它的代码。通常一个进程有5个内存区域,如下表所示: Segment 描述 代码 – text segment 通常称为 text segment, 这里是程序的可执行代码指令区.  比如, 当一个程序同时运行多个实例时,Linux/Unix 会尽可能让他们共享text segment。 这个内存区的内容对应可执行文件的 text section. 初始化的数据 – data segment 非零值的静态分配的数据以及全局数据被加载到data segment.  一个程序有多个进程时,每个进程都有自己的data segment。这个内存区的内容对应可执行文件的 data section. 未初始化数据 – bss segment BSS 表示 ‘Block Started by Symbol’(符号开始的区域)。零值的静态分配的数据以及全局数据被加载到bss segment.  一个程序有多个进程时,每个进程都有自己的bss segment。运行时,bss里面的数据会被放入data segment。在可执行文件中,它们被保存在BSS…

Read more

[翻译] 简谈编译、汇编、链接和加载 – part IV

W. 6: 共享对象的使用方式 要理解一个程序如何使用共享对象,让我们首先检查一下可执行文件的格式以及如何让程序开始运行。 w6.1一些ELF格式的细节 ELF(Executable and Linking Format, 可执行与链接格式)是一个二进制格式,用于SVR4 Unix和Linux系统。它是一种在磁盘上存储程序的格式。ELF不仅简化了创建共享文件的过程,也改进了运行时模块的动态加载。 W6.2 ELF块(sections) Linux及其它使用ELF格式的系统在可执行程序中定义了一些”块“。这些块用于为二进制文件提供指令,并且可被查看。重要的函数块包括GOT(Global Offset Table,全局偏移表),存储系统函数的地址,和PLT(Procedure Linking Table过程连接表),用于存储指向GOT的非直接链接;.init/.fini,用于内部初始化和关闭;.ctors/.dtors,用于constructors and destructors. 数据相关的块有.rodata,用于只读数据,.data用于已经初始化的数据,和.bss,用于未初始化的数据。 下面是一个ELF块的不完全列表(从低到高): .init – Startup .text – String .fini – Shutdown .rodata – Read Only .data – Initialized Data .tdata…

Read more

[翻译] 简谈编译、汇编、链接和加载 – part III

W.3 重定位记录(relocation records) 由于不同的对象文件会互相引用对方的代码和/或数据,所以不同的位置,这些不需要在链接时合并。比如,在下面的图中,包含main()的对象文件含有对funct()和printf()函数的调用。把所有对象文件链接在一起后,链接器使用重定位记录来定位那些需要填充的地址。 W.4 符号表 汇编成机器代码的过程会把代码中所有的标签移除,对象文件格式需要把这些信息存放在不同的地方。符号表就是做这个用的。符号表包含了一个名字及其在text和data块的偏移量的列表。反汇编程序可以把这个过程从对象文件或可执行文件翻译回来。   W.5  链接 链接让分开编译成为可能。如下图所示,一个可执行文件可以由多个可独立编译的源文件编译而来,而且每个文件都可以编译为自己的对象文件。 5.1 共享对象 在一个典型的系统上会有多个程序同时运行。每个程序都依赖于很多函数。而这些函数有很多是标准的C语言的库函数,比如printf(), malloc(), strcpy()等,有些则是非标准的或程序员自定义的函数。 如果每个程序都使用标准的C库(library),那就意味着每个程序都要在它的(地址空间)内部有一份C库。这样就会浪费很多资源,也影响效率和性能。 既然对C库文件的需求是共通的,那么如果每个程序都可以共享访问一份C库文件就是一个更好的选择,而不是每个程序都把标准C库文件加载一份到自己的内存地址中。 这个功能也是在链接过程中来实现的。有些对象是在链接的时候链接进来,而有些则是在程序运行时才链接进来(称为延迟链接或动态链接) 5.2 静态链接 静态链接表示一个程序和它需要的库文件在链接阶段就被链接器合并在一起了。这也意味着这个程序和库文件之间的绑定是固定的,且在程序开始运行之前就是已知的。同时也意味着我们无法改变这个结合状态,除非使用一个新版的库文件重新链接这个程序。 静态链接的程序所用的库(对象)一般是一个以.a为扩展名的文件。一个常见的例子就是标准的C库,libc.a。 如果不确定程序运行时能得到正确的库文件版本,或者想要测试一个新版的库文件,还不想把它作为共享库文件安装,这时可以使用静态链接。 对gcc来说,在编译/链接时使用-static选项会得到静态链接的程序 gcc –static filename.c –o filename 静态编译出的可执行文件会大很多,因为所有需要的信息和文件都要被放入可执行文件中。 5.3 动态链接 动态链接表示程序和它使用的特定库文件并非链接阶段就被链接在一起。相反,链接器在可执行文件中放入特定信息,告诉加载器(loader)代码在哪一个共享对象模块中以及应该使用哪一个运行时链接器来找到这个对象文件并将它与程序绑定。这意味着程序和共享对象之间的绑定是在运行时完成,且在程序开始执行之前。 这种类型的程序称为部分绑定可执行文件,因为它并非全部(符号或引用)可解析的。链接器在链接时间并不会将程序中引用的符号和库文件的特定代码结合起来。相反,链接器只是留一些类似这样的信息:这个程序调用的这个共享对象的这个函数,我只是记一下这个函数在哪个共享对象中。现在我要继续下面的工作。 共享对象中的符号会被验证以确认它们确实存在,但不会被合并到程序中。 链接器在可执行程序中存储了外部库文件的地址,用于找到哪些无法在程序中找到的符号。这造成的实际效果就是把(符号与代码之间的)绑定推迟到了程序运行时。 动态链接程序使用的共享对象通常使用.so的扩展名。一个最常见的例子就是标准C库libc.so。 动态链接的好处有: 1….

Read more

[翻译] 简谈编译、汇编、链接和加载 – part II

Tanslated from: http://www.tenouk.com/ModuleW.html W.2  对象文件与可执行文件 源文件经历汇编后会变成对象文件(文件名通常以.o为后缀),再经过链接后,就变成了可执行文件。 对象文件与可执行文件的格式有几种。如Linux上的ELF (Executable and Linking Format) 格式以及Windows上的 COFF (Common Object-File Format). 对象文件格式 描述 a.out Unix上最初的可执行文件格式.  它包含有三个块(sections):  text, data, and bss, 分别对应程序代码, 初始化过的数据以及未初始化的数据.  此格式无法包含排错信息。它能包括的唯一排错信息是被编码为一套带有不同属性的符号的、称为stabs的东西。 COFF COFF格式 (Common Object File Format) 由Unix SVR3引入 .可包含多个块, 每个块都有一个”块头(header)”. 块的数量是有限的.  COFF标准是包含排错信息的,但是也是有限的。此格式没有文件扩展名。 ECOFF COFF的一种变体. …

Read more

关于高可用的系统

在《这多年来我一直在钻研的技术》这篇文章中,我讲述了一下,我这么多年来一直在关注的技术领域,其中我多次提到了工 […]

Read more

[翻译] 简谈编译、汇编、链接和加载 – part I

Tanslated from: http://www.tenouk.com/ModuleW.html 本文详细介绍了一个C/C++程序从源文件到进程的过程。但它仍然只是我的一个缓冲溢出教程的节选。本文试图解释C/C++源码如何经历预处理(pre-processed)、编译(compiled)、链接(linked)以及最后被加载(loaded)为一个运行的进程(process)的过程。文章基于GCC。如果你使用了某个集成的IDE编程环境,那么这些过程就被这些IDE隐藏了。   W.1  编译器(compiler),汇编器(assembler)和链接器(linker) 不管使用何种操作系统或编程工具,C程序的编译过程一般有四步:预处理,编译,汇编和加载,直至最后变为一个单一的可执行文件。 预处理:处理包含文件,条件编译指令以及宏(Macros) 编译:将预处理过的源文件编译成汇编语言文件 汇编:将汇编语言文件转成带有偏移量的机器指令(assembly listing with offsets)。结果存储在一个对象文件中。 链接:将一个或多个对象文件以及库文件组合成一个(通常是可执行的)文件。在这个过程中,链接器需要解决外部对象的引用,为函数或过程调用和变量分配地址,更改代码块和数据以匹配新的地址(relocation). 下表列出了源文件的扩展名及其需要进行的处理: 文件扩展名 描述 file_name.c C源文件,需要进行预处理 file_name.i C源文件。不需要预处理。 file_name.ii C++ 源文件。不需要预处理。 file_name.h C 头文件 (不需要编译或链接。). file_name.cc file_name.cp file_name.cxx file_name.cpp file_name.c++ file_name.C C++源文件。需要进行预处理 。对于 file_name.cxx文件名, xx 必须就是两个小写的字母x 而…

Read more
Sidebar