StephenChan's Tech Space 潜心修炼

7Jun/100

动态链接

动态链接的基本思想是把程序按照模块拆分成各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接那样把所有的程序模块都链接成一个单独的可执行文件。动态链接涉及运行时的链接及多个文件的装载,必需要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布会比静态链接情况下更为复杂,还有一些存储管理、内存共享、进程线程等机制在动态链接下也会有一些微妙的变化。在Linux系统中,ELF动态链接文件被称为“动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件,而在Windows系统中,动态链接文件被称为动态链接库(Dynamical Linking Library),即平时见到的以“.dll”为扩展名的文件。

动态链接库的特点与优势

把函数库推迟到程序运行时加载的好处有几个:

  • 可以实现进程之间的资源共享。就是说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝。如果有,则让其共享那一拷贝(共享代码段,数据码各自独立一份);只有没有才链接载入。这种模式虽然会带来一些”动态链接“额外的开销,但却大大地节省了系统的内存资源,通过一定的优化,与静态链接相比,性能损失大约在5%以下。
  • 程序升级变得简单。用户只需要升级动态链接库,而无需像静态链接那样重新编译其他原有的代码就可以完成整个程序的升级。
  • 可以使链接载入由程序员在程序代码中控制,如dlopen、dlsym、dlclose等等。
6Jun/100

ELF文件格式说明

ELF文件类型

ELF文件主要分为三种类型:

  • 可重定位文件(Relocatable File)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
  • 可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了exec() 如何创建一个程序的进程映像。
  • 共享目标文件(Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。

ELF文件的数据表示

ELF文件头结构及相关常数被定义在"/usr/include/elf.h"里。ELF目标文件中的所有数据结构都遵从自然大小和对齐规则。如果必要,数据结构可以包含显式的补齐,例如为了确保4字节对象按4字节边界对齐。数据对齐同样适用于文件内部。下面为ELF中常用的数据格式:

名称 大小 对齐 描述
Elf32_Addr 4 4 无符号程序地址
Elf32_Half 2 2 无符号短整型
Elf32_Off 4 4 无符号偏移地址
Elf32_Sword 4 4 有符号整型
Elf32_Word 4 4 有符号整型

ELF除了32位版还有64位版本,数据类型的名称和大小也相应地变化(Elf64_Addr...)。

5Jun/100

可执行文件的装载

对于32位平台下的4G的虚拟空间,在默认情况下,Linux操作系统将进程的虚拟地址空间划分为两部分,其中操作系统本身用去了一部分:从地址0xC0000000到0xFFFFFFFF,共1GB。剩下的从0x00000000地址开始到0xBFFFFFFF共3GB的空间都是进程使用的。而对于Windows操作系统来说,它的进程虚拟地址空间划分是操作系统占用2GB。

覆盖装入(Overlay)和页映射(Paging)是两种很类型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中。

覆盖载入在没有发明虚拟存储之前使用比较广泛,现在已经几乎被淘汰了。覆盖载入的方法是把挖掘内存潜力的任务交给了程序员,即是程序员在编写代码的同时要自己手动对程序模块进行内存和磁盘的切换,使不需要的代码切换出磁盘,腾出空间给需要执行的代码块。

与覆盖载入的原理相似,页映射也不是一下子就把程序的所有数据和指令都载入内存,而是将内存和所有磁盘中的数据和指令按照“页(Page)”为单位划分分成若干个页,以后所有的装载和操作的单位就是页。以目前的情况,硬件规定的页大小有4096字节、8192字节、2MB、4MB等。其实这个是属于操作系统存储管理的一部分,内存页的切换会使用先进先出、最少使用等等算法。

可执行文件的装载

可执行文件的装载和进程的建立大概经历3个步骤:

  • 首先是创建虚拟地址空间。一个虚拟空间是由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录(Page Directory)就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页错误的时候再进行设置。
  • 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系。上面一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该“缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪个位置。
  • 将CPU指令寄存器设置成可执行文件入口,启动运行。这一步涉及了内核堆栈和用户堆栈的切换、CPU运行权限的切换等等。跳转到入口指令其实就是在ELF文件头中e_entry记录的可执行文件入口地址。

3Jun/100

静态链接

链接有哪些过程?

假如有2个源文件如下,这两个源文件生成的目标文件链接成可执行文件后,涉及到空间和地址的分配、符号解析和重地位等等过程。

/* a.c */
extern int shared;

int main()
{
    int a = 100;
    swap( &a, &shared);
}
/* b.c */
int shared = 1;

void swap( int* a, int* b)
{
    *a ^= *b ^= *a ^= *b;
}

在a.c中引用到了b.c中的变量"shared"和函数"swap"。使用gcc编译成目标文件a.o和b.o之后,接着就需要将两个目标文件链接成一个可执行文件。

gcc -c a.c b.c

一般来说,链接过程分为两步:

31May/100

ELF文件结构简述

现在PC平台流行的可执行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是COFF(Common File Format)格式的变种。目标文件就是源代码编译后但未进行链接的那些中间文件(Windows的.obj和Linux下的.o),它跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。

Linux的.o/Windows的.obj、/bin/bash或Windows的exe、Linux的.so/Windows的.dll分别是什么文件?

  1. Linux的.o和Windows的.obj称为可重定位文件(Relocatable File),这类文件包含了代码和数据,可以被用来链接成可执行文件共享目标文件静态链接库也可以归为这一类。
  2. /bin/bash和Windows的.exe是可以直接执行的程序,它的代表就是ELF可执行文件,它们一般是没有扩展名的。
  3. Linux的.so和Windows的.dll称为共享目标文件,这种文件也包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重位文件和共享目标文件链接、产生新的目标文件。第二种是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像的一部分来执行。

ELF目标文件的格式是怎样?

elf struct

Page 1 of 212