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的目标都会继承这个宏定义。这与PUBLICPRIVATE关键字不同,PUBLIC会同时影响目标和链接到目标的其他目标,而PRIVATE则只影响目标本身。(复制自网上)

因此解决方法是:把ON换成OFF,重新make,问题解决。

类似文章

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注