静态链接库和动态链接库
一、前言
在跟着滴水学习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博客