7

我遇到了以下奇怪的代码块。想象一下你有以下 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 )。那么,通过最后一行的函数调用,我们不是打开了可能的堆栈损坏的大门吗?我知道这可以编译,但是如果我们将错误的参数传递给我们指向的函数,会发生什么样的运行时错误?

4

8 回答 8

4

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

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

于 2010-03-04T14:58:41.893 回答
4

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

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

用两句话来说:Undefined behavior

于 2010-03-04T15:38:14.367 回答
3

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

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

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

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

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

于 2010-03-04T15:21:57.090 回答
1

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

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

希望这会有所帮助,最好的问候,汤姆。

于 2010-03-04T15:02:38.483 回答
1

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

于 2010-03-04T15:15:00.847 回答
1

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

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

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

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

于 2010-03-04T15:43:45.713 回答
0

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

于 2010-03-04T15:01:19.420 回答
0

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

于 2010-03-04T15:06:23.483 回答