笔记 —— 《程序员的自我修养:链接,装载和库》 之 静态链接
正如康斯坦丁·斯坦尼斯拉夫斯基的《演员的自我修养》,作为一个有专业素养的程序员,应该对程序的运行有所了解,知其原理,了其规则。
第六章 —— 可执行文件的装载与进程
装载 –> 简单理解为把程序段和数据段装载进入内存,使得执行路径在执行时,所需要的程序和数据都正确处于内存中。
装载方式
覆盖装入 (程序员负责执行路径装载模块的操作) – 淘汰
页映射 - 装载替换 - 局部性原 理
从操作系统的角度看待,进程最关键的特征是它有自己的独立的虚拟地址空间,这使得它有别于别的进程。
进程的创建 – 创建一个独立的虚拟地址空间 -> 读取可执行文件头,建立映射关系 -> 将CPU指令寄存器设置成可执行文件的入口地址,运行
堆和栈
C - malloc() 分配堆
栈 -> 也叫堆栈,每个线程都有自己的堆栈
第七章 —— 动态链接
静态链接的问题
- 空间浪费
无法进行代码共享,内存中同一份代码存在多处。
- 更新困难
一处模块更新,整个链接程序都要更新
动态链接
核心思想: 模块拆分 + 链接过程推迟到运行时 + 分模块链接
优点: 代码共享 + 运行时动态加载程序模块(此即为插件) —> 只需要规定好程序的接口,他人即可按照这种接口标准来编写符合接口要求的动态链接文件
DLL HELL: 早期 Windows,接口不兼容导致的模块更新后程序报错 —> 缺少一种有效的共享库版本管理机制
Linx - .so 文件 动态共享对象
Windows - .dll 动态链接库
共享对象最终在进程虚拟空间中的地址,在编译期是不确定的,在装载时才进行分配计算。
1.- gcc - shared 装载时重定位– 可以解决动态模块中有绝对地址引用的问题,但指令无法在多个进程之间共享,使得无法做到代码共享。
2.- gcc -fPIC 地址无关码 ==> 简单理解为把动态模块中需要改变地址的那部分,单独抽离出来放到了数据块,然后在每一个进程里都保留了一份备份。
动态链接器优化:延迟绑定(PLT )- 函数第一次用到时才进行绑定(符号查找、重定位)
动态链接步骤:
-
启动动态链接器本身
-
装载所有需要的共享对象
-
重定位 + 初始化
动态链接器
本身也是一个共享对象
它的重定位工作由自己完成。
动态链接器本身不可以依赖任何其他的共享对象,不可以使用任何库、运行库。 —> 自举启动
显示运行时链接(运行时加载)
第九章 —— 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
- 旧版本的dll替代了新版本的dll引起
- 新版本的dll无意发生改变
- 新版本的dll含有bug
解决
- 静态链接 - 编译链接期链接,避免运行期依赖dll
- 防止DLL覆盖 (Windows File Protection)
- 避免DLL冲突 - 不同程序依赖不同版本的DLL ,则各自拥有dll副本
- .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的目录。