cmake(动态库和静态库)
官方文档:https://cmake.org/cmake/help/latest/index.html
现代cmake 基于目标的库的发布和查找流程。
静态库
静态库是在编译期就把一组目标文件(.o / .obj),打包成一个归档文件,并在最终链接时,一次性复制到可执行文件或共享库中的代码与数据集合中。
静态库只是一个归档目标文件,没有经过链接阶段。在生成阶段不需要链接,在使用阶段才会发生链接。
一、文件形式
Linux / macOS/ BSD:libfoo.a(ar 归档格式)
windows:foo.lib (COFF归档格式,区别于import-lib)
现在制作一个加减法的静态库。
下面是目录的结构,和每个CMakeLists.txt的内容
下面是构建目录
可以观察到,源代码目录和构建目录生成的二进制文件和静态库位置是对应的。如果需要修改生成的位置。比如需要将二进制文件放进bin目录下,库文件放在lib目录下,可以使用RUNTIME_OUTPUT_DIRECTORY目标属性更改默认输出路径。
下面分别是修改二进制文件和库文件默认输出位置,在各自的CMakeLists.txt中进行修改。
CMAKE_BINARY_DIR的值,就是执行 cmake 命令时所在的那一层目录。
下面是执行结果
可以观察到,二进制文件和库文件已经生成在了指定的目录下了
重点命令行解释
cmake是基于目标的属性传递的现代化构建系统。
这里有目标、属性、属性API和属性传递机制四个概念。其中目标、属性和属性API就是cmake的三大核心,这样的三大核心和属性的传递机制,共同构建了一个现代化的构建系统的核心。
目标-Target
目标有类型、属性和操作属性的API。
目标的种类如下
类型 | 创建命令 | 说明 |
EXECUTABLE | add_executable | main ,cur |
STATIC | add_library(... STATIC) | libfoo.a, foo.lib |
SHARED | add_library(... SHARED) | libfoo.so, foo.dll |
MODULE | add_library(... MODULE) | 插件:libplugin.so, 使⽤dlopen 运⾏时加载 |
OBJECT | add_library(... OBJECT) | 仅 .o/.obj,存在于内存,不⽣成库⽂件 |
INTERFACE | add_library(... INTERFACE) | ⽆库⽂件,携带使⽤要求 |
IMPORTED | add_library(... IMPORTED) | 使⽤cmake 内存⽬标对象引⽤磁盘上的外 部构建产物 |
ALIAS | add_library(... ALIAS) | 为同项⽬内的现有⽬标取别名 |
属性-Property
属性种类如下
类别 | 作用域 | 典型读/写命令 | 常用属性示例 |
全局属性 (Global) | 整个 CMake 运⾏⽣命周期 | get/set_property(GLOBAL PROPERTY …) | CMAKE_ROLE |
⽬录属性 (Directory) | 当前源码⽬录及其⼦ ⽬录 | get/set_property(DIRECTORY PROPERTY …) | INCLUDE_DIRECTORIES |
⽬标属性 (Target) | 单个构建⽬标(库、 可执⾏、接⼝库…) | get/set_property(TARGET <tgt> PROPERTY …) | LINK_LIBRARIES INCLUDE_DIRECTORIES |
源⽂件属性 (Source File) | 单个源码/资源⽂件 | get/set_source_files_properti es | COMPILE_FLAGS |
测试属性 (Test) | 由 add_test() 定义的 单个测试 | get/set_tests_properties() | WORKING_DIRECTORY |
安装⽂件属性 (Installed File) | install() ⽣成的安装 清单条⽬ | set_property(INSTALL … PROPERTY …) | RPATH |
属性的作用域与传播范围(main ----> curl)
关键字 | 对当前目标的构建影响 | 是否传播 | 对当前目标使用者的影响 | 解释 | 例子(面包和面粉的例子) |
PRIVATE | 生效 | 否 | 不生效 | 自己用 | 制作面包的面粉品牌不公开 |
PUBLIC | 生效 | 是 | 生效 | 自己-下游用 | 公开制作⾯包的⾯粉的品牌 |
INTERFAC E | 不生效 | 是 | 生效 | 自己不用-下游用 | 说明书,说明⽤什么⾯粉制 作,不卖东西 |
这里的关键字类似于c++中的限定关键字
操作目标和属性的API
类别 | 典型命令(可选关键词) | 主要作用 | 涉及的核心属性(部分示例) |
1. 通⽤读 / 写接 ⼝ | set_target_properties() get_target_property() | 任意⽬标属性的设置、 追加、查询(最底层 API) | 任何 prop_tgt |
2. 编译阶段相关 | target_compile_definitions target_compile_options target_precompile_headers target_include_directories target_sources | 控制源⽂件编译:宏定 义、编译选项、语⾔特 性、预编译头、包含⽬ 录、源⽂件列表等 | COMPILE_DEFINITIONS、 COMPILE_OPTIONS、 COMPILE_FEATURES、 PRECOMPILE_HEADERS、 INCLUDE_DIRECTORIES、 SOURCES 等 |
3. 链接 & 输出 阶段相关 | target_link_libraries target_link_options target_link_directories | 配置⽬标被链接时的 库、选项及搜索路径 | LINK_LIBRARIES INTERFACE_LINK_LIBRARIES LINK_OPTIONS INTERFACE_LINK_OPTIONSLI NK_DIRECTORIES INTERFACE_LINK_DIRECTORIE S |
4. 安装 & 打包 阶段相关 | install(TARGETS …) install(EXPORT …) | ⽣成安装规则与包,控 制⽬标在安装树中的布 局及其运⾏时⾏为 | RUNTIME_OUTPUT_DIRECTO RY、 LIBRARY_OUTPUT_DIRECTOR Y、 ARCHIVE_OUTPUT_DIRECTOR Y EXPORT_NAME、 INSTALL_RPATH |
add_library
添加一个静态库或者动态库目标,让cmake从指定的文件列表生成。
参数解释
参数 | 含义 |
<name> | 库的名称(不包含前缀和后缀,如 Foo 会⽣成 libFoo.a)。项⽬内部唯⼀ |
STATIC | 创建静态库(默认值,若不指定类型)。 |
SHARED | 创建动态库(共享库)。 |
【source】 | 构建库的源⽂件列表。 |
默认情况下,将在与调⽤命令的源树⽬录相对应的构建树⽬录中创建库⽂件。 可以使⽤ARCHIVE_OUTPUT_DIRECTORY 、 LIBRARY_OUTPUT_DIRECTORY 和 RUNTIME_OUTPUT_DIRECTORY ⽬标属性修改默认的输出路径。
target_include_directories
设置⽬标在开发和发布阶段的头⽂件搜索⽬录,可以传递性传播给下游的使⽤⽅。
参数解释
参数 | 含义 |
<target> | ⽬标名称(由 add_executable 或 add_library 定义) 可以是普通库/可执⾏,也可以是 INTERFACE_LIBRARY 或导⼊⽬标 (IMPORTED) |
<INTERFACE|PUBLIC|PRIVATE> | 属性的作⽤域关键字 |
path | 头⽂件搜索路径: 如果是相对路径则相对于当前的CMAKE_CURRENT_SOURCE_DIR |
通过target_include_directories添加的 路径 最终是通过gcc 的 -I 参数传递给编译器的。
target_link_libraries
设置⼆进制⽬标的依赖库列表,相当于使⽤通⽤的set属性设置函数设置了LINK_LIBRARIES或者 INTERFACE_LINK_LIBRARIES这个属性,在cmake源代码⾥这2个属性是2个 vector<string> 成员。最终以 -l 的形式出现在gcc参数⾥
参数解释
参数 | 含义 |
<target> | ⽬标名称(由 add_executable 或 add_library 定义) |
<INTERFACE|PUBLIC|PRIVATE> | 属性的作⽤域关键字 |
PRIVATE 关键字:相当于使⽤set_target_properties 设置了LINK_LIBRARIES属性,设置的库列表 只 会写进⽬标的LINK_LIBRARIES列表⾥。
INTERFACE 关键字:相当于使⽤set_target_properties 设置了INTERFACE_LINK_LIBRARIES,只会 写进⽬标的INTERFACE_LINK_LIBARIERS列表⾥。
PUBLIC 关键字设置的列表会同时写进LINK_LIBRARIES和INTERFACE_LINK_LIBRARIES⾥。INTERFACE_LINK_LIBRARIES 列表出现的库会被传播给这个⽬标的使⽤⽅。
通过 target_link_libraries最终是通过gcc 的-l 选项传递给链接器的。
作用:
1、设置目标的依赖库列表,列出的依赖者会以 -l 的形式出现目标的gcc的参数中
2、建立依赖关系,被依赖者需要传播的属性可以沿着关系链传播给依赖者
解释:
在发布方发布库的时候,需要设置INTERFACE级别属性,告诉下游使用方,使用该库的时候需要链接什么资源以及配置文件。
而在下游的使用方,首先使用find_package查找到发布的库,创建可执行文件,并通过target_link_libraries读取上游发布方告知使用该库需要的组件的信息。
使用示例
接下来使用add_library、target_include_directories、target_link_libraries做个示例
首先下面是结构目录,和各级目录下的CMakeLists.txt内容
然后观察构建目录的生成流程,进入build目录,运行cmake --build . -v
在生成静态库的时候,正确执行了去对应位置下寻找private和public。也对应属性作用域的传播范围
在编译链接执行的之后,正确执行了去对应位置下寻找public和interface,包括需要额外链接的依赖库
API
操作通用读/写接口
set_target_properties和 get_target_properties
设置/查询 ⽬标(如可执⾏⽂件、库)的各种属性,控制编译、链接、安装等⾏为
参数解释
参数 | 含义 |
<target1> | 库的名称 |
<prop1> <value1> | 属性名字和值 |
常见的属性名字和含义
属性名字 | 含义 | gcc选项 |
INCLUDE_DIRECTORIES | 构建规范-⾃⼰编译时包含的⽬录 | -l |
INTERFACE_INCLUDE_DIRECTORIES | 使⽤要求-下游适⽤房需要包含的⽬ 录 | -l |
LINK_LIBRARIES | 构建规范-⾃⼰链接时需要链接的库 列表 | -l |
INTERFACE_LINK_LIBRARIES | 使⽤要求-⾃⼰链接时需要链接的库 列表 | -l |
LIBRARY_OUTPUT_DIRECTORY | 库的输出路径 | |
LIBRARY_OUTPUT_NAME | 库的⽂件的输出名字 | |
BUILD_RPATH | 构建⽬录中的运⾏时库⽂件搜索路 径 | -Wl,-rpath |
INSTALL_RPATH | 安装⽬录中的运⾏时库⽂件搜索路 径 | -Wl,-rpath |
add_subdirectory
添加⼦⽬录到构建树,cmake会⾃动进⼊到源码树⼦⽬录,执⾏位于⼦⽬录⾥的CMakeLists.txt⽂件。(add_subdirectory()是CMake构建层次结构里的“递归入口”)
处理顺序:当处理到add_subdirectory的时候,CMake 会⽴即处理 source_dir 中的 CMakeLists.txt,当前⽂件的处理会暂停,直到⼦⽬录 处理完毕,在继续处理当前⽂件add_subdirectory之后的命令。
1)、目录变量作用域
子目录能读取父目录已经set的变量,也能覆盖再向下传递(继承性传递);父目录看不到子目录定义的普通变量(除非使用PARENT_SCOPE或CACHE)。
2)、目标范围
只要add_library()/ add_executable()定义了目标,他就被注册到了全局目标表(map<string, cmTarget>),在任何目录都能链接。
3)、include()/add_subdirectory()区别
include()只是立即执行另一个cmake脚本文件,不会开辟新的目录作用域,而add_subdirectory()会进入新的CMAKE_CURRENT_SOURCE_DIR / BINARY_DIR并递归生成。
include(<file>) = 把这段脚本当做当前目录的一部分立即执行。add_subdirectory(<dir>) = 进入一个新目录作用域,递归执行其中的CMakeLists.txt,并为它指定独立的构建输出目录
参数解释
参数 | 含义 |
source_dir | 通常为当前⽂件夹下的⼦⽬录的名字。 |
[binary_dir] | cmake会在构建树⾥创建同名的⼦⽬录,⽤于保存⼦⽬录的的cmake⽂件⾥⽣成的⽬标和⼆进制 |
cmake内部静态库的生成与定位流程
第一步:目标生成
当你写下add_library(MyMath STATIC add.cpp sub.cpp)这⼀⾏命令时,cmake内部会在全局的 Targets 容器中注册⼀个名为 MyMath 的cmTarget⽬标。
第二步:目标信息存储
每个cmTargetInternals会存储⽬标的名称,类型,源⽂件列表,输出地址等属性。当你使⽤LIBRARY_OUTPUT_DIRECTORY等属性修改⽬标输出地址的时候,cmake也会更新最终的输出地址。
第三步:生成器阶段-定位静态库路径
CMake 配置阶段结束后,进⼊⽣成阶段(cmLocalGenerator、cmGlobalGenerator),⽣成器会遍历所有⽬标(cmTarget),根据⽬标的属性(如 ARCHIVE_OUTPUT_DIRECTORY)和平台规则, 推导出静态库的实际输出路径。⽐如:build/libmylib.a。
第四步:链接命令的⽣成
⽣成器会为每⼀个⽬标⽣成链接规则,其中包括⽬标⽂件的输出路径,最终会⽣成⼀下 makefile 指令
静态库(编译、链接、安装)
安装头文件、库文件和定义导入目标
常见内置变量
变量 | 含义 | 常见取值 |
CMAKE_SOURCE_DIR | 顶层源码目录(整个项目的根) | |
CMAKE_BINARY_DIR | 顶层构建目录(整个构建的根) | |
CMAKE_INSTALL_LIBDIR | 安装时库文件的标准相对目录(由 GNUInstallDirs 提供) | lib 或 lib64 |
CMAKE_INSTALL_INCLUDEDIR | 安装时头文件的标准相对目录(由 GNUInstallDirs 提供) | include |
CMAKE_CURRENT_BINARY_DIR | 当前正在处理的 CMakeLists.txt 对应的构建目录 | |
CMAKE_CURRENT_SOURCE_DIR | 当前正在处理的 CMakeLists.txt 所在的源码目录 |
下面是目录结构
下面是顶级CMakeLists.txt的内容
cmake_minimum_required(VERSION 3.27.4)project(InstallMyMathLANGUAGES CXX
)add_subdirectory(my_lib)
下面是子目录下的CMakeLists.txt内容(关键),
# 生成数学库和发布使用
# 1-4是数学库的生成 5-8是头文件的安装 9-10是安装配置文件的生成# 1、收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2、添加构建目标
add_library(MyMath STATIC ${SRC_LISTS})
# 3、设置库的使用要求,也就是下游消费者必须包含的头文件搜索路径
target_include_directories(MyMath INTERFACE # CMAKE_INSTALL_INCLUDEDIR的值就是include"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # /usr/local/include
)
# 4、设置库的默认输出路径
set_target_properties(MyMath PROPERTIESARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)# 凡是涉及到安装树的时候都是使用的相对路径,cmake会自动将相对路径拼接在prefix路径后面
# CMAKE_INSTALL_PREFIX的值就是/usr/local,这些内置变量都是在GNUInstallDirs模块中定义好的# 5、安装静态库文件
include(GNUInstallDirs)
install(TARGETS MyMath# 导出集合 在导出的时候,使用这个导出集合即可EXPORT MyMathTargets DESTINATION ${CMAKE_INSTALL_LIBDIR} # CMAKE_INSTALL_LIBDIR的值就是lib
)
# 6、安装静态库头文件
install(DIRECTORY include/DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math # /usr/local/include/math/math.hFILES_MATCHING PATTERN "*.h" # 过滤
)
# 7、安装导出目标集合 到 构建目录(构建树)(本地)
export(EXPORT MyMathTargets# CMAKE_CURRENT_BINARY_DIR当前构建目录FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake
)
# 8、安装导出目标集合 到 安装目录(安装树)
install(EXPORT MyMathTargetsFILE MyMathTargets.cmakeNAMESPACE MyMath:: # 命名空间 MyMath::MyMath# CMAKE_INSTALL_LIBDIR标准安装路径DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath # /usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)# 9、生成find_package需要的配置文件
include(CMakePackageConfigHelpers)
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" # 自己定义的模版"${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake" #生成结果(构建目录)INSTALL_DESTINATION "lib/cmake/MyMath" #最终安装位置
)# 10、安装配置文件(第9步生成的)到cmake 规定的标准的安装路径
install(FILES"${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake"DESTINATION "lib/cmake/MyMath"
)# 1-4 步:把代码变成静态库,并设定“对外接口”(头文件路径)。
# 5-6 步:把库文件和头文件“拷贝”到系统。
# 7-8 步:生成“导出脚本”,让 CMake 能在任何位置重新导入已安装的目标。
# 9-10 步:提供 find_package 的入口文件,完成“安装即发现”。
这是最终的执行结果
CMAKE_CURRENT_LIST_DIR表示当前正在处理的
.cmake
脚本或CMakeLists.txt
文件所在的目录。
查找并使用目标
这是使用方的目录结构
如果在main.cpp中使用#include "math/math.h"会报错,就需要在c_cpp_properties.json中的includePath中增添 /usr/local/include 路径。
下面是CMakeLists.txt中的内容
cmake_minimum_required(VERSION 3.27.4)project(TestMyMath)add_executable(main main.cpp)# 查找MyMath数学库
find_package(MyMath CONFIG REQUIRED)
# 声明依赖
target_link_libraries(main PRIVATE MyMath::MyMath)
使用效果如下
export
将项⽬中的⽬标(如可执⾏⽂件、库)及其相关属性导出到⼀个⽂件中,以便在其他项⽬中使⽤。
参数解释
参数 | 含义 |
<export_name> | 导出集的名称,⽤于在其他项⽬中引⽤ |
<file_name> | 导出⽂件的名称,通常为 <export_name>Config.cmake |
NAMESPACE <namespace>: (可选) | 为导出的⽬标添加命名空间,避免命名冲突 |
DESTINATION <destination>: (可选) | 指定导出⽂件的安装⽬录 |
CONFIGURATIONS <config1> : (可选) | 指定要导出的配置(如 Debug、Release) |
INCLUDE_INSTALL_DIRS:(可选) | 导出的配置⽂件中包含安装路径 |
export() 命令会⽣成⼀个 CMake ⽂件,其中包含已导出⽬标的信息(如位置、依赖项、编译选项)。 其他项⽬可以通过 find_package() 加载此配置⽂件,从⽽使⽤你项⽬中的⽬标。
通常和install 配合使⽤, install 定义需要导出的导出组,export 将导出组中的所有⽬标导出到配置⽂ 件。
与 install(EXPORT) 命令的区别:
export(EXPORT):⽣成临时配置⽂件,仅⽤于从构建⽬录中导出⽬标。
install(EXPORT ...):⽣成正式配置⽂件,并安装到系统,⽤于从安装⽬录导出⽬标。
configure_package_config_file
根据模板⽂件⽣成⾃定义包配置⽂件,可以被其他项⽬使⽤来查找和配置你的包。
参数解释
参数 | 含义 |
<input> | 模板⽂件的路径,通常是⼀个 CMake 脚本⽂件,其中包含了⼀些变量和 逻辑,⽤于⽣成最终的配置⽂件 |
<output> | ⽣成的配置⽂件的路径 |
COPYONLY:(可选) | 如果指定,将直接复制模板⽂件⽽不进⾏任何配置 |
INSTALL_DESTINATION <dir>: (可选) | 指定⽣成的配置⽂件的安装⽬录 |
CONFIGURATIONS <config1> : (可选) | 指定要导出的配置(如 Debug、Release) |
NO_SET_AND_CHECK_MACRO: (可选) | 如果指定,将不⽣成 set_and_check 宏,⽤于设置变量并验证其路径是 否存在 |
NO_CHECK_REQUIRED_COMPO NENTS_MACRO:(可选) | 如果指定,将不⽣成 check_required_components 宏,⽤于检查包的 所有必需组件是否都已找到并正确配置。 |
动态库(编译、链接、引用)
当前的目录结构如下
app下的CMakeLists.txt文件内容。这是作为动态库使用方的CMakeLists.txt文件
# 1、搜集文件列表
file(GLOB SRC_LISTS "*.cpp")
# 2、添加构建目标
add_executable(main ${SRC_LISTS})# 3、添加依赖库列表
# 这里是指定链接时的库,这样链接器才能找到库中定义,完成重定位
target_link_libraries(main PRIVATE MyMath)# 4、修改默认的输出路径
set_target_properties(main PROPERTIESRUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)# 5、打印库文件相对main的相对路径 自动定义命令
add_custom_command(TARGET mainPOST_BUILDCOMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:MyMath>"COMMENT "输出动态库的全路径"
)
my_lib下的CMakeLists.txt文件内容。这是作为动态库的发布方的CMakeLists.txt文件
# 1、收集库的源代码
#将src目录下的所有cpp文件添加到SRC_LISTS变量中
file(GLOB SRC_LISTS "src/*.cpp")# 2、添加构建目标
#将SRC_LISTS变量中的文件构建动态库MyMath
add_library(MyMath SHARED ${SRC_LISTS})# 3、设置库的使用要求
# 实际上是告诉使用方头文件的位置
target_include_directories(MyMathPUBLIC ${CMAKE_CURRENT_LIST_DIR}/include
)# 4、修改默认的输出路径
set_target_properties(MyMath PROPERTIESARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/libLIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)# 5、添加 -fPIC 编译选项
target_compile_options(MyMath PRIVATE "-fPIC") #-fPIC全称为Position Independent Code,即位置无关代码,
顶级的CMakeLists.txt文件如下
# 1、设置最低版本号
cmake_minimum_required(VERSION 3.27.4)
# 2、设置项目名称
project(TestMyMath)
# 3、添加目录层级
add_subdirectory(my_lib)
add_subdirectory(app)
现在,下面是动态库的构建过程
这里可以看见动态库的生成过程中,使用了-fPIC选项,最终生成了以so结尾的动态库文件。
这里为什么生成动态库需要使用-fPIC选项呢?
-fPIC的全称为(Position Independent Code)位置无关代码。使用-fPIC选项的作用是让编译器生成“与位置无关”的机器码(Position Independent Code)。动态库必须被加载到任意进程地址空间,且多个进程要共享同一份物理代码段。如果代码不是位置无关的,就无法满足这两个需求。
另外,可以观察到。这里使用target_link_libraries命令和静态库的使用完全一致,并没有标注依赖的是静态库还是动态库。这是因为,在生成动、静态库的时候,有一个全局的cmTarget属性,其中包含着目标的属性,包括是static还是shared类型的。在使用target_link_libraries的时候,会自动根据这里的属性判断库的属性。
系统动态加载器-ld
elf文件介绍
elf文件是是一种用于可执行文件、目标文件、共享库的标准文件格式,它包含了程序运行所需的二进制代码、数据、符号表、重定位信息等,是操作系统加载和运行程序的基础。
elf作用:1、作为可执行文件的容器,存储编译后的机器指令和数据。2、通过共享库(
.so
文件)实现代码复用,减少内存占用。3、统一的格式使得不同工具链(编译器、链接器、调试器)能够协同工作。4、包含调试符号(如 DWARF 格式),支持 GDB 等调试工具。elf文件结构:
组成部分 作用 ELF Header 描述文件类型(可执行/共享库/目标文件)、目标架构(如 x86-64)、入口地址等。 Program Header Table 描述如何加载文件到内存(段信息),仅存在于可执行文件和共享库中。 Section Header Table 描述文件的节区(如 .text
、.data
、.bss
),用于链接和调试。节区(Sections) 存储实际数据,如代码( .text
)、全局变量(.data
)、符号表(.symtab
)等。如何加载并运行elf文件?
内核加载过程(Linux为例):
当用户运行
./program
时,操作系统通过以下步骤加载 ELF 文件:1、读取 ELF Header
内核首先检查文件的前几个字节(
7f 45 4c 46
,即ELF
的 ASCII 编码),确认是 ELF 格式。2、解析 Program Header Table
内核根据段类型(如
PT_LOAD
)将文件的代码段(.text
)、数据段(.data
)等加载到内存中,并设置权限(代码段为r-x
,数据段为rw-
)。3、处理动态链接
如果 ELF 文件是动态链接的(如依赖
libc.so
),内核会加载动态链接器(如/lib64/ld-linux-x86-64.so.2
),由它负责:加载所需的共享库列表并加载到内存。
重定位符号(如
printf
的地址)。初始化全局变量(如 C++ 的全局构造函数)。
4、跳转到入口点
内核将 CPU 指令指针(
RIP
)设置为 ELF Header 中指定的入口地址(通常是_start
,而非main
),开始执行程序。
elf怎么知道程序依赖哪些动态库,并从哪里去加载需要的动态库的?
首先从elf文件中的DT_NEEDED字段了解到(需要的库)
另外动态链接器依次按照LD_LIBRARY_PATH环境变量列出的目录、ELF文件的DT_RUNPATH记录、系统缓存 /etc/ld.so.cache和默认目录 /lib、/usr/lib/... 中去查找。如果在这四个路径下都没找到,就会报错。
从这里可以看出LD_LIBRARY_PATH的优先级高于elf文件中的DT_RUNPATH
cmake中的动态加载器
相较于上面Linux中使用动态加载器去寻找需要的库的逻辑,可以看见更多的依赖环境变量的设置,如果环境变量的设置出错,出错率就比较高。而cmake直接就将寻找动态库的逻辑改为了在elf文件的头中的DT_RUNPATH中去寻找。
动态库(编译-链接-安装)
下面是动态库的目录结构
顶级CMakeLists.txt中的内容
cmake_minimum_required(VERSION 3.27.4)project(InstallMyMathLANGUAGES CXX
)
add_subdirectory(my_lib)
子文件夹中的CMakeLists.txt中的内容
# 生成数学库和发布使用
# 1-4是数学库的生成 5-8是头文件的安装 9-10是安装配置文件的生成# 1、收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2、添加构建目标
add_library(MyMath SHARED ${SRC_LISTS})
# 3、设置库的使用要求,也就是下游消费者必须包含的头文件搜索路径
target_include_directories(MyMath INTERFACE # CMAKE_INSTALL_INCLUDEDIR的值就是include"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # /usr/local/include
)
# 4、设置库的默认输出路径
set_target_properties(MyMath PROPERTIESLIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/libOUTPUT_NAME MyMathVERSION 1.2.3SOVERSION 20COMPILE_OPTIONS "-fPIC"
)# 凡是涉及到安装树的时候都是使用的相对路径,cmake会自动将相对路径拼接在prefix路径后面
# CMAKE_INSTALL_PREFIX的值就是/usr/local,这些内置变量都是在GNUInstallDirs模块中定义好的# 5、安装静态库文件
include(GNUInstallDirs)
install(TARGETS MyMath# 导出集合 在导出的时候,使用这个导出集合即可EXPORT MyMathTargets DESTINATION ${CMAKE_INSTALL_LIBDIR} # CMAKE_INSTALL_LIBDIR的值就是lib
)
# 6、安装静态库头文件
install(DIRECTORY include/DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math # /usr/local/include/math/math.hFILES_MATCHING PATTERN "*.h" # 过滤
)
# 7、安装导出目标集合 到 构建目录(构建树)(本地)
export(EXPORT MyMathTargets# CMAKE_CURRENT_BINARY_DIR当前构建目录FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake
)
# 8、安装导出目标集合 到 安装目录(安装树)
install(EXPORT MyMathTargetsFILE MyMathTargets.cmakeNAMESPACE MyMath:: # 命名空间 MyMath::MyMath# CMAKE_INSTALL_LIBDIR标准安装路径DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath # /usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)# 9、生成find_package需要的配置文件
include(CMakePackageConfigHelpers)
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in" # 自己定义的模版"${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake" #生成结果(构建目录)INSTALL_DESTINATION "lib/cmake/MyMath" #最终安装位置
)# 10、安装配置文件(第9步生成的)到cmake 规定的标准的安装路径
install(FILES"${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake"DESTINATION "lib/cmake/MyMath"
)# 1-4 步:把代码变成动态库,并设定“对外接口”(头文件路径)。
# 5-6 步:把库文件和头文件“拷贝”到系统。
# 7-8 步:生成“导出脚本”,让 CMake 能在任何位置重新导入已安装的目标。
# 9-10 步:提供 find_package 的入口文件,完成“安装即发现”。
动态库(查找-使用)
无论是动态库还是静态库的使用,大体都是一样的。只是在编译链接的时候,会有所不同。
这里查找库的路径是 /usr/local/lib下的动态库。
而在未安装之前,查找库的路径是构建树的目录。
rpath是有两个声明周期的,一个是构建阶段另一个是在安装阶段。而在安装之后,cmake会自动将rpath修改成为安装阶段的时候。
下面查看elf文件可以看出,rpath已经修改为了 /usr/local/lib 了