符号未定义实例一则

该示例来源于Github项目**MiniPSI的复现,该项目的编译用到了另一个Github项目MIRACL**编译的静态库文件,但是在连接的过程中总是无法链接,提示符号未定义错误。因为我对CMake文件也不是相当了解,一开始的思路就陷入到了静态库文件的位置是不是设置的有问题,后续依靠着之前看到过的C/C++编译的知识,才排查到是C++调用C的库时符号的问题。

一、问题场景

**MiniPSI**项目中,作者提供了一个一键安装脚本,但是在项目中确实了脚本中所用到的libOTe/cryptoTools/thirdparty/linux/路径部分,导致一键安装脚本失效。从项目作者的README中了解到,缺失的编译依赖内容是boost和miracl,所以就手动创建该目录,在该目录下进行依赖的编译即可。

Boost库的编译很顺利,毕竟这是一个很知名的库。MIRACL在编译的过程中则遇到了一点问题:

  • MIRACL需要扁平式解压,但是MiniPSI项目中对该项目的依赖具有项目结构:对MiniPSI项目中依赖MIRACL项目的地方修改,去除项目结构部分(头文件目录和静态库目录等等,改为使用根目录即可)。
  • 本次实验环境是CentOS,MIRACL项目的lib文件夹中包含编译Linux库文件的有三个脚本:linux、linux64和linux64_cpp,linux脚本对应着i386 32位系统环境,linux64对应着64位系统环境,linux64_cpp是C++库特供版。在项目编译中,首选的是linux64_cpp,因为MiniPSI是C++的:
    • MiniPSI中需要MIRACL的多线程版本,在MIRACL的文档中有记录,在头文件mirdef.h中添加宏定义MR_GENERIC_MT即可
    • MIRACL多线程版本只有linux、linux64两个脚本可以实现,但是无法编译一些测试文件,如果不想出现过多的报错可以在脚本文件中把测试文件的编译删除掉。C++版本的linux64_cpp不支持多线程。

二、问题描述

使用C版本的编译脚本来编译MIRACL多线程版本,形成一个静态库libmiracl.a(从miracl.a拷贝而来),给libOTE编译的时候用。

在libOTE形成可执行文件的时候(例如frontend_libOTe),这个时候就是要处理一些未定义的符号的时候了(之前的编译都是形成的静态库文件,所以未定义的符号是没有问题的),但是这个时候出现了大量的符号未定义错误:

1
2
3
4
5
6
7
**../lib/libcryptoTools.a**(Curve.cpp.o): In function `osuCrypto::EccNumber::randomize(osuCrypto::PRNG&)':
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x3a): undefined reference to `zero(bigtype*)'
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x10a): undefined reference to `mr_compare(bigtype*, bigtype*)'
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x124): undefined reference to `mr_lzero(bigtype*)'
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x139): undefined reference to `divide(miracl*, bigtype*, bigtype*, bigtype*)'
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x14d): undefined reference to `mr_compare(bigtype*, bigtype*)'
Curve.cpp:(.text._ZN9osuCrypto9EccNumber9randomizeERNS_4PRNGE+0x176): undefined reference to `mr_compare(bigtype*, bigtype*)'

经过nm命令对库文件符号的观察,缺失的符号应该是在的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  miracl git:(master) ✗ nm libmiracl.a | grep compare
0000000000002570 T mr_compare
U mr_compare
U mr_compare
U mr_compare
00000000000006f0 T zzn2_compare
U mr_compare
00000000000000a0 T zzn3_compare
U mr_compare
U mr_compare
U mr_compare
0000000000001180 T ecn2_compare
U mr_compare
U zzn2_compare
U zzn2_compare
00000000000002c0 T zzn4_compare

根据编译frontend_libOTe的输出内容,报错的应该是库文件libcryptoTools.a,去看一下库文件里对应的符号是什么:

1
2
3
➜  libOTe git:(master) ✗ nm ./lib/libcryptoTools.a | grep compare
**U _Z10mr_compareP7bigtypeS0_**
U _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKc

可以看出来库文件libcryptoTools.a里对应的符号是_Z10mr_compareP7bigtypeS0_,但是libmiracl.a库里对应的符号是mr_compare,说明是C++符号修饰的问题。

上面报未定义的错误undefined reference to zero(bigtype*)’`,我只关注到了函数名称,却没有考虑到要去对应报错的库里看具体的符号名是什么(显示出来的信息和具体库里实际符号名是不一样的),导致我一致在找编译时链接库的问题

三、问题解决

排查到C++调用C的库,那么问题就很明显了,给C库的结构上套一层宏就可以了,表明这些是C的接口,不需要符号修饰。

1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C"{
#endif
// some api define
#ifdef __cplusplus
}
#endif

四、总结

其实如果一开始就能关注到C++调用C的库会有问题,那么整体的过程会顺利很多,但是我先入为主(从输出的信息中看到函数名找不到,一直在找链接问题),认为问题大概率出现在CMake的文件上,一直在排查链接的过程。这个问题的解决过程花费了我几乎一天的时间,书上的知识想要在实际中灵活运用还是有点距离。

排查链接问题还是要自顶向下的排查:

  • 先被链接的库里的缺失的符号是什么样的(库有没有问题,符号有没有导出)
  • 查找可执行文件中对应缺失的符号是什么样的,是否与被链接库的符号一致(符号问题)
  • 查看编译的链接过程,是否没有找到对应的链接库(链接问题)