[分享] setjmp/longjmp 實作 (x86 32 bit)
基礎知識: 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