[分享] setjmp/longjmp 實作 (x86 32 bit)

看板C_and_CPP作者 (「雄辯是銀,沉默是金」)時間11年前 (2014/02/15 23:58), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串1/1
基礎知識: x86 function call 時的 stack 變化以及 function 如何 return。 setjmp/longjmp 這兩組函式我很陌生, 沒怎麼用過這對 function, 不知道什麼場合會出 動他們, 只知道在 c 上可以用他們模擬類似 c++ 的 exception (請參閱: ref 2)。c++ 上有 exception 可用, 這兩個傢伙一點都派不上用場, 在純 c 裡頭才會需要。而 coroutine 似乎也可用這組 function 來實作。 我有興趣的是: 「這是」怎麼做的? 不過在這之前先來看看怎麼用 setjmp/longjmp。 t0.c 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <setjmp.h> 4 5 jmp_buf mark; 6 7 int main(int argc, char *argv[]) 8 { 9 int ret = setjmp(mark); 10 printf("ret: %d\n", ret); 11 if (ret == 0) 12 { 13 printf("init setjmp\n"); 14 } 15 else 16 { 17 printf("exit\n"); 18 exit(0); 19 } 20 printf("xxx\n"); 21 longjmp(mark, 5); 22 return 0; 23 } 執行結果: ret: 0 init setjmp xxx ret: 5 exit 第一次執行 setjmp (L9), setjmp 會記住 L9 的位置 (記住哪個位址呢?), 而 ret 會設 為 0 (setjmp 傳回 0), 在執行 longjmp (L21) 之後, 會跳到 L9, 並且將 ret 設為 5 (longjmp 傳入的 5)。有點神奇, 是嗎? 參考 newlib 的實作:http://goo.gl/9y1DR ( http://sourceware.org/newlib/ ) msp430 (不知道在寫什麼, 跳過) src/newlib/libc/machine/msp430/setjmp.S 1 /* Copyright (c) 2013 Red Hat, Inc. All rights reserved. 2 3 This copyrighted material is made available to anyone wishing to use, 4 modify, copy, or redistribute it subject to the terms and conditions 5 of the BSD License. This program is distributed in the hope that 6 it will be useful, but WITHOUT ANY WARRANTY expressed or implied, 7 including the implied warranties of MERCHANTABILITY or FITNESS FOR 8 A PARTICULAR PURPOSE. A copy of this license is available at 9 http://www.opensource.org/licenses. Any Red Hat trademarks that are 10 incorporated in the source code or documentation are not subject to 11 the BSD License and may only be used or replicated with the express 12 permission of Red Hat, Inc. 13 */ 14 15 # setjmp/longjmp for msp430. The jmpbuf looks like this: 16 # 17 # Register Jmpbuf offset 18 # small large 19 # r0 (pc) 0x00 0x00 20 # r1 (sp) 0x02 0x04 21 # r4 0x04 0x08 22 # r5 0x06 0x0c 23 # r6 0x08 0x10 24 # r7 0x0a 0x14 25 # r8 0x0c 0x18 26 # r9 0x0e 0x1c 27 # r10 0x10 0x20 28 29 .text 30 .global setjmp 31 setjmp: 32 ; Upon entry r12 points to the jump buffer. 33 ; Returns 0 to caller. 34 35 #if defined __MSP430X_LARGE__ 36 mova @r1, r13 37 mova r13, 0(r12) 38 mova r1, 4(r12) 39 mova r4, 8(r12) 40 mova r5, 12(r12) 41 mova r6, 16(r12) 42 mova r7, 20(r12) 43 mova r8, 24(r12) 44 mova r9, 28(r12) 45 mova r10, 32(r12) 46 clr r12 47 reta 48 #else 49 ;; Get the return address off the stack 50 mov.w @r1, r13 51 mov.w r13, 0(r12) 52 mov.w r1, 2(r12) 53 mov.w r4, 4(r12) 54 mov.w r5, 6(r12) 55 mov.w r6, 8(r12) 56 mov.w r7, 10(r12) 57 mov.w r8, 12(r12) 58 mov.w r9, 14(r12) 59 mov.w r10, 16(r12) 60 clr r12 61 ret 62 #endif 63 64 65 .global longjmp 66 longjmp: 67 ; Upon entry r12 points to the jump buffer and 68 ; r13 contains the value to be returned by setjmp. 69 70 #if defined __MSP430X_LARGE__ 71 mova @r12+, r14 72 mova @r12+, r1 73 mova @r12+, r4 74 mova @r12+, r5 75 mova @r12+, r6 76 mova @r12+, r7 77 mova @r12+, r8 78 mova @r12+, r9 79 mova @r12+, r10 80 #else 81 mov.w @r12+, r14 82 mov.w @r12+, r1 83 mov.w @r12+, r4 84 mov.w @r12+, r5 85 mov.w @r12+, r6 86 mov.w @r12+, r7 87 mov.w @r12+, r8 88 mov.w @r12+, r9 89 mov.w @r12+, r10 90 #endif 91 ; If caller attempts to return 0, return 1 instead. 92 cmp.w #0, r13 93 jne .Lnot_zero 94 mov.w #1, r13 95 .Lnot_zero: 96 mov.w r13, r12 97 98 #if defined __MSP430X_LARGE__ 99 adda #4, r1 100 mova r14, r0 101 #else 102 add.w #2, r1 103 mov.w r14, r0 104 #endif x86, 跳過 ... A, 再跳這篇就結束了! 終於來到我熟悉的 32bit x86, 來看看 jmp_buf 到底是什麼?真是太簡單了, 就是一塊 記憶體區域, 用來存這些暫存器的值。jmp_buf 可以看成一個指標指向一塊記憶體, setjmp 會接收這個指標當參數, 然後把暫存器存在這裡 (L24~L34 那些暫存器)。 include/machine/setjmp-dj.h 1 /* 2 * Copyright (C) 1991 DJ Delorie 3 * All rights reserved. 4 * 5 * Redistribution, modification, and use in source and binary forms is permitted 6 * provided that the above copyright notice and following paragraph are 7 * duplicated in all such forms. 8 * 9 * This file is distributed WITHOUT ANY WARRANTY; without even the implied 10 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 */ 12 13 /* Modified to use SETJMP_DJ_H rather than SETJMP_H to avoid 14 conflicting with setjmp.h. Ian Taylor, Cygnus support, April, 15 1993. */ 16 17 #ifndef _SETJMP_DJ_H_ 18 #define _SETJMP_DJ_H_ 19 20 #ifdef __cplusplus 21 extern "C" { 22 #endif 23 24 typedef struct { 25 unsigned long eax; 26 unsigned long ebx; 27 unsigned long ecx; 28 unsigned long edx; 29 unsigned long esi; 30 unsigned long edi; 31 unsigned long ebp; 32 unsigned long esp; 33 unsigned long eip; 34 } jmp_buf[1]; 35 36 extern int setjmp(jmp_buf); 37 extern void longjmp(jmp_buf, int); 38 39 #ifdef __cplusplus 40 } 41 #endif 42 43 #endif 希望這張圖有幫到忙, 圖上方是高位址, 在執行 setjmp 時的 stack frame。 ( http://goo.gl/PyUOlt ) 所以 setjmp 就是把這些暫存器存起來, 那 eip 存什麼呢?就存 return address (src/newlib/libc/sys/sysvi386/_setjmp.S L20, 21), 然後 longjmp 會想辦法跳到這 裡; L11 ~ L19 則是把其他暫存器存到其他的 offset 上, 不過 offset 24 應該是 %ebp, 28 應該是 %esp, 把上面的這兩個欄位對就即可, 不過不影響程式的正確性, 因為 longjmp 也寫反了。 src/newlib/libc/sys/sysvi386/_setjmp.S 1 / 2 / our buffer looks like: 3 / eax,ebx,ecx,edx,esi,edi,esp,ebp,pc 4 5 .globl _setjmp 6 .globl setjmp 7 _setjmp: 8 setjmp: 9 pushl %ebx 10 movl 8(%esp), %ebx # 取得 setjmp 參數, 也就是 jmp_buf (是一個指標), jmp_buf 存到 %ebx 11 movl %eax, (%ebx) 12 popl %eax 13 movl %eax, 4(%ebx) 14 movl %ecx, 8(%ebx) 15 movl %edx, 12(%ebx) 16 movl %esi, 16(%ebx) 17 movl %edi, 20(%ebx) 18 movl %esp, 24(%ebx) 19 movl %ebp, 28(%ebx) 20 movl (%esp), %eax 21 movl %eax, 32(%ebx) 22 xorl %eax, %eax 23 ret 總之, setjmp 就是把的當時的 stack frame 的狀態存起來,但是只有暫存器的部份, 一 旦用 longjmp 回到那時候的 stack frame, 但是 stack frame 裡頭的資料和當時儲存的 時候不同, 那就 ... 嘿嘿, 因為 setjmp 並沒有保存 stack, 這是和 context switch 的不同之處。 longjmp 自然做的是相反的事情, 把 jmp_buf 裡頭的值設定給所有暫存器, 還原當時 setjmp 的執行環境。 src/newlib/libc/sys/sysvi386/_longjmp.S 1 / 2 / our buffer looks like: 3 / eax,ebx,ecx,edx,esi,edi,esp,ebp,pc 4 / 5 / _longjmp is called with two parameters: jmp_buf*,int 6 / jmp_buf* is at 4(%esp), int is at 8(%esp) 7 / retaddr is, of course, at (%esp) 8 9 .globl _longjmp 10 .globl longjmp 11 _longjmp: 12 longjmp: 13 movl 4(%esp), %ebx / address of buf 14 movl 8(%esp), %eax / store return value 15 16 movl 24(%ebx), %esp / restore stack 17 movl 32(%ebx), %edi 18 / Next line sets up return address. 19 movl %edi, 0(%esp) 21 movl 12(%ebx), %edx 22 movl 16(%ebx), %esi 23 movl 20(%ebx), %edi 24 movl 28(%ebx), %ebp 25 movl 4(%ebx), %ebx 26 testl %eax,%eax 27 jne bye 28 incl %eax / eax hold 0 if we are here 29 bye: 30 ret newlib/newlib/libc/machine/i386/setjmp.S 這是另外一個版本, 有點不同。 L14 則是 longjmp 傳入的數字, 在 longjmp 回到 setjmp 的時候, 拿來改變 setjmp 的 return value。 怎麼改變的, 以下是呼叫 setjmp 時的反組譯程式碼, setjmp 存的就是 L184, longjmp 發動時就是回到 L184, 所以改變了 %eax, 就會改變 setjmp 的回傳值。 objdump -dS a.out 181 int ret = setjmp(mark); 182 80484c6: c7 04 24 20 98 04 08 movl $0x8049820,(%esp) 183 80484cd: e8 9e fe ff ff call 8048370 <_setjmp@plt> 184 80484d2: 89 44 24 1c mov %eax,0x1c(%esp) // 把 %eax 設 定給 ret L19 設定這次的 function return address, 這樣離開時, 就會回到 setjmp 存的位址。 // 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字, http://blog2bbs.herokuapp.com/index.php (current only support chrome) // blog 原文: http://descent-incoming.blogspot.tw/2014/01/setjmplongjmp-x86-32-bit.html -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 58.114.144.243

02/17 01:21, , 1F
詳細推
02/17 01:21, 1F
文章代碼(AID): #1I__yq0s (C_and_CPP)