X64整体的栈幁整体以下图
git
GCC没有优化的状况下的反编译
github
在线反汇编地址app
读者经过上图观察能够看到函数的第一个参数放在RDI寄存器,第二个参数放在RSI寄存器 ... 后续参数放在何处,相关调用约定能够查看AMD 64 调用约定jvm
读者从上图能够看出 ,在汇编层面上,由函数的被调用方保存 rsp rbp 指针, 函数a b c 在汇编层面都是首先将 rbp入栈(push rbp) 而后对rsp操做 (例如sub rsp , 16;)并都会在函数返回前分别 将rbp出栈 (pop rbp) 还原rsp (例如 add rsp, 16;)函数
另外X86864 fastcall约定使用RAX寄存器保存返回值oop
总结一下汇编的函数的模板大体以下优化
返回前将当前帧须要返回的结果写入RAX寄存器,这一步比较隐晦 由于只有函数c 出现了这个步骤,在实际中函数返回都会依照约定写入RAX寄存器,因为函数b自己并无对函数返回结果(RAX寄存器的值)进行加工,所以函数b返回的时候RAX寄存器的值并不变化,同理a也是如此。ui
分别恢复rsp rbp寄存器 ,以下图中 add rsp, 16 跟 pop rbp操作系统
经过上面几个套路模板,基本上咱们就能使用汇编来编写汇编函数了,而后记住要很是当心地操做寄存器,避免破坏调用方的栈幁(函数中的临时变量)。线程
X64下一个线程在运行的时候,有一个PC寄存器指向当前线程的汇编代码的位置,咱们须要经过更换PC寄存器中的值 让CPU接下来从PC寄存器中新的位置运行汇编代码
另外咱们但愿 从当前的上下文切换到另一个上下文后,CPU可以切换回来继续正常运行,根据前面所说,那么咱们须要保护当前上下文的 RSP RBP 指针,而且保证当前上下文的整个栈幁的区域不会被另一个上下文给破坏
参考 我以前写的 JVM暂停工做线程机制
//全局变量 char *buffer; int pagesize; void allocate_memory() { /* * 初始化信号量结构体 */ struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); // 注册 handler 函数 ,当咱们触发信号的时候 操做系统会将切换到 handler 这个函数上下文来运行代码 sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction"); pagesize = sysconf(_SC_PAGE_SIZE); if (pagesize == -1) handle_error("sysconf"); // 初始化buffer指针指向的内存区域 buffer = memalign(pagesize, 4 * pagesize); if (buffer == NULL) handle_error("memalign"); printf("Start of region: 0x%lx\n", (long) buffer); //设置buffer指针为只读,接下来若是访问到buffer指针指向的内存区域就会触发信号 if (mprotect(buffer, pagesize * 4, PROT_READ) == -1) handle_error("mprotect"); }
int main(int argc, char *argv[]) { /* * allocate memory and set memory access READ */ allocate_memory(); char *p = buffer; // 初始化当前函数调用帧中的3个临时变量 uint64 local_pc = 0; uint64 local_rsp = 0; uint64 local_rbp = 0; //如下汇编代码是我根据C语言反编译后肯定 三个local_变量的位置 __asm__(".intel_syntax;" // 将当前CPU运行的代码的位置写入 RAX寄存器 方便handler中切换回来 // 其实这个rax寄存器中最终存放的内存地址 也就是 lea rax, [rip] 这个汇编代码在内存中的位置。 "lea rax, [rip];" //保存RAX寄存器 到 local_pc变量 "mov [rbp-0x20], rax;" //保存RSP寄存器 到 local_rsp变量 "mov [rbp-0x28], rsp;" //保存RBP寄存器 到 local_rbp变量 "mov [rbp-0x30], rbp;" ); //写入全局变量 pc = local_pc; rsp = local_rsp; rbp = local_rbp; for (int i = 0; i < 4; i++) { //当咱们操做p指针也就是buffer指针的时候,就会触发信号 *(p) = 'a'; p++; } *(p) = '\0'; printf("p = %s\n", buffer); //printf("%x",local_pc); /* * if we didn't restore those registers, * it should not happen. */ printf("Loop completed\n"); exit(EXIT_SUCCESS); }
static void handler(int sig, siginfo_t *si, void *unused) { /* * 打印受保护的内存地址 */ printf("Got SIGSEGV at address: 0x%lx\n", (long) si->si_addr); // 取消buffer指针指向内存区域的内存保护权限 if (mprotect(buffer, pagesize * 4, PROT_READ | PROT_WRITE) == -1) handle_error("mprotect"); // 此时线程的控制权 已经归JVM代码掌控,JVM能够挂起当前线程,等完成GC垃圾回收工做后再恢复状态 // 老办法 将以前全局变量保存的寄存器值恢复到当前函数帧中的临时变量 uint64 local_rsp = rsp; uint64 local_pc = pc; uint64 local_rbp = rbp; // 具体三个 local_xxx 变量的地址,依旧是我经过反编译C程序肯定的 // 仍是老办法 该怎么写到全局变量的 又怎么经过本地变量 local_xxx 写回到寄存器里面去 __asm__(".intel_syntax;" "mov rsp,[rbp-0x20];" "mov rax,[rbp-0x28];" "mov rbp,[rbp-0x30];" // 此处很是关键 直接jmp 调回去 让CPU回到以前main函数的上下文继续执行代码 "jmp rax;" ); //never happen printf("rsp:%x", local_rsp); printf("pc:%x", local_pc); printf("rbp:%x", local_rbp); exit(EXIT_FAILURE); }
整个流程的DEBUG的GIF以下