静态链接库和动态链接库

一、前言

在跟着滴水学习PE结构的时候,接触到了静态链接和动态链接的知识。但是那时并没有做笔记,有也只是杂乱的(是啊!为什么不写博客啊???)。如今学习Android逆向时,也需要了解这方面的知识,趁着这个机会写一篇文章记录一下。


1.1 编译过程

先来了解一下程序的编译过程(借用菜鸟教程的图片):

从我们编写好的源文件开始,先经过预编译、编译、汇编,得到了.o文件(或.obj文件),这类文件是二进制文件。之后在链接过程,会将我们源文件对应的.obj文件与源文件所需要的静态链接库(.lib.o)或动态链接库(.dll.so)合并,形成了我们常见的可执行文件(如.exe.elf文件)。静态链接库和动态链接库的区别就在于链接过程中是如何合并的。

二、静态链接库

2.1 概念

静态链接库在Windows平台下是.lib文件,在Linux平台下是.a文件。

静态链接库是一种资源的集合,包括函数、变量、类等,程序通过与其链接,调用库中的资源。在链接的时候,静态链接库会将自身完整复制到程序中,这样就使得程序可以独立运行,无需依赖外部库文件。但这样做也导致了一些问题,比如最终的可执行文件非常大、静态链接库更新则依赖于该库的所有程序都需要重新编译、多个程序使用同一个静态库会在内存中重复加载等。

2.2 Windows平台下创建与使用静态链接库

lib项目的编写比较简单,参照官方文档来就是了。

然后是使用lib时的一些配置。这部分在官方文档在应用中使用静态库的功能处。官方用的是比较简单的方法,一个添加引用,一个添加引用库的路径就可以了。

我这里改变了一下方法,并没有使用“添加引用”,这个想法是基于后面的使用动态库的配置,步骤如下:

  1. 首先将 lib.h头文件的所在路径添加到使用该lib项目的属性页 -> C/C++ -> 常规 -> 附加包含目录。这一步是为了能够使用头文件(库函数)。

  2. 然后是将lib文件的文件名添加到使用该lib项目的属性页 -> 链接器 -> 输入 -> 附加依赖项。

    接下来需要在使用该lib项目的属性页 -> 链接器 -> 常规 -> 附加库目录处,添加lib文件的所在路径。

(因为是写完dll配置后尝试的,图片的话就参考后面的3.2 Windows平台下创建与使用动态链接库吧)

以上完成后,就可以成功的使用lib文件了。

2.3 Linux平台下创建与使用静态链接库

代码同上,编写好后执行指令生成.o文件。

1
g++ -c StaticMath.cpp(cpp文件名)

注意带参数-c,否则直接编译为可执行文件。然后,通过ar工具将目标文件打包成.a静态库文件

1
ar -crv libstaticmath.a(.a文件名任意) StaticMath.o(生成的.o文件名)

这一步就会生成我们所需要的静态库。接下来是编写使用案例(代码同上),编写好了之后执行以下命令

1
g++ TestStaticLibrary.cpp -I ./StaticLibrary -L ./StaticLibrary -lstaticmath -o TestStaticLibrary.out

其中 -I 用于指定头文件搜索路径,-L 用于指定库文件搜索路径,-l 用于指定静态库或共享库文件(不需要lib前缀和.a后缀,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称),-o用于指定生成的可执行文件的名称,默认情况下会生成一个名为 a.out 的可执行文件。

因为我在测试的时候,TestStaticLibrary.cpp和静态库项目并没有放在同一个目录下,如果不加-I参数,可能会报错

1
2
3
4
TestStaticLibrary.cpp:1:10: fatal error: StaticMath.h: No such file or directory
1 | #include "StaticMath.h"
| ^~~~~~~~~~~~~~
compilation terminated.

