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以下