C++交叉编译链接踩坑实录(cmake项目构建失败)
使用cmake构建项目,在编译阶段(Building CXX object xxx.o
)全部通过,在最后一步 Linking CXX executable main
之后报了一大堆错误。
以此为例,说明链接错误时的排查方法。
错误信息
/usr/aarch64-linux-gnu/bin/ld: CMakeFiles/main.dir/proto/proto_idc/hdmap.pb.cc.o
:(.data.rel.ro._ZTVN5hdmap14DirectionPointE[_ZTVN5hdmap14DirectionPointE]+0x20):
undefined reference to `google::protobuf::Message::GetTypeName() const'
这里只节选了一小部分,但分析可看到几乎所有调用protobuf的函数都失败了。遂开始查找原因
1. 动态库是否找到
查看make的过程中是否找到了这个库。将原来的make命令换为
make VERBOSE=1
这将会打印出make过程中的详细信息,另外还可以在CMakeLists.txt中加入这句
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--verbose")
这里我是要链接protobuf库,我在链接器的输出结果中查看到
attempt to open /usr/aarch64-linux-gnu/lib/libprotobuf.so succeeded
/usr/aarch64-linux-gnu/lib/libprotobuf.so
说明找到了动态库
2. 动态库架构是否和目标一致
查看该动态库是否和目标平台属于同一个架构(比如都是x64或者aarch64)
file xxx.so # 看它属于什么架构,以及对应的符号链接地址
结果:
/usr/aarch64-linux-gnu/lib/libprotobuf.so: symbolic link to libprotobuf.so.23.0.4
通常libxxx.so
都会是一个符号链接指向真正的动态库,这时逐级查找即可
file /usr/aarch64-linux-gnu/lib/libprotobuf.so.23.0.4
结果:
/usr/aarch64-linux-gnu/lib/libprotobuf.so.23.0.4:
ELF 64-bit LSB shared object, ARM aarch64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=63750ce1a37a19e42bef701aa7e45948c38d2b21, with debug_info, not stripped
在这里查看它的架构是否属于我的目标平台,发现是正确的,继续查找问题。
3. 动态库里面有没有想要的函数
注意,要用nm -C
而不是 nm
,这样输出出来的符号更容易理解,也更好看
查看我的源代码被编译成的二进制文件里面的符号名字
nm -C CMakeFiles/main.dir/proto/proto_idc/nn_data.pb.cc.o | grep InitializationErrorString
# 查看nn_data.pb.cc.o里面所有和InitializationErrorString相关的函数
结果:
U google::protobuf::Message::InitializationErrorString() const
这里结果是U,表示这个函数还没有被实现,正常来讲这个实现应该在其他的动态库中。
然后查看protobuf里面的函数实现
nm -C /usr/aarch64-linux-gnu/lib/libprotobuf.so.23.0.4 | grep InitializationErrorString
结果:
00000000000f6138 T google::protobuf::MessageLite::InitializationErrorString[abi:cxx11]() const
00000000000f6138 t google::protobuf::MessageLite::InitializationErrorString[abi:cxx11]() const [clone .localalias]
00000000001971e0 T google::protobuf::Message::InitializationErrorString[abi:cxx11]() const
这里就发现了不同:源文件中是google::protobuf::Message::InitializationErrorString() const
,但是标准库里的函数实现是google::protobuf::Message::InitializationErrorString[abi:cxx11]() const
,因此找不到源文件函数的实现,这是导致链接报错的原因
4. 查找为什么编译出的二进制文件结果不同
使用make VERBOSE=1
查看这个文件的编译代码
结果:
/usr/bin/aarch64-linux-gnu-g++
-DCASADI_SNPRINTF=snprintf -DGFLAGS_IS_A_DLL=0 -DGLOG_USE_GLOG_EXPORT
-D_GLIBCXX_USE_CXX11_ABI=0
-I/root/idc/./include -I/root/idc/build
-isystem /usr/aarch64-linux-gnu/include/opencv4
-isystem /usr/aarch64-linux-gnu/casadi/include
-g -std=gnu++14 -MD -MT
CMakeFiles/main.dir/proto/proto_idc/io_port_ip.pb.cc.o
-MF CMakeFiles/main.dir/proto/proto_idc/io_port_ip.pb.cc.o.d
-o CMakeFiles/main.dir/proto/proto_idc/io_port_ip.pb.cc.o
-c /root/idc/build/proto/proto_idc/io_port_ip.pb.cc
注意到这里的-D_GLIBCXX_USE_CXX11_ABI=0
,这就是导致失败的元凶。
网上搜了一下,当这个宏定义为0时,代码将使用旧的C++98/C++03的ABI。这就导致编译出来的二进制文件没有带 [abi:cxx11] 后缀,导致了和库函数不同,导致了链接失败,导致了make失败。。。
5. 解决问题
那么到底为什么会出现这个编译选项?因为我自己没有设置过这个变量,所以肯定是引入某个库的时候发生了错误。我的CMakeLists.txt部分调包代码如下
find_package(OpenCV REQUIRED)
find_package(Protobuf REQUIRED)
find_package(casadi REQUIRED)
find_package(glog REQUIRED)
find_package(GTest REQUIRED)
那么大概率出现在这些库中,使用vscode打开这些库的安装目录(通常在/usr/local/lib/cmake
中,每个库会形成一个文件夹),在这个目录中搜索GLIBCXX_USE_CXX11_ABI
,发现它在casadi-config.cmake
中被设置,该文件如下:
# Config file for the CasADi package
get_filename_component(CASADI_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include("${CASADI_CMAKE_DIR}/casadi-targets.cmake")
if(ON AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
target_compile_definitions(casadi INTERFACE _GLIBCXX_USE_CXX11_ABI=0)
endif()
问题就出在这里,它添加了编译选项,而且是INTERFACE
模式。
INTERFACE
意味着宏定义将被添加到目标接口中,这意味着任何链接到casadi
的目标都会继承这个宏定义。这与PUBLIC
和PRIVATE
关键字不同,PUBLIC
会同时影响目标和链接到目标的其他目标,而PRIVATE
则只影响目标本身。(复制自网上)
因此解决方法是:把ON换成OFF,重新make,问题解决。