笔记 —— 《程序员的自我修养:链接,装载和库》 之 装载与动态链接

2019/07/14 Book CS Compiler

笔记 —— 《程序员的自我修养:链接,装载和库》 之 静态链接

正如康斯坦丁·斯坦尼斯拉夫斯基的《演员的自我修养》,作为一个有专业素养的程序员,应该对程序的运行有所了解,知其原理,了其规则。

第六章 —— 可执行文件的装载与进程

装载 –> 简单理解为把程序段和数据段装载进入内存,使得执行路径在执行时,所需要的程序和数据都正确处于内存中。

装载方式

覆盖装入 (程序员负责执行路径装载模块的操作) – 淘汰

页映射 - 装载替换 - 局部性原 理

从操作系统的角度看待,进程最关键的特征是它有自己的独立的虚拟地址空间,这使得它有别于别的进程。

进程的创建 – 创建一个独立的虚拟地址空间 -> 读取可执行文件头,建立映射关系 -> 将CPU指令寄存器设置成可执行文件的入口地址,运行

堆和栈

C - malloc() 分配堆

栈 -> 也叫堆栈,每个线程都有自己的堆栈

第七章 —— 动态链接

静态链接的问题

  1. 空间浪费

无法进行代码共享,内存中同一份代码存在多处。

  1. 更新困难

一处模块更新,整个链接程序都要更新

动态链接

核心思想 模块拆分 + 链接过程推迟到运行时 + 分模块链接

优点: 代码共享 + 运行时动态加载程序模块(此即为插件) —> 只需要规定好程序的接口,他人即可按照这种接口标准来编写符合接口要求的动态链接文件

DLL HELL: 早期 Windows,接口不兼容导致的模块更新后程序报错 —> 缺少一种有效的共享库版本管理机制

Linx - .so 文件 动态共享对象

Windows - .dll 动态链接库

共享对象最终在进程虚拟空间中的地址,在编译期是不确定的,在装载时才进行分配计算。

1.- gcc - shared 装载时重定位– 可以解决动态模块中有绝对地址引用的问题,但指令无法在多个进程之间共享,使得无法做到代码共享。

2.- gcc -fPIC 地址无关码 ==> 简单理解为把动态模块中需要改变地址的那部分,单独抽离出来放到了数据块,然后在每一个进程里都保留了一份备份。

动态链接器优化:延迟绑定(PLT )- 函数第一次用到时才进行绑定(符号查找、重定位)

动态链接步骤

  1. 启动动态链接器本身

  2. 装载所有需要的共享对象

  3. 重定位 + 初始化

动态链接器

本身也是一个共享对象

它的重定位工作由自己完成。

动态链接器本身不可以依赖任何其他的共享对象,不可以使用任何库、运行库。 —> 自举启动

显示运行时链接(运行时加载)

第九章 —— Windows下的动态链接

DLL : Windows 下的DLL和EXE本质上都是具有PE格式的二进制文件。DLL的扩展名除了DLL,也可能是.ocx 或者是.CPL

早期16bit的Windows,所有应用程序共享内存空间,没有进程隔离,DLL一旦装载所有程序都可以访问。

从32bit的Windows开始,开始支持进程独立空间。一个DLL在不同的进程空间中拥有不同的私有数据副本。这类似于Linux下的地址无关码处理解决方案。

基地址 / 相对地址

由于Windows中DLL内的代码段和数据段并不是地址无关的,而是在ImageBase中指定了基地址,后续优先使用这个入口地址,再考虑换用其他地址,并进行rebase,调整其他地址。

装载时重定位 - 调整所有的相对地址。

exe默认不支持装载时重定位,因为它一般都是进程启动时第一个被装载的程序,也就是进程虚拟空间基本上应该都是空的,但DLL就需要支持装载时重定位了。

这种策略比Linxu的地址无关码要快一些,但是空间上更加浪费,空间换时间。

DLL中的数据段有两个,一个是可以在进程之间共享的数据段,一个是私有的数据段。(但共享数据段应该尽可能避免使用,因为并不安全。其他低权限危险进程可以破坏该段共享数据,从而影响其他进程)

程序使用DLL的过程其实就是引用DLL中的导出函数和符号的过程,这个过程其实就是导入。

.lib –> 导入库,描述.dll中的导出符号,供.obj文件链接.dll文件时使用,最终生成.exe文件。

定义导出符号/函数,使用_declspec(dllexport) 或 .def 文件单独声明。

WINAPI - 以DLL的形式提供,其中出现大量的符号导出,供外部exe使用,这些都是使用.def进行声明,方便进行命名管理。

Windows的系统调用很多由DLL提供,所以系统单独划分出一大块区域,用于映射常用的系统DLL,从而做到在使用时不需要重新进行装载时重定位基址。

DLL绑定 - 导出函数地址的记录 (时间戳验证更新)

COM - Component Object Model - 组件对象模型,用来解决程序开发过程中迭代产生的兼容性问题。DLL若使用C++编写,在升级覆盖上会存在诸多问题,因为C++标准只规定了语言层面的限制,而对二进制级别的却没有任何限制。 ==> 《COM本质论》

DLL HELL

  1. 旧版本的dll替代了新版本的dll引起
  2. 新版本的dll无意发生改变
  3. 新版本的dll含有bug

解决

  1. 静态链接 - 编译链接期链接,避免运行期依赖dll
  2. 防止DLL覆盖 (Windows File Protection)
  3. 避免DLL冲突 - 不同程序依赖不同版本的DLL ,则各自拥有dll副本
  4. .NET 下,一个Assembly(程序集)包含两种类型,应用程序程序(exe)集 和 库程序(dll)集。则一个程序集需要一个清单文件,即Manifest文件,描述程序集的名字,版本号和各种资源,也标注了依赖资源的描述。Manifest的形式时XML-like文件,每个DLL都有自己的Manifest文件,每个EXE也都有自己的Manifest文件。 - 从Windows XP开始,才会优先读取这个Manifiest文件。

Windows中的DLL Loader中含有一个SxS Manager (Side by Side Manager),负责利用Manifest中关于dependency的描述,精确查找DLL并进行装载。C:\Windows\WinSxS 这是一个集中的存放SxS查找DLL的目录。

Search

    Table of Contents