cmake相对路径载入so文件和rpath
1. so文件
在一个c++的项目中,将一些模块生成so文件是很常见的 一种技术,so文件即linux系统
下的动态链接库文件,对应windows下是dll文件。使用so文件,可以更好的管理整个项目。在linux系统中,可执行文件的执行时会自动去搜索一些固定的文件夹来尝试载入可执行文件所使用的so文件,如环境变量LD_LIBRARY_PATH里面包含的目录,/lib目录,/usr/lib目录。但是按照正常的项目需求,不太可能会把项目本身的so文件部署在实际的运行机器的/lib目录或者/usr/lib目录,正常都是把动态库文件与项目文件放在一起,一般项目目录下面会建立一个lib文件夹,然后把库文件放在里面。于是就衍生这样子的一个需求,如何使可执行文件按照相对路径来载入so文件。
2. rpath
rpath 是 runtime library search path 的缩写,通过使用rpath,可以实现系统在载入动态库文件的时候,自动的去搜索rpath指定的路径,使用rpath可以实现我们的需求。
3. 实际代码
Talk is cheap,show me your code.
3.1 没有使用rpath
代码所在系统环境是Ubuntu 18.04
目录文件如下
.
├── hello.cpp
├── hello.h
├── libhello.so
├── test
└── test.cpp
代码链接
其中代码分为两个部分,test.cpp用来编译可执行文件,hello.cpp用来编译动态库文件。
test.cpp
#include <string>
#include <iostream>
#include "hello.h"
using namespace std;
int main(){
hello a;
cout<<a.show()<<endl;
string b;
cin >> b;
return 1;
}
hello.h
class hello{
public:
int show();
};
hello.cpp
#include "hello.h"
int hello::show(){
return 1;
}
然后使用g++命令编译动态库
g++ -shared -o libhello.so hello.cpp
该命令会生成一个libhello.so文件,lib是linux系统默认的动态库文件的开头,然后编译test.cpp文件
g++ -o test test.cpp -lhello -L ./
-l的意思是链接某个库进行编译,-lhello 就是要搜索libhello.so文件。因为我们的libhello.so文件不在/lib或者/usr/lib下,所以必须指定搜索的路径。-L 就是指定搜索库文件的目录,上述命令是指定在当前目录。执行完成后,生成了test可执行文件。使用 ./test 命令运行该文件
./test
./test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
可以看到,文件执行失败,linux在载入libhello.so时进行了报错。使用 readelf -d test 查看文件的动态库
readelf -d test
Dynamic section at offset 0x1d40 contains 30 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libhello.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0xb18
0x000000000000000d (FINI) 0xe84
0x0000000000000019 (INIT_ARRAY) 0x201d28
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x201d38
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x298
0x0000000000000005 (STRTAB) 0x548
0x0000000000000006 (SYMTAB) 0x2d8
0x000000000000000a (STRSZ) 709 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x201f60
0x0000000000000002 (PLTRELSZ) 240 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0xa28
0x0000000000000007 (RELA) 0x8d8
0x0000000000000008 (RELASZ) 336 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x848
0x000000006fffffff (VERNEEDNUM) 3
0x000000006ffffff0 (VERSYM) 0x80e
0x000000006ffffff9 (RELACOUNT) 4
0x0000000000000000 (NULL) 0x0
可以看到该可执行文件依赖的动态库,并且没有rpath。
3.2 g++使用rpath
为了解决上面载入so文件报错的文件,我们可以在编译test时加入rpath。使用命令
g++ -o test test.cpp -lhello -L ./ -Wl,-rpath=./
-Wl标签是告诉g++将后面的参数传递给链接器,可以指定rpath为可执行文件所在的目录为库文件的搜索目录,此时,将test文件和libhello.so文件复制到任务一个文件夹内,运行test文件都可以正确的载入libhello.so。使用 readelf -d test 查看文件的动态库
readelf -d test
Dynamic section at offset 0x1d30 contains 31 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libhello.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [./]
0x000000000000000c (INIT) 0xb18
0x000000000000000d (FINI) 0xe84
0x0000000000000019 (INIT_ARRAY) 0x201d18
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x201d28
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x298
0x0000000000000005 (STRTAB) 0x548
0x0000000000000006 (SYMTAB) 0x2d8
0x000000000000000a (STRSZ) 712 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x201f60
0x0000000000000002 (PLTRELSZ) 240 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0xa28
0x0000000000000007 (RELA) 0x8d8
0x0000000000000008 (RELASZ) 336 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x848
0x000000006fffffff (VERNEEDNUM) 3
0x000000006ffffff0 (VERSYM) 0x810
0x000000006ffffff9 (RELACOUNT) 4
0x0000000000000000 (NULL) 0x0
可以看到可执行文件多了一个RUNPATH属性,对应的值是./,即我们刚才设置的rpath。
3.3 在cmake中使用rpath
在目录下增加一个CMakeLists.txt文件和build目录。
CMakeLists.txt文件内容如下
cmake_minimum_required(VERSION 3.0.0)
project(rpathTest)
#设置可执行文件输出
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#设置库文件输出
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/lib)
#生成共享库
add_library(hello SHARED hello.cpp)
#设置RPATH
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
#生成可执行文件
add_executable(rpathTest test.cpp)
#设置依赖的库文件
target_link_libraries(rpathTest hello)
#设置RPATH路径
set_target_properties(rpathTest PROPERTIES INSTALL_RPATH "$ORIGIN/lib")
然后使用下列命令
cd build
cmake ..
make
可以看到,项目已经成功编译完成。然后在项目目录下的bin文件夹中,可以看到可执行文件rpathTest和一个lib文件夹。使用./rpathTest命令即可运行测试程序,可以看到程序已经在正常运行。使用 readelf -d rpathTest查看文件的动态库
readelf -d rpathTest
Dynamic section at offset 0x1d30 contains 31 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libhello.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/lib]
0x000000000000000c (INIT) 0xc30
0x000000000000000d (FINI) 0xfa4
0x0000000000000019 (INIT_ARRAY) 0x201d18
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x201d28
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x298
0x0000000000000005 (STRTAB) 0x618
0x0000000000000006 (SYMTAB) 0x300
0x000000000000000a (STRSZ) 769 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x201f60
0x0000000000000002 (PLTRELSZ) 240 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0xb40
0x0000000000000007 (RELA) 0x9f0
0x0000000000000008 (RELASZ) 336 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x960
0x000000006fffffff (VERNEEDNUM) 3
0x000000006ffffff0 (VERSYM) 0x91a
0x000000006ffffff9 (RELACOUNT) 4
0x0000000000000000 (NULL) 0x0
可以看到,已经设置rpath为$ORIGIN/lib,$ORIGIN指的是可执行文件所在的文件夹。