静态链接库和动态链接库
一、前言
在跟着滴水学习PE结构的时候,接触到了静态链接和动态链接的知识。但是那时并没有做笔记,有也只是杂乱的(是啊!为什么不写博客啊???)。如今学习Android逆向时,也需要了解这方面的知识,趁着这个机会写一篇文章记录一下。
1.1 编译过程
先来了解一下程序的编译过程(借用菜鸟教程的图片):

从我们编写好的源文件开始,先经过预编译、编译、汇编,得到了.o文件(或.obj文件),这类文件是二进制文件。之后在链接过程,会将我们源文件对应的.obj文件与源文件所需要的静态链接库(.lib、.o)或动态链接库(.dll、.so)合并,形成了我们常见的可执行文件(如.exe、.elf文件)。静态链接库和动态链接库的区别就在于链接过程中是如何合并的。
二、静态链接库
2.1 概念
静态链接库在Windows平台下是.lib文件,在Linux平台下是.a文件。
静态链接库是一种资源的集合,包括函数、变量、类等,程序通过与其链接,调用库中的资源。在链接的时候,静态链接库会将自身完整复制到程序中,这样就使得程序可以独立运行,无需依赖外部库文件。但这样做也导致了一些问题,比如最终的可执行文件非常大、静态链接库更新则依赖于该库的所有程序都需要重新编译、多个程序使用同一个静态库会在内存中重复加载等。
2.2 Windows平台下创建与使用静态链接库
lib项目的编写比较简单,参照官方文档来就是了。

然后是使用lib时的一些配置。这部分在官方文档的在应用中使用静态库的功能处。官方用的是比较简单的方法,一个添加引用,一个添加引用库的路径就可以了。
我这里改变了一下方法,并没有使用“添加引用”,这个想法是基于后面的使用动态库的配置,步骤如下:
首先将
lib的.h头文件的所在路径添加到使用该lib项目的属性页 -> C/C++ -> 常规 -> 附加包含目录。这一步是为了能够使用头文件(库函数)。然后是将
lib文件的文件名添加到使用该lib项目的属性页 -> 链接器 -> 输入 -> 附加依赖项。接下来需要在使用该
lib项目的属性页 -> 链接器 -> 常规 -> 附加库目录处,添加lib文件的所在路径。
(因为是写完dll配置后尝试的,图片的话就参考后面的3.2 Windows平台下创建与使用动态链接库吧)
以上完成后,就可以成功的使用lib文件了。

2.3 Linux平台下创建与使用静态链接库
代码同上,编写好后执行指令生成.o文件。
1 | |
注意带参数-c,否则直接编译为可执行文件。然后,通过ar工具将目标文件打包成.a静态库文件
1 | |
这一步就会生成我们所需要的静态库。接下来是编写使用案例(代码同上),编写好了之后执行以下命令
1 | |
其中 -I 用于指定头文件搜索路径,-L 用于指定库文件搜索路径,-l 用于指定静态库或共享库文件(不需要lib前缀和.a后缀,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称),-o用于指定生成的可执行文件的名称,默认情况下会生成一个名为 a.out 的可执行文件。
因为我在测试的时候,TestStaticLibrary.cpp和静态库项目并没有放在同一个目录下,如果不加-I参数,可能会报错
1 | |
最后执行生成的.out文件,结果如下

三、动态链接库
3.1 概念
动态链接库在Windows平台下是.dll文件,在Linux平台下是.so文件。
与静态链接库相同的是,动态链接库也是一种资源的集合,包括函数、变量和类等资源。
但是动态链接库在链接的时候,并不会把自身复制到可执行程序中,而是把对动态链接库的引用信息放在可执行文件中,以指示程序在运行时需要加载的动态链接库。在程序运行时,操作系统会根据这些引用信息来动态加载并链接动态链接库,使得程序可以调用其中的函数和使用其中的数据。
这样做的优点,比如动态链接库的代码在内存中是共享的,可以被多个程序同时使用。但也有缺点,如果缺少所需的.dll文件,则可执行文件无法运行。
3.2 Windows平台下创建与使用动态链接库
dll项目编译后,会生成.lib、.dll文件。.lib文件是一个辅助文件,它包含了与动态链接库相关的一些信息,包括函数名、符号名以及函数在dll中的偏移等,主要作用是在编译期间提供引用信息,以便编译器能够正确地解析和链接动态链接库。
创建dll文件比较简单,但需要注意在函数名前面加上extern "C" __declspec(dllimport)。
extern "C"用于告诉编译器将代码按照 C 语言的规则进行链接。因为 C++ 在编译过程中会对函数名进行重载和名称修饰,导致函数名在编译后变成了不同的形式。而动态链接库对外暴露的函数名通常是使用 C 语言的命名规则,为了确保正确链接,需要使用extern "C"来告诉编译器使用 C 语言的链接规则。__declspec(dllimport)用于显式地指示编译器该函数是从dll文件中导入的。使用__declspec(dllimport)修饰的函数表示该函数在编译时是从外部的dll文件中引入的,而不是当前编译的源文件中定义的。这样,在链接阶段,编译器会根据该关键字生成正确的导入表,并在运行时动态加载对应的 DLL 文件。
这一部分的编写跟着官方文档走就行了。
这里主要说一下使用.dll文件时所需要的设置。
首先将
dll的.h头文件的所在路径添加到使用该dll项目的属性页 -> C/C++ -> 常规 -> 附加包含目录,这一步的设置跟静态链接库一样。是为了能够使用头文件(库函数)。(这一步在官方文档的将 DLL 标头添加到包含路径处)
然后是将
lib文件的文件名(以项目名命名的,我还傻乎乎的填.c的文件名)添加到使用该dll项目的属性页 -> 链接器 -> 输入 -> 附加依赖项,
现在只是告诉链接器在编译时需要使用到这个
lib文件,但是还没有告诉链接器去哪里找这个lib文件,所以接下来需要在使用该dll项目的属性页 -> 链接器 -> 常规 -> 附加库目录处,添加lib文件的所在路径(奇怪,这个lib文件怎么在使用dll项目的路径下???)
(以上在官方文档的将 DLL 导入库添加到项目中处)
最后是将
dll文件复制到使用该dll的项目中,在该项目的属性页 -> 生成事件 -> 生成后事件 -> 命令行中添加指令:1
xcopy /y /d dll所在路径 "$(OutDir)"(这一步在官方文档的在生成后事件中复制 DLL处)

以上完成后,就可以愉快地使用自己写的dll了。

3.3 Linux平台下创建与使用动态链接库
编写好so项目后,执行命令生成.o文件。
1 | |
其中-fPIC用于创建与地址无关的编译程序,是为了能够在多个应用程序间共享。
之后,执行命令生成动态库
1 | |
其中-shared用于生成动态链接库。
编写使用案例,执行下列命令,引用动态库编译成可执行文件(跟静态库方式一样)。
1 | |
接下来需要将生成的so文件复制到/usr/lib目录下(在root模式下)。
1 | |
否者报错如下
1 | |
找不到libdynmath.so,那为什么会这样呢?
原因是在Linux系统下,程序通过以下方式定位so文件:
- 默认搜索路径:系统会在一组默认的目录中搜索动态共享库,包括
/lib,/usr/lib,/lib64,/usr/lib64等等。 - 环境变量
LD_LIBRARY_PATH:程序可以通过设置环境变量LD_LIBRARY_PATH来指定额外的动态共享库搜索路径。系统会在该环境变量所指定的路径中搜索动态共享库。 rpath/rpath-link:程序可以在编译时通过-Wl,-rpath或-Wl,-rpath-link选项指定运行时搜索动态共享库的路径。这些路径会被保存在可执行文件的ELF头部中,并在程序运行时生效。- 缓存文件:
Linux系统还维护了一个动态共享库的缓存文件,通常是/etc/ld.so.cache。该文件记录了系统中可用的动态共享库的位置,加快了库的搜索速度。
所以最简单的方式是将so文件复制 /lib, /usr/lib等目录下。最终执行效果如下

四、总结
库是一种资源的集合,包括函数、变量、类等,程序可以通过与其链接来调用库中的资源。由于链接的方式的不同,可以分为静态链接库和动态链接库,静态链接库在编译阶段将自身复制到可执行文件中,这样一来可执行文件就可以独立运行;而动态链接库则是将引用信息复制到可执行文件中,可执行文件通过这些引用信息来动态加载动态链接库,这样一来可执行文件就不能独立运行。
参考:
演练:创建并使用静态库 (C++) | Microsoft Learn
演练:创建和使用自己的动态链接库 (C++) | Microsoft Learn
【Visual Studio】创建并使用静态库(.lib) 松库本库的博客-CSDN博客