我可以调用带有错误签名的dlsym()导入的函数,为什么?

时间:2012-09-24 20:40:15

标签: c++ casting shared-libraries void-pointers dlsym

host.cpp有:

int main (void)
{
    void * th = dlopen("./p1.so", RTLD_LAZY);
    void * fu = dlsym(th, "fu");

    ((void(*)(int, const char*)) fu)(2, "rofl");

    return 0;
}

p1.cpp有:

#include <iostream>

extern "C" bool fu (float * lol)
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

(我故意将错误检查出来)

当执行主机时,“fuuuuuuuu !!!”被正确打印,即使我将带有完全不同功能签名的符号的void指针类型化。

为什么会发生这种情况,并且不同编译器之间的这种行为是否一致?

4 个答案:

答案 0 :(得分:5)

因为void指针中没有关于函数签名的信息。或者除了地址之外的任何信息。如果你开始使用参数,那么你可能会遇到麻烦。

答案 1 :(得分:5)

发生这种情况是因为UB,这种行为与任何事情都不一致,无论出于何种原因。

答案 2 :(得分:2)

这实际上并不是一个非常好的例子,可以创建一个失败的案例,因为:

  1. 您永远不会使用函数fu中的参数。
  2. 您的函数fu具有较少的参数(或激活框架本身的内存占用空间较小),而不是您要投射的函数指针类型,因此您永远不会最终fu尝试在调用者设置的激活记录之外访问内存的情况。
  3. 最后,您所做的仍然是未定义的行为,但您没有做任何事情来创建可能导致问题的违规,因此它最终会成为无声错误。

      

    不同编译器之间的这种行为是否一致?

    没有。如果您的平台/编译器使用了一个需要被调用者来清理堆栈的调用约定,那么如果调用者和调用者期望的激活记录大小不匹配,那么很可能会使用。 ..在返回被调用者时,堆栈指针将被移动到错误的位置,可能会破坏堆栈,并完全弄乱任何堆栈指针相对寻址。

答案 3 :(得分:2)

刚刚发生了,

  • C使用cdecl调用转换(因此调用者清除堆栈)
  • 您的函数不使用给定的参数参数

所以你的电话似乎才能正常工作。

但实际上行为是未定义的。更改签名或使用参数将导致程序崩溃:

ADD:

例如,考虑stdcall调用转换,其中 callee 桅杆清除堆栈。在这种情况下,即使您为调用者和被调用者声明了正确的调用转换,您的程序仍然会崩溃,因为您的堆栈将被破坏,因为被调用者将根据其签名清除它,但调用者根据另一个签名填充:

#include <iostream>
#include <string>

extern "C" __attribute__((stdcall)) __attribute__((noinline)) bool fu (float * lol) 
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

void x()
{
    (( __attribute__((stdcall)) void(*)(int, const char*)) fu)(2, "rofl");
}

int main (void)
{
    void * th = reinterpret_cast<void*>(&fu);

    std::string s = "hello";

    x();

    std::cout << s;

    return 0;
}