问题
In x-86, if you push a value from a register (for example, %eax), and then return, the program transfers control to the address corresponding to the value at %eax, to my understanding.
In another run of the program, if you edited the code so that you pushed onto the stack through a different way (for example, such as dereferencing a register, moving that value to another register, and then pushing this register), and then return, the program should also transfer control to the address corresponding the value pushed right?
In the two different runs of the program, if the values pushed were equivalent (even if they were not pushed the same way), then would the program behave the same way in each run?
I can't show my code, but I wanted to make sure my conceptual thinking was correct as mine throws errors in one case but not another. Thanks!
ex 1:
func1:
/* should push address onto stack when calling */
call func2
...
...
func2:
/* should pop address and transfer control back to func1 */
ret
ex 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 should also return to func1, right? but second example does not work
回答1:
Yes you can mismatch call/ret and do them manually (at a large performance code), but you have to emulate them correctly (including what they do to the stack pointer) if you want to not break anything.
but second example does not work
push
/ ret
is equivalent to jmp
to an absolute address. (Except for performance: it always causes a branch mispredict and mismatches the call/ret predictor).
You jump to the right place (presumably) but you forgot to pop the return address off the stack. So you "return" with ESP pointing to the wrong place. That can easily lead to a crash in most callers; their own ret
may pop the wrong return address.
(A caller that was using EBP as a frame pointer and didn't access anything relative to ESP before leave
/ ret
might not notice. e.g. if the caller's asm was generated by a C compiler in debug mode. Even with that, modern Linux requires 16-byte stack alignment before a call
, and function calls made after calling your func2
broke its ESP would no longer be made with 16-byte aligned stack. Some libc functions may crash when you violate the ABI that way.)
Normally you'd describe things as:
call target
=push $retaddr
;jmp target
;retaddr:
ret
=pop %tmp
;jmp *%tmp
. It's an indirect branch.
(Think ofret
as a way to writepop %eip
)
But yes if a function only has one caller, you could hardcode the return address and use add $esp, 4
/ jmp after_call
instead of ret
. (Again breaking branch prediction for future rets by not using ret
to return from a call
.)
Or replace the caller's call
with a jmp
, and jmp
back. This effectively makes func2
a block that's that part of func1
for some ways of looking at it. It can't be call
ed from anywhere else because it doesn't take a return address.
Taking a return address (i.e. removing it from the stack before jumping back) but ignoring it and always jumping to a hard-coded location in func1
doesn't seem useful.
来源:https://stackoverflow.com/questions/58351556/push-same-value-onto-stack-and-ret-behaves-differently