linux库的链接问题
最近调试OpenSSL的证书验证功能,遇到了一个诡异的linux库的链接问题。
大背景如下,产品库 libtp.so 需要对下载的CA根证书做校验。在调用OpenSSL的某个校验函数时居然失败了。通过GDB跟踪OpenSSL源码,在调用函数 EVP_get_digestbyname 获取 SHA256 哈希算法的时候出现了问题。该函数理应返回 SHA256 算法的描述结构,但是调用后却返回了空指针。拿不到算法的描述信息,校验自然会失败。
首先怀疑的是,OpenSSL没有正确初始化。在使用OpenSSL库之前需要调用 OPENSSL_add_all_algorithms_noconf 做初始化。 检查代码,然后GDB跟踪,初始化一切正常。
于是,对函数 EVP_get_digestbyname 下断点。该函数仅调用 OBJ_NAME_get 获取数据。此外,算法描述信息被存放在一个哈希表中,变量 names_lh 指向这个哈希表。 函数 OBJ_NAME_get 就是从这个哈希表里面拿数据。根据变量 names_lh 的值也能证明OpenSSL库已经初始化完毕。
|
|
接着step进入函数 OBJ_NAME_get,再次打印变量 names_lh。期间没有其他赋值操作发生(也可以肯定其他线程不会调用OpenSSL库),变量 names_lh 的值居然变成了空,而且变量的地址也发生了变化。 要知道,这可是一个全局静态变量,一旦进程运行,它的地址不可能改变!而且变量 names_lh 值为空说明OpenSSL库没有被初始化。。。
|
|
只有一个解释,我们的进程内部有两份相同的OpenSSL源代码。初始化和获取数据发生在不同的源代码中。我们可以对比初始化和获取数据时的调用栈来证实这一点。
这个是初始化过程的调用栈。
|
|
这个是校验时候的调用栈。(略去了调用参数)
|
|
然后利用 ps aux + grep 找到进程的pid,比如8499。打开/proc/8499/smaps,可以看到进程的虚拟地址空间详细信息。其中,我们只关心上面两个调用栈在其中的位置。两串地址分别分布在如下两个虚拟地址段中。
|
|
第一个 libgch.so 是产品的核心库文件,第二个 libcrypto.so 是OpenSSL的库文件。问题来了,产品的库文件里面怎么会有OpenSSL的源代码?
检查编译配置,OpenSSL通过静态库方式链接到了 libgch.so 。因此生成的 libgch.so 包含了OpenSSL的源代码。静态库链接时,如果静态库里面某个 .o 文件完全没有用到,那么这个 .o 中的函数是不会被放入目标文件中。
本案中, libgch.so 有调用 OPENSSL_add_all_algorithms_noconf 初始化OpenSSL(发送HTTPS需要用到)。但是校验函数 CMS_verify 却没有用到。因此静态链接时前者被链入 libgch.so ,后者则不在其中。其后将 libgch.so 和 libtp.so 一起链接到主程序的时候,对于 libtp.so 中引用的 OPENSSL_add_all_algorithms_noconf 优先在 libgch.so 中找到(链接时libgch.so排在OpenSSL库的前面)。而校验函数 CMS_verify 则只能链接到OpenSSL的库 libcrypto.so 中。因此就出现了这么个奇怪的问题。
解决方案 & 总结:很简单,都用动态库。自己挖的坑自己填。