dlsym() + RTLD_NEXT在Ubuntu 20.04上无法按预期工作。

3

在使用 dlsym() 调用时,我面对着 Ubuntu 20.04(gcc 版本为 9.3.0)上的奇怪运行时行为。

请看下面的简单示例:

  • 文件 test.cpp
#include <iostream>
#include <dlfcn.h>
#include <execinfo.h>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>

extern "C"
{
    void __cxa_throw(void *ex, void *info, void (*dest)(void *))
    {
        std::cout << "__cxa_throw() invoked \n";

        static void (*const rethrow)(void *, void *, void (*)(void *)) __attribute__((noreturn))
            = (void (*)(void *, void *, void (*)(void *)))dlsym(RTLD_NEXT, "__cxa_throw");

        std::cout << "addr in lib=" << &rethrow << "\n";

        rethrow(ex, info, dest);

        std::terminate();
    }
}

  • 文件 main.cpp:
#include <iostream>

void foo()
{
  throw std::runtime_error("error");
}

int main()
{
    foo();
    return 0;
}

请按照以下步骤构建这2个文件:

g++ -fPIC -std=c++17 test.cpp -g -c -o test.o
g++ -shared ./test.o -o libtest.so
g++ main.cpp -std=c++17 -g -pedantic -L./ -ltest -ldl

将 ldd 命令传递给 ./a.out,将会得到以下结果:

ldd a.out 
    linux-vdso.so.1 (0x00007ffe01186000)
    /usr/local/lib/AppProtection/libAppProtection.so (0x00007f1dbd738000)
    libtest.so (0x00007f1dbd733000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f1dbd708000)
    libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1dbd526000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1dbd50b000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1dbd319000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f1dbd2f4000)
    libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007f1dbd1b7000)
    libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f1dbd18d000)
    libXi.so.6 => /lib/x86_64-linux-gnu/libXi.so.6 (0x00007f1dbd17b000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f1dbd964000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1dbd02c000)
    libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007f1dbd024000)
    libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f1dbd01c000)
    libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007f1dbd007000)
    libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f1dbcfed000)


我们可以看到在libstdc++.so之前,libtest.so被解析出来了。 我期望代码的工作方式如下:
  • __cxa_throw()在两个共享库中都有定义:libtest.solibstdc++.so
  • 当在foo()调用中抛出异常时,__cxa_throw将从libtest.so中解析并调用。
  • 调用dlsym(RTLD_NEXT, "__cxa_throw");会在共享库列表中进一步查找__cxa_throw,并在libstdc++.so中找到并调用。
在所有平台上都按预期工作,除了Ubuntu 20.04,在那里rethrow引用了libtest.so中的__cxa_throw(但不是libstc++.so),从而导致无限递归。 请协助解决运行时行为问题。

你尝试过在调用 dlsym( RTLD_NEXT, ... ); 之前先调用 dlsym( RTLD_DEFAULT, "__cxa_throw" ); 吗? - Ingo Leonhardt
1
/usr/local/lib/AppProtection/libAppProtection.so 是什么?在没有 dlsym() 问题的系统上是否存在?您可能想查看您正在调用哪个 dlsym()... - Andrew Henle
在我的Ubuntu 20.04上可以工作。尝试执行sudo apt-get purge icaclient并重新启动,它是否能够正常工作?如果您在新安装的Ubuntu 20.04上尝试,它是否能够正常工作? - Joseph Sible-Reinstate Monica
卸载 icaclient 很有帮助。这是由 Citrix 客户端制作的非常意外的钩子。感谢您的帮助! - drus
2个回答

4
Citrix ICA客户端的“应用程序保护”组件安装了库/usr/local/lib/AppProtection/libAppProtection.so,并将其添加到/etc/ld.so.preload中,导致它被加载到每个动态链接的进程中。这个库取代了dlsym函数,实现了其他功能。(如果你好奇这不会一直陷入无限循环是如何实现的,请参见How can I intercept dlsym calls using LD_PRELOAD?。实际上,看起来Citrix的代码可能是直接从那个答案中复制粘贴过来的。) 问题在于,由于RTLD_NEXT依赖于能够检查返回地址,所以需要特别小心地避免在钩住dlsym时破坏它,但他们没有采取这种特殊的小心。结果,RTLD_NEXT将在libAppProtection.so之后的下一个库中查找符号,而不是在你的代码之后的下一个库中查找,这就导致了你遇到的问题。
以下是对此该做什么的一些选择:
  • 向 Citrix 支持投诉,直到他们修复了他们有问题的库
  • sudo apt-get purge icaclient (可选:之后重新安装,但在被询问应用程序保护组件时选择“否”
  • 修改您的程序以加载来自libdl.so的真正dlsym,然后对使用RTLD_NEXT的任何调用使用它

2
THB,我非常惊讶地发现Citrix客户端钩子dlsym,而且这个钩子本身做错了工作。非常感谢您的回答! - drus

1
正如评论中所提到的,该问题是由 Citrix ICA 客户端钩子引起的,它钩取了 dlsym() ,从而导致调用错误的 dlsym()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接