【问题标题】:Function pointers and unknown number of arguments in C++C ++中的函数指针和未知数量的参数
【发布时间】:2011-01-23 17:22:31
【问题描述】:

我遇到了以下奇怪的代码块。假设您有以下 typedef:

typedef int (*MyFunctionPointer)(int param_1, int param_2);

然后,在一个函数中,我们尝试通过以下方式从 DLL 中运行一个函数:

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

问题是,我们无法知道我们通过 LoadLibrary 获得地址的函数是否接受两个整数参数。dll 名称由用户在运行时提供,然后列出导出函数的名称并且用户选择一个进行测试(同样,在运行时 :S:S )。 那么,通过最后一行的函数调用,我们不是打开了可能的堆栈损坏的大门吗?我知道这可以编译,但是如果我们将错误的参数传递给我们指向的函数,会发生什么样的运行时错误?

【问题讨论】:

  • 如果您不确定 dll 导出了哪些确切的函数(确切的类型,但不是调用约定),请使用 Dependency Walker。

标签: c++ dll function-pointers loadlibrary


【解决方案1】:

如果预期和使用的参数数量或类型以及调用约定不同,我可以想到三个错误:

  • 如果调用约定不同,将读取错误的参数值
  • 如果函数实际上需要比给定更多的参数,则将使用随机值作为参数(如果涉及指针,我会让你想象后果)
  • 在任何情况下,返回地址都将是完全垃圾,因此函数一返回就会运行带有随机数据的随机代码。

两个字:Undefined behavior

【讨论】:

    【解决方案2】:

    恐怕没有办法知道——程序员在获取函数指针并使用它时,需要事先知道原型。

    如果您事先不知道原型,那么我猜您需要使用 DLL 实现某种协议,您可以通过调用 DLL 中的已知函数来枚举任何函数名称及其参数。当然,DLL需要写成符合这个协议。

    【讨论】:

    • 这不是我的意思:我知道我不应该这样调用函数,但它到底会导致什么样的错误呢?它是可以处理的,还是我只是要弄乱我的调用堆栈并使应用程序崩溃。
    • 你无法处理它。如果函数是C函数,你可能不会弄乱堆栈,但参数会错误,这当然可能导致崩溃。
    【解决方案3】:

    如果它是一个 __stdcall 函数并且他们已经完整地保留了名称修改(两个很大的如果,但肯定有可能)名称将有 @nn 在最后,nn 是一个数字。该数字是函数期望作为参数的字节数,并将在返回之前清除堆栈。

    因此,如果这是一个主要问题,您可以查看函数的原始名称并检查您放入堆栈的数据量是否与它将从堆栈中清除的数据量相匹配。

    请注意,这仍然只是对墨菲的保护,而不是对马基雅维利的保护。创建 DLL 时,可以使用导出文件来更改函数的名称。这经常被用来去除名称修饰——但我很确定它也会让你将函数从 xxx@12 重命名为 xxx@16 (或其他),从而误导读者它期望的参数。

    编辑:(主要是回复 msalters 的评论):确实,您不能将 __stdcall 应用于成员函数之类的东西,但您当然可以将它用于诸如全局函数之类的东西,无论它们是用 C 还是C++。

    对于成员函数之类的东西,函数的导出名称将被破坏。在这种情况下,您可以使用UndecorateSymbolName 获取其完整签名。使用它有点不简单,但也不是非常复杂。

    【讨论】:

    • 问题标记为 C++,而不是 C。但您描述了 C 函数的名称修饰。
    【解决方案4】:

    我不这么认为,这是个好问题,唯一的规定是你必须知道函数指针工作的参数是什么,如果你不知道并且盲目地填充参数并调用它,它会崩溃或跳进树林再也见不到了......由程序员传达有关函数期望的信息和参数类型的消息,幸运的是你可以反汇编它并通过查看堆栈指针来找出答案并通过“堆栈指针”(sp)查找参数类型的预期地址。

    以 PE Explorer 为例,您可以找出使用了哪些函数并检查反汇编转储...

    希望这会有所帮助, 最好的祝福, 汤姆。

    【讨论】:

      【解决方案5】:

      它会在 DLL 代码中崩溃(因为它传递了损坏的数据),或者:我认为 Visual C++ 在调试版本中添加代码来检测此类问题。它会说类似:“ESP 的值没有在函数调用中保存”,并将指向调用附近的代码。它有帮助,但并不完全健壮——我认为它不会阻止你传递错误但大小相同的参数(例如 int 而不是 x86 上的 char* 参数)。正如其他答案所说,你只需要知道,真的。

      【讨论】:

      • 这将非常有用。有什么方法可以检测到问题而不出现错误屏幕?因为如果是这样的话,我可以在一定程度上解决这个问题。我并不担心传递相同大小的错误参数,只要我避免函数外的堆栈损坏。
      • 不知道自己检测;我最好的猜测是弄清楚调试版本的作用,并添加您自己的内联程序集以在发布版本中执行相同的检查。不过听起来很难。
      【解决方案6】:

      没有普遍的答案。该标准要求在某些情况下抛出某些异常,但除此之外,还描述了如何执行符合标准的程序,并且有时说某些违规必须导致诊断。 (这里或那里可能有更具体的东西,但我当然不记得了。)

      代码在做什么不符合标准,并且由于有演员表,编译器有权继续做程序员想做的任何愚蠢的事情,而不会抱怨。因此,这将是一个实施问题。

      您可以查看您的实现文档,但它可能也不存在。您可以试验或研究如何在您的实现中完成函数调用。

      不幸的是,答案很可能是它会在不立即显而易见的情况下搞砸一些事情。

      【讨论】:

        【解决方案7】:

        通常,如果您调用 LoadLibrary 和 GetProcByAddrees,则您有文档告诉您原型。更常见的是,所有的 windows.dll 都为您提供了一个头文件。虽然如果错误会导致错误,但它通常很容易观察到,而不是那种会潜入生产环境的错误。

        【讨论】:

        • 我有理由相信写这篇文章的人除了函数名之外没有任何关于被调用函数的信息。所以,如果我们确实传递了错误数量的参数,会发生什么?
        • 任何事情都可能发生。如果函数需要三个 args 或一个字符串和一个 int 怎么办?使用错误数量/类型的参数调用函数是未定义的。
        【解决方案8】:

        大多数 C/C++ 编译器让调用者在调用之前设置堆栈,然后重新调整堆栈指针。如果被调用的函数不使用指针或引用参数,则不会出现内存损坏,尽管结果将毫无价值。正如 rerun 所说,指针/引用错误几乎总是出现在少量测试中。

        【讨论】:

        • 嗯,我认为对于 stdcall,在函数调用后重新调整堆栈是被调用者的责任。因此,如果我们传递错误数量的 args,那不会抛出 SP ?
        • 没有。我是普通的 C/C++ 编译器,调用者负责。这就是像 printf 这样的可变参数函数可以以合理的安全程度工作的方式。
        • 是的,但话又说回来,据我所知,大多数 C/C++ 编译器的默认调用约定是 _cdecl,而不是 _stdcall
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-07
        • 1970-01-01
        • 2016-03-14
        • 2022-01-16
        • 2012-06-17
        • 2015-04-18
        相关资源
        最近更新 更多