我将弄清楚如何正确设置程序堆栈。我知道使用[]调用函数
call pointer;
实际上与:相同
mov register, pc ;programcounter add register, 1 ; where 1 is one instruction not 1 byte ... push register jump pointer
但是,这意味着当Unix内核调用main函数时,堆栈库应该指向重新进入调用main的内核函数。
因此,在C代码中跳转“ * rbp-1”应重新输入主要功能。
但是,以下代码中不会发生这种情况:
#include <stdlib.h> #include <unistd.h> extern void ** rbp(); //pointer to stack pointing to function int main() { void ** p = rbp(); printf("Main: %p\n", main); printf("&Main: %p\n", &main); //WTF printf("*Main: %p\n", *main); //WTF printf("Stackbasepointer: %p\n", p); int (*c)(void) = (*p)-4; asm("movq %rax, 0"); c(); return 0; //should never be executed... }
汇编文件:rsp.asm
...
.intel_syntax .text: .global _rbp _rbp: mov rax, rbp ret;
毫不奇怪,这是不允许的,也许是因为此时的指令不完全是64位,也许是因为UNIX不允许这样做...
但是也
不允许此呼叫:void (*c)(void) = (*p); asm("movq %rax, 0"); //Exit code is 11, so now it should be 0 c(); //this comes with stack corruption, when successful
这意味着我没有义务退出主调用函数。
然后我的问题是:为什么我在每个GCC主函数的末尾都使用ret时会使用ret?这应该与上面的代码有效地做同样的事情。 Unix-系统如何有效地检查此类尝试...我希望我的问题很清楚...
谢谢。附注:代码只能在macOS上编译,请更改Linux的程序集]
我将弄清楚如何正确设置程序堆栈。我了解到使用调用指针来调用函数;实际上与:mov register,pc; programcounter add ...
但是,这意味着当Unix内核调用main函数时,堆栈库应该指向重新进入调用main的内核函数。
所以我认为您在这里有很多误解。首先,main不是内核调用的内容。内核分配一个进程并将二进制文件加载到内存中。如果使用的是基于UNIX的操作系统,则通常来自ELF文件。该ELF文件包含所有需要映射到内存中的部分,以及一个地址,该地址是ELF中代码的“入口点”(除其他外)。 ELF可以指定加载程序要跳转到的任何地址,以开始启动程序。在使用GCC构建的应用程序中,这是一个名为“ _start”的函数,“ _ start”然后设置堆栈并在调用“ main”之前执行其所需的任何其他初始化。