char program[3] = { 0x6a, 0x0a, 0xC3 }; //push 10; ret;
char (*p)(void) = program;
printf("char = %c \n", p());
此代码崩溃。为什么呢?
以下代码可以正常使用:
char program[3] = { 0x90, 0x90, 0xC3 }; //nop; nop; ret
void (*p)(void) = program;
p();
答案 0 :(得分:5)
因为您需要通过弹出来清理堆栈。阅读ret
指令的更多内容 - 它将使用堆栈中的值加载cs:ip!
答案 1 :(得分:3)
首先,你应该明白,你不能只是在装配中“做事”并期望它能够发挥作用。有一种称为应用程序二进制接口的东西,用于指定操作系统中以及代码内部的程序如何表现。
例如,在大多数x86-32平台上的C语言中,一个常见规则是eax
应该包含返回值。还会有一组值被压入堆栈(称为函数的堆栈帧)。另一个是需要保留某些寄存器,而其他寄存器(有时称为临时寄存器)可能会留下垃圾。
简而言之,如果您违反这些规则,则会出现问题并且操作系统会为您整理。这不仅取决于您的处理器,还取决于您的操作系统;例如,即使是x64
上的linux和windows也是不同的。
我还应该添加char program
不是程序,它是现有程序中的一个函数。
最后,nop, nop, ret
什么也没做。所以你可以执行这些指令并返回,因为你实际上没有对堆栈帧做任何事情。
答案 2 :(得分:3)
我强烈建议你学习调用约定。对于32位x86,修改堆栈的函数看起来应该更像这样(cdecl):
push ebp
mov ebp, esp ; or ENTER instruction for the push+mov
; Function code!
mov esp, ebp
pop ebp ; or LEAVE instruction for the mov+pop
ret
mov esp, ebp
确保您的堆栈返回到函数开头的方式,这意味着返回地址在堆栈上,ret
加载并跳转到。
函数中的局部变量放在堆栈上(sub esp, 0x4
将为一个32位变量分配空间,可用作[esp + 0]
)。
64位x86(非Itanium)有自己的调用约定。然后是快速呼叫等。
答案 3 :(得分:2)
char program[] = {
0x55, /* push %ebp */
0x89, 0xe5, /* mov %esp, %ebp */
0x6a, 0x0a, /* push $0xa */
0x8b, 0x04, 0x24, /* mov (%esp), %eax */
0x89, 0xec, /* mov %ebp, %esp */
0x5d, /* pop %ebp */
0xc3, /* ret */
};
那就行了*。你的程序崩溃是因为你弄乱了堆栈布局。
*只要program
驻留在内存的可执行部分中,这不一定是真的,并且您使用典型的C调用约定在x86-32上运行
答案 4 :(得分:0)
除了根据你的程序的意图(期望输出10)清理堆栈......
char program[3] = { 0x6a, 0x0a, 0xC3 }; //push 10; ret;
char (*p)(void) = program;
printf("char = %c \n", p())
...,你不应该按10,函数的返回值存储在AX(16位),EAX(如果是32位)
如果要传递参数,清理堆栈取决于调用函数约定,如果它是__fastcall(或pascal),则它是必须进行清理的调用例程;如果它是C调用约定(_cdecl),那么调用者应该清理堆栈