高版本cmake竟然能直接修改二进制文件的符号列表数据-二进制文件

高版本cmake竟然能直接修改二进制文件的符号列表数据

1.提出问题

采用cmake配置安装软件,可以指定通过CMAKE_INSTALL_PREFIX变量来指定安装目录。在允许cmake配置好编译工程目录并运行完make后,就可以用make install命令把编译好的程序安装在指定目录。

这是一个很好的机制,但是对于其运行原理我有一些困惑:可执行文件是采用什么机制来寻找动态链接库的?

首先,我们知道每一个动态链接库或者可执行文件都有一个动态符号哈希数据表,RPATH就指向该文件相关动态链接库所存放的位置。对于安装软件来说,一个重要的点就是让软件知道去哪儿找它需要的动态链接库。

通过linux自带的一些查看2进制符号列表的工具(如readelf,nm等),可以查看到CMake首先把源文件编译成可执行文件,此时的可执行文件中的RPATH符号指向的未安装前,库所在的位置。而执行完make install之后,新的可执行文件的RPATH符号居然指向了被安装的位置。也就是说,在安装的过程中,可执行文件不仅仅是被拷贝了一下,而且还修改了RPATH符号所指向的位置。对此,我感到十分的好奇,于是下决心一探究竟!

2.编译的时候指定run-time定位动态链接库机制

仔细搜索cmake的工程编译目录,发现在这个目录的好几层子目录下为每一个源文件都配置了一个目录(目录名为<source>.dir)。在这个目录里配置了编译,链接这个源文件等等的所有信息。这个目录里就有一个link.txt文件,这个文件内容定义了该源文件的连接方法。

在查阅link.txt的文件内容时,我发现了这样一个关键的link信息:-Wl,rpath,<path_to_library>。rpath的参数是显式地(explicit)告诉linker在对目标文件做链接时,让其在run-time时到指定的目录下去寻找shared库。

那么问题就更奇怪了,因为link.txt中通过rpath指定的位置是编译工程目录下的库所在位置。这么做是可以理解的。因为编译工程目录下的/bin,/lib,/include等等也确实是应该组成一个系统,从而编译调式。/bin中的可执行文件如果指定另外一个地方为链接库目标位置,这会导致调试困难。问题就来了,当我执行make install把这些/bin,/lib等安装到另外一个位置后,/bin中的可执行文件又是去哪儿找动态链接库的呢?

使用readelf指令可以查看elf可执行文件格式的内容。使用readelf-d <object_file_name>可以查看可执行文件内部的所有动态符号。使用readelf-d <object_file_name> | grep rpath命令就可以查看到这个elf可执行文件的rpath配置了。

接着,用这个指令分别查看编译工程目录下的/bin/<executable_file>和安装目录下的/bin/<executable_file>。我发现了一下出乎意料的东西。

工程目录下的/bin/<executable_file>和安装目录下的/bin/<executable>的这两个rpath符号内容是不一样的!工程目录下/bin/<executable_file>的rpathsymbol指向了工程目录下的/lib,而安装目录下/bin/<executable_file>的rpathsymbol指向了安装目录下的/lib。

看来makei nstall不仅仅是把编译工程目录的合适的文件放到安装目录下,而且还对这些二进制文件动了一下小手脚!

3 make install的秘密

为了弄清楚cmake生成的Makefile在执行makeinstall时发生了什么,我打开了编译工程目录的Makefile文件,找到install目标的recipe。这个recipe除了生成preinstall依赖之外,还调用了编译工程目录下的cmake_install.cmake文件。调用指令为:/usr/bin/cmake-P cmake_install.cmake。(我已经查阅了此recipe下的所有其他相关信息,那些信息都没有找到有价值的东西)

在cmake_install.cmake下,有递归的调用了一些子目录的cmake_install.cmake。深入这些子目录,我终于发现了关键的代码:

file(RPATH_CHECK

FILE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/<executable_file>"

RPATH "<path_to_installing_directory>")

以及:

file(RPATH_CHANGE

FILE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/<executable_file>"

OLD_RPATH "<path_to_compiling_project_directory>/bin"

NEW_RPATH "<path_to_installing_directory>")

从字面上来理解,这一段RPATH的处理机制肯定实现了二进制文件内rpath动态符号内容的修改。查阅更多的资料,发现了以下这段描述:

通过CMake,软件的开发者可以有全权的控制可执行文件或者共享链接库的RPATH变量。开发者可以通过各种各样的目标属性来调整,具体如何做可以参考SET_TARGET_PROPERTIES()文档。

在CMake2.6以前,CMake必须在可执行文件(或者软件库)安装的时候重新链接一下来修改PRATH所指向的位置。而CMake2.6以后可以在安装前就链接好,而在安装的时候可以直接修改链接好的2进制文件的符号哈希列表。

到此,疑惑终于解开。(本文为头条号作者原创)

推荐阅读