assembly - 将相同的值压入堆栈并且 ret 的行为不同
问题描述
在 x-86 中,如果您从寄存器中压入一个值(例如,%eax),然后返回,则程序将控制权转移到与 %eax 处的值对应的地址,据我所知。
在程序的另一次运行中,如果您编辑了代码,以便通过不同的方式压入堆栈(例如,取消引用寄存器,将该值移动到另一个寄存器,然后压入该寄存器),然后返回,程序也应该将控制权转移到与推送的值对应的地址吗?
在程序的两次不同运行中,如果推送的值是相等的(即使它们的推送方式不同),那么程序在每次运行中的行为方式是否相同?
我无法展示我的代码,但我想确保我的概念性思维是正确的,因为我在一种情况下会抛出错误,但在另一种情况下不会。谢谢!
例 1:
func1:
/* should push address onto stack when calling */
call func2
...
...
func2:
/* should pop address and transfer control back to func1 */
ret
例 2:
func1:
...
...
func2:
/* %eax contains value equivalent to address from the first example */
pushl %eax
/* should pop %eax and transfer control to address contained in %eax */
ret
func2 也应该返回 func1 吧?但第二个例子不起作用
解决方案
是的,您可以不匹配 call/ret 并手动执行它们(在大型性能代码中),但是如果您不想破坏任何东西,则必须正确模拟它们(包括它们对堆栈指针所做的事情)。
但第二个例子不起作用
push
/ret
相当于jmp
一个绝对地址。(性能除外:它总是会导致分支错误预测并与调用/调用预测器不匹配)。
你跳到了正确的地方(大概),但你忘了从堆栈中弹出返回地址。所以你“返回”,ESP指向错误的地方。这很容易导致大多数呼叫者崩溃;他们自己ret
可能会弹出错误的返回地址。
(调用者使用 EBP 作为帧指针并且之前没有访问与 ESP 相关的任何内容leave
/ret
可能不会注意到。例如,如果调用者的 asm 是由 C 编译器在调试模式下生成的。即使这样,现代 Linux 也需要 16- a 之前的字节堆栈对齐call
,以及在调用您的中断后进行的函数调用func2
将不再使用 16 字节对齐的堆栈进行。当您以这种方式违反 ABI 时,某些 libc 函数可能会崩溃。)
通常你会把事情描述为:
call target
=push $retaddr
;jmp target
;retaddr:
ret
=pop %tmp
;jmp *%tmp
. 这是一个间接分支。
(认为是ret
一种写作方式pop %eip
)
但是是的,如果一个函数只有一个调用者,您可以硬编码返回地址并使用add $esp, 4
/jmp after_call
而不是ret
. ret
(再次通过不使用从 a 返回来打破对未来 rets 的分支预测call
。)
或者用 , 替换调用者的call
,jmp
然后jmp
返回。这有效地形成func2
了一个块,它是func1
某些看待它的方式的一部分。它不能call
从其他任何地方编辑,因为它不需要返回地址。
获取返回地址(即在跳回之前将其从堆栈中删除)但忽略它并总是跳转到硬编码的位置func1
似乎没有用。
推荐阅读
- angular - 为什么 Angular Project 在创建构建后不起作用?
- java - 如何调用存储在字符串变量中的Java方法
- html - 引导导航栏未正确显示
- string - 带函数的字符串插值
- mysql - 如何在 mysql 8 中删除重复行并更新关系
- python - LINE 1: ..." 时区 NOT NULL 的时间戳,"attributes" hstore NOT
- java - aspecj 在 java 11 中访问 sun 包
- javascript - 从另一个数组的匹配对象值创建一个数组
- cypress - 赛普拉斯:“任务”事件尚未在插件文件中注册。您必须在使用 cy.task() 之前注册它
- java - 从 hashmap 键集中用连字符分隔的字符串