如果在调用 DLL 中的函数时没有传递足够的参数,会发生什么情况?

If I do not pass enough parameters when calling a function in a DLL, what will happen?

本文关键字:参数 什么情况 DLL 调用 函数 如果      更新时间:2023-10-16

在dll项目中,函数是这样的:

extern "C" __declspec(dllexport) void foo(const wchar_t* a, const wchar_t* b, const wchar_t* c)

在不同的项目中,我将使用foo函数,但我在头文件中声明foo函数

extern "C" __declspec(dllimport) void foo(const wchar_t* a, const wchar_t* b)

我只用两个参数调用它。

结果是成功,我认为这是__cdecl电话,但我想知道这是如何以及为什么工作的。

32 位

默认调用约定是__cdecl,这意味着调用方将参数从右到左推送到堆栈上,然后在调用返回后清理堆栈。

因此,在您的情况下,调用方:

  1. 推 b
  2. 推送一个
  3. 推送返回地址
  4. 调用函数。

此时堆栈如下所示(例如,假设有 4 个字节的指针,请记住,当您推动事物时堆栈指针向后移动):

+-----+ <--- this is where esp is after pushing stuff
| ret | [esp]
+-----+
|  a  | [esp+4]
+-----+
|  b  | [esp+8]
+-----+ <--- this is where esp was before we started
| ??? | [esp+12 and beyond]
+-----+

好的,太好了。现在问题发生在被叫方端。被调用方期望参数位于堆栈上的某些位置,因此:

  • 假定a处于[esp+4]
  • 假定b[esp+8]
  • 假设c处于[esp+12]

这就是问题所在:我们不知道[esp+12]是什么。因此,被调用者将看到正确的ab值,但会将恰好在[esp+12]处的任何未知垃圾解释为c

在这一点上,它几乎是未定义的,并且取决于您的函数实际对c做什么。

在所有这些结束并且被调用方返回后,假设您的程序没有崩溃,调用方将恢复esp并且堆栈指针将返回到应有的位置。因此,从调用方的 POV 来看,一切可能都很好,堆栈指针最终回到了应有的位置,但被调用方看到了c垃圾。

<小时 />

64 位

64 位机器上的机制不同,但最终结果大致相同。Microsoft 在 64 位计算机上使用以下调用约定,而不考虑__cdecl或任何约定(您指定的任何约定都将被忽略,并且所有约定都以相同的方式处理):

寄存器
  • 中的前四个整数或指针参数按从左到右的顺序依次排列在寄存器rcxrdxr8r9
  • 寄存器
  • 中的前四个浮点参数按从左到右的顺序排列在寄存器xmm0xmm1xmm2xmm3
  • 剩余的任何内容都会从右到左被推到堆栈中。
  • 调用方负责恢复esp以及在调用后恢复所有易失寄存器的值。

因此,在您的情况下,调用方:

  1. a放在rcx
  2. .
  3. b放在rdx.
  4. 在堆栈上分配额外的 32 字节"影子空间"(请参阅 MS 文章)。
  5. 推送返回地址。
  6. 调用函数。

但被叫方期待:

  • a假设在rcx(检查!
  • b假设在rdx(检查!
  • c假定在r8(问题)

因此,与 32 位情况一样,被调用方将r8中发生的任何内容解释为c,随之而来的是潜在的 hijink,最终效果取决于被调用方如何处理c。当它返回时,假设程序没有崩溃,调用者恢复所有易失性寄存器(rcxrdx,通常还包括r8和朋友)并恢复esp

相关文章: