【问题标题】:How is the stack set up in a Windows function call?Windows 函数调用中的堆栈是如何设置的?
【发布时间】:2017-03-16 13:05:14
【问题描述】:

首先,我想说我有足够的汇编背景,可以理解成为函数式汇编程序员所需了解的大部分内容。不幸的是,我不明白 Windows API 调用在返回地址方面是如何工作的。

这是一些用 GAS 汇编编写的 Windows 示例代码,使用 MinGW 作为汇编器,MinGW 的 ld 作为链接器...

    .extern _ExitProcess@4
    .text
    .globl _main
_main:
     pushl $0
     call _ExitProcess@4

此代码在汇编后编译运行...

as program.s -o program.o

并链接它...

ld program.o -o program.exe -lkernel32

据我了解,Windows API 调用通过推送指令获取参数,如上所示。然后在通话期间;

call _ExitProcess@4

函数的返回地址被放置在堆栈上。然后,这就是我感到困惑的地方,该函数将所有参数从堆栈中弹出。

我很困惑,因为堆栈是后进先出的,在我看来,当弹出堆栈上的参数时,它会首先弹出返回地址。首先是参数,然后是返回地址,因此从技术上讲,它会首先弹出。

我的问题是,通过推送操作将参数传递给函数调用并将返回地址放在堆栈上后,堆栈的布局是什么样的?函数在执行时如何将参数和返回地址从堆栈中弹出?最后,返回地址如何出栈,函数调用又返回到返回地址中指定的地址?

【问题讨论】:

  • 不好的例子,ExitProcess() 根本不应该返回。
  • 它没有返回值,但它在堆栈上有一个返回地址。当它使用 ret 指令(我上次检查的 \xc3)返回时,它会从堆栈中弹出返回值并将执行流程定向到 Windows API,然后再次将执行定向到开始函数调用的 call 指令之后的下一条指令第一名。
  • 正如 Turbo J 所说,ExitProcess 永远不会返回。堆栈上有一个返回地址,但它从未使用过,并且该函数从不执行 RET 指令。正如函数名所说,函数导致进程退出,这意味着它不能返回给调用者,因为调用者将不再存在。
  • 哦,对不起,你是对的。试试GetStdHandle,这是一个可口的功能。它接受一个推入堆栈的参数,实际上返回。

标签: winapi assembly x86 stack


【解决方案1】:

几乎所有 Windows API 函数都使用stdcall calling convention。这就像正常的“cdecl”约定一样工作,除了你已经看到被调用的函数负责在它返回时删除参数。它使用 RET 指令执行此操作,该指令采用可选的立即操作数。该操作数是第一次弹出返回值后要弹出栈的字节数。

在 cdecl 和 stdcall 调用约定中,函数的参数不会在函数执行时从堆栈中弹出。它们留在堆栈上并使用 ESP 或 EBP 相对寻址进行访问。因此,当 ExitProcess 需要访问其参数时,它会使用 mov 4(%esp), %eaxmov 4(%ebp), %eax 之类的指令。

【讨论】:

  • 不,我的意思是如果函数的参数在返回地址之前被压入堆栈,函数如何在不先弹出返回地址的情况下弹出参数?
  • @August 在返回之前不会弹出参数。
  • @August 它会在它返回时完成所有这些操作。 RET 指令弹出堆栈的返回地址,将指令指针 (EIP) 设置为该返回地址,然后将参数从堆栈中弹出。
  • 我认为“pop”这个词具有误导性,因此会造成混淆。 “ret”实际上并没有像“pop eax”指令那样在任何地方“弹出”任何东西(当然,除了“弹出”返回地址。)。它只是将堆栈指针增加立即操作数。
  • @August 检查RET imm16 变体的详细信息。
猜你喜欢
  • 2018-03-20
  • 2013-01-30
  • 2018-05-08
  • 1970-01-01
  • 2019-11-22
  • 2013-10-12
  • 2021-06-05
  • 2014-11-13
  • 1970-01-01
相关资源
最近更新 更多