我可以从堆栈中间弹出吗?

clo*_*0rk 6 x86 assembly callstack abi red-zone

在x86汇编语言中:

我假设我有一个正常的功能序言,请阅读

push ebp
mov  ebp,esp
Run Code Online (Sandbox Code Playgroud)

我知道我可以通过访问内存目标操作数来读取或写入寄存器,假设我想要第一个参数。我会做

mov eax,[ebp +8]
Run Code Online (Sandbox Code Playgroud)

从堆栈中获取一个整数参数。

那为什么我不直接使用stackpointer呢?

add  esp,8              ; point ESP at the data we want
pop  eax
sub  esp,12             ; restore ESP to its original position
Run Code Online (Sandbox Code Playgroud)

这会导致错误吗?在任何情况下都使用吗?

我当然知道第一个操作的大小较小,因为它只是一个操作码,mov而不是三个,但这不是问题的重点。

(编者注:mov eax, [ebp+8]。在x86机器代码的3字节的指令 add/ subESP,imm8指定为3个字节的每个,pop eax是1个字节。
mov eax, [esp+8]是一个4字节的指令:不像在16位寻址模式,ESP可以是一个基址寄存器。但是它确实需要一个SIB字节来对其进行编码。
这些都是现代CPU上的单联指令,不包括额外的堆栈同步联指令。)

为什么这样做是错误的做法?

Joh*_*ica 5

您可以ESP直接使用作为指针。
但是,如果发生任何推动或弹出操作,ESP就会变成移动目标,这会使您的计算更加困难。

因此,我们将堆栈指针的副本放在EBP中,因此我们不必担心ESP更改。

但是,如果您不打算做任何更改堆栈指针的操作,那么最好使用ESP代替EBP
而且,如果您进行更改ESP,则当然可以相应地更改ESP的偏移量。

在此处输入图片说明 警告
您绝对不能这样做:

add  esp,8
mov ecx,[esp-4]     //never access data outside the actual stack.
pop  eax
sub  esp,12
Run Code Online (Sandbox Code Playgroud)

请记住,中断随时可能发生。
该中断将假定可以更改堆栈指针以下的任何内容。如果手动增加堆栈指针,然后访问它下面的数据,就像它仍在堆栈中一样,您可能会发现那里的数据已被中断处理程序(Oops)取代。

规则:ESP以北的任何东西都是安全的,ESP以南的任何东西都被标记为死亡,
这就是例程创建的原因stack frame。通过降低堆栈指针(记住堆栈变小),可以保护一个内存区域,因为它现在位于堆栈内部。
堆栈的语义意味着,ESP之上的任何数据都是安全的,ESP之下的任何数据都是公平的游戏。

如果您通过
A 违反了这两个原则之一,则A-使用非固定的ESP作为基本指针,或者
B 违反了ESP 之下的数据。
您将冒以下风险:A:损坏他人的数据或B:自己使用损坏的数据。

这是不好的做法吗?

add esp,8 //equivalent to pop anyreg, pop anyreg pop eax //pop from the (new) top of the stack. sub esp,12 //reset the stack back to where is was.

是!这不好

如果sub esp,12在更改此堆栈空间位中存储的3个整数之前发生中断,则将导致应用程序中的数据损坏。

请使用以下代码。

mov eax,[esp+8]
Run Code Online (Sandbox Code Playgroud)

这段代码是:A:安全,B:更快,C:不破坏标志寄存器,D:较短,E:编码较少的字节。

有关添加/
订阅的注释如果您在FLAGS中有一些有用的东西,则可以通过使用LEA添加来避免破坏它。如果不是,则add/ sub至少一样快(例如,在某些主流CPU上的更多执行端口上运行,而LEA只能在Ryzen,Haswell及更高版本的4个整数ALU执行单元中的2个上运行)。两种方式都没有代码大小优势。

lea esp,[esp+8] == add esp,8 (but without altering the flags).  

lea edx, [esp+8]    ; copy-and-add replacing mov + add is very useful
Run Code Online (Sandbox Code Playgroud)

当它可以代替2条或更多条其他指令时,一定要使用LEA,而不仅仅是用来代替添加/订阅,除非您有保留FLAGS的用途。

  • 对于用户空间(而不是内核代码),任何“ ESP南部”都可以绝对安全-当IRQ发生时,CPU切换到其他堆栈。EBP通常用作“帧指针”,以使其更容易编写能够确定堆栈帧的调试器。并且(某些调试器除外)最好不要将EBP用作“帧指针”,而仅将其用作另一个通用寄存器(特别是对于32位80x86,其中通用寄存器的数量非常有限)。 (3认同)
  • @ Ped7g:我更喜欢涵盖所有可能性,并且发现那些被教导像编译器一样编写汇编程序的人很少会从“汇编程序必须受到完全不同的语言的限制所限制”的最初假设中恢复过来。另请注意,“ RSP南部”的使用已成为至少一项现代调用约定(AMD64 ABI的“红色区域”)的正式组成部分。 (3认同)
  • @Brendan我不认为OP确实指定了目标平台,因此,如果他正在运行一些奇怪的32b模式设置(例如他自己的自定义OS),则他可能具有共享堆栈。;)IMO以这种经典方式首先学习它们并没有什么害处,之后首先介绍常见的x86怪癖和便利性,原则,之后可能会在其他平台上遇到问题。 (2认同)