最后执行生成的.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文件时所需要的设置。

  1. 首先将 dll.h头文件的所在路径添加到使用该dll项目的属性页 -> C/C++ -> 常规 -> 附加包含目录,这一步的设置跟静态链接库一样。是为了能够使用头文件(库函数)。(这一步在官方文档将 DLL 标头添加到包含路径处)

  2. 然后是将lib文件的文件名(以项目名命名的,我还傻乎乎的填.c的文件名)添加到使用该dll项目的属性页 -> 链接器 -> 输入 -> 附加依赖项,

    现在只是告诉链接器在编译时需要使用到这个lib文件,但是还没有告诉链接器去哪里找这个lib文件,所以接下来需要在使用该dll项目的属性页 -> 链接器 -> 常规 -> 附加库目录处,添加lib文件的所在路径(奇怪,这个lib文件怎么在使用dll项目的路径下???)

    (以上在官方文档将 DLL 导入库添加到项目中处)

  3. 最后是将dll文件复制到使用该dll的项目中,在该项目的属性页 -> 生成事件 -> 生成后事件 -> 命令行中添加指令:

    1
    xcopy /y /d dll所在路径 "$(OutDir)"

    (这一步在官方文档在生成后事件中复制 DLL处)

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

3.3 Linux平台下创建与使用动态链接库

编写好so项目后,执行命令生成.o文件。

1
g++ -fPIC -c DynamicMath.cpp(cpp文件名)

其中-fPIC用于创建与地址无关的编译程序,是为了能够在多个应用程序间共享。

之后,执行命令生成动态库

1
g++ -shared -o libdynmath.so(.a文件名任意) DynamicMath.o(生成的.o文件名)

其中-shared用于生成动态链接库。

编写使用案例,执行下列命令,引用动态库编译成可执行文件(跟静态库方式一样)。

1
g++ TestDynamicLibrary.cpp -I ./DynamicLibrary -L ./DynamicLibrary -ldynmath -o TestDynamicLibrary

接下来需要将生成的so文件复制到/usr/lib目录下(在root模式下)。

1
sudo cp ./DynamicLibrary/libdynmath.so /usr/lib

否者报错如下

1
2
./TestDynamicLibrary: error while loading shared libraries: libdynmath.so: 
cannotopen shared object file: No such file or directory

找不到libdynmath.so,那为什么会这样呢?

原因是在Linux系统下,程序通过以下方式定位so文件:

  1. 默认搜索路径:系统会在一组默认的目录中搜索动态共享库,包括 /lib, /usr/lib, /lib64, /usr/lib64 等等。
  2. 环境变量LD_LIBRARY_PATH:程序可以通过设置环境变量 LD_LIBRARY_PATH 来指定额外的动态共享库搜索路径。系统会在该环境变量所指定的路径中搜索动态共享库。
  3. rpath/rpath-link:程序可以在编译时通过 -Wl,-rpath-Wl,-rpath-link 选项指定运行时搜索动态共享库的路径。这些路径会被保存在可执行文件的 ELF 头部中,并在程序运行时生效。
  4. 缓存文件:Linux系统还维护了一个动态共享库的缓存文件,通常是 /etc/ld.so.cache。该文件记录了系统中可用的动态共享库的位置,加快了库的搜索速度。

所以最简单的方式是将so文件复制 /lib, /usr/lib等目录下。最终执行效果如下

四、总结

库是一种资源的集合,包括函数、变量、类等,程序可以通过与其链接来调用库中的资源。由于链接的方式的不同,可以分为静态链接库和动态链接库,静态链接库在编译阶段将自身复制到可执行文件中,这样一来可执行文件就可以独立运行;而动态链接库则是将引用信息复制到可执行文件中,可执行文件通过这些引用信息来动态加载动态链接库,这样一来可执行文件就不能独立运行。


参考:

演练:创建并使用静态库 (C++) | Microsoft Learn

演练:创建和使用自己的动态链接库 (C++) | Microsoft Learn

【Visual Studio】创建并使用静态库(.lib) 松库本库的博客-CSDN博客

【Visual Studio】创建并使用动态链接库(C++)_松库本库的博客-CSDN博客

C++静态库与动态库 | 菜鸟教程 (runoob.com)


静态链接库和动态链接库
http://example.com/2023/07/26/Android安全/静态链接库和动态链接库/
作者
gla2xy
发布于
2023年7月26日
许可协议