setjmp/longjmp in C
Table of Contents
Example Analysis
下面代码是setjmp以及longjmp的使用实例,以及其反汇编出来的结果,具体api的使用细节请参考man手册
#include <stdio.h>#include <setjmp.h>static jmp_buf env;double divide(double a,double b){const double delta = 0.00000000001;if(!((-delta < b) && (b < delta))){return a/b ;}else{longjmp(env, 1);return 0;}}int main( ){int ret;ret = setjmp(env);if (!ret) {printf("5/0=%lf\n", divide(5,0));} else if (ret == 1) {printf("ERR\n");}return 0;}
对应的汇编代码
Dump of assembler code for function divide:/var/folders/jh/0ybww9_12tv8l_jbrc8kwhxw0000gn/T/ox-bash-wtGI3WvdO/test.c:7 {8 const double delta = 0.00000000001;9 if(!((-delta < b) && (b < delta)))0x0000000000400674 <+0>: 00 00 00 90 adrp x0, 0x4000000x0000000000400678 <+4>: 02 bc 43 fd ldr d2, [x0, #1912]0x000000000040067c <+8>: 30 20 62 1e fcmpe d1, d20x0000000000400680 <+12>: 6c 00 00 54 b.gt 0x40068c <divide+24>10 {11 return a/b ;0x0000000000400684 <+16>: 00 18 61 1e fdiv d0, d0, d116 return 0;17 }18 }0x0000000000400688 <+20>: c0 03 5f d6 ret9 if(!((-delta < b) && (b < delta)))0x000000000040068c <+24>: 00 00 00 90 adrp x0, 0x4000000x0000000000400690 <+28>: 02 c0 43 fd ldr d2, [x0, #1920]0x0000000000400694 <+32>: 30 20 62 1e fcmpe d1, d20x0000000000400698 <+36>: 44 00 00 54 b.mi 0x4006a0 <divide+44> // b.first0x000000000040069c <+40>: fa ff ff 17 b 0x400684 <divide+16>12 }13 else14 {15 longjmp(env, 1);0x00000000004006a0 <+44>: fd 7b bf a9 stp x29, x30, [sp, #-16]!0x00000000004006a4 <+48>: 80 00 00 b0 adrp x0, 0x411000 <_setjmp@got.plt>0x00000000004006a8 <+52>: 21 00 80 52 mov w1, #0x1 // #17 {0x00000000004006ac <+56>: fd 03 00 91 mov x29, sp12 }13 else14 {15 longjmp(env, 1);0x00000000004006b0 <+60>: 00 20 01 91 add x0, x0, #0x480x00000000004006b4 <+64>: 9b ff ff 97 bl 0x400520 <longjmp@plt>End of assembler dump.Dump of assembler code for function main:/var/folders/jh/0ybww9_12tv8l_jbrc8kwhxw0000gn/T/ox-bash-wtGI3WvdO/test.c:21 {22 int ret;2324 ret = setjmp(env);0x0000000000400530 <+0>: fd 7b bf a9 stp x29, x30, [sp, #-16]!0x0000000000400534 <+4>: 80 00 00 b0 adrp x0, 0x411000 <_setjmp@got.plt>0x0000000000400538 <+8>: 00 20 01 91 add x0, x0, #0x4821 {0x000000000040053c <+12>: fd 03 00 91 mov x29, sp22 int ret;2324 ret = setjmp(env);0x0000000000400540 <+16>: e4 ff ff 97 bl 0x4004d0 <_setjmp@plt>2526 if (!ret) {0x0000000000400544 <+20>: a0 00 00 35 cbnz w0, 0x400558 <main+40>15 longjmp(env, 1);0x0000000000400548 <+24>: 80 00 00 b0 adrp x0, 0x411000 <_setjmp@got.plt>0x000000000040054c <+28>: 21 00 80 52 mov w1, #0x1 // #10x0000000000400550 <+32>: 00 20 01 91 add x0, x0, #0x480x0000000000400554 <+36>: f3 ff ff 97 bl 0x400520 <longjmp@plt>27 printf("5/0=%lf\n", divide(5,0));28 } else if (ret == 1) {0x0000000000400558 <+40>: 1f 04 00 71 cmp w0, #0x10x000000000040055c <+44>: 81 00 00 54 b.ne 0x40056c <main+60> // b.any29 printf("ERR\n");0x0000000000400560 <+48>: 00 00 00 90 adrp x0, 0x4000000x0000000000400564 <+52>: 00 c0 1d 91 add x0, x0, #0x7700x0000000000400568 <+56>: ea ff ff 97 bl 0x400510 <puts@plt>30 }31 return 0;0x000000000040056c <+60>: 00 00 80 52 mov w0, #0x0 // #00x0000000000400570 <+64>: fd 7b c1 a8 ldp x29, x30, [sp], #160x0000000000400574 <+68>: c0 03 5f d6 retEnd of assembler dump.
从上面的汇编代码可以看到,setjmp/longjmp只是普通的函数调用,调用到c库里边的_setjmp/longjmp函数,并没有和编译器有太大的联系.
libc implement of setjmp/longjmp
setjmp
/* Keep traditional entry points in with sigsetjmp(). */ENTRY (setjmp)mov x1, #1b 1fEND (setjmp)ENTRY (_setjmp)mov x1, #0b 1fEND (_setjmp)libc_hidden_def (_setjmp)ENTRY (__sigsetjmp)DELOUSE (0)1:stp x19, x20, [x0, #JB_X19<<3]stp x21, x22, [x0, #JB_X21<<3]stp x23, x24, [x0, #JB_X23<<3]stp x25, x26, [x0, #JB_X25<<3]stp x27, x28, [x0, #JB_X27<<3]#ifdef PTR_MANGLEPTR_MANGLE (4, 30, 3, 2)stp x29, x4, [x0, #JB_X29<<3]#elsestp x29, x30, [x0, #JB_X29<<3]#endif/* setjmp probe takes 3 arguments, address of jump bufferfirst argument (8@x0), return value second argument (-4@x1),and target address (8@x30), respectively. */LIBC_PROBE (setjmp, 3, 8@x0, -4@x1, 8@x30)stp d8, d9, [x0, #JB_D8<<3]stp d10, d11, [x0, #JB_D10<<3]stp d12, d13, [x0, #JB_D12<<3]stp d14, d15, [x0, #JB_D14<<3]#ifdef PTR_MANGLEmov x4, spPTR_MANGLE (5, 4, 3, 2)str x5, [x0, #JB_SP<<3]#elsemov x2, spstr x2, [x0, #JB_SP<<3]#endif#if IS_IN (rtld)/* In ld.so we never save the signal mask */mov w0, #0RET#elseb C_SYMBOL_NAME(__sigjmp_save)#endifEND (__sigsetjmp)hidden_def (__sigsetjmp)
从setjmp的实现中可以看到,setjmp只是简单的保存了当前的寄存器信息,包括lr,以及其他需要保存的寄存器,__sigjmp_save还会保存当前的signal mask信息
longjmp
ENTRY (__longjmp)cfi_def_cfa(x0, 0)cfi_offset(x19, JB_X19<<3)cfi_offset(x20, JB_X20<<3)cfi_offset(x21, JB_X21<<3)cfi_offset(x22, JB_X22<<3)cfi_offset(x23, JB_X23<<3)cfi_offset(x24, JB_X24<<3)cfi_offset(x25, JB_X25<<3)cfi_offset(x26, JB_X26<<3)cfi_offset(x27, JB_X27<<3)cfi_offset(x28, JB_X28<<3)cfi_offset(x29, JB_X29<<3)cfi_offset(x30, JB_LR<<3)cfi_offset( d8, JB_D8<<3)cfi_offset( d9, JB_D9<<3)cfi_offset(d10, JB_D10<<3)cfi_offset(d11, JB_D11<<3)cfi_offset(d12, JB_D12<<3)cfi_offset(d13, JB_D13<<3)cfi_offset(d14, JB_D14<<3)cfi_offset(d15, JB_D15<<3)DELOUSE (0)ldp x19, x20, [x0, #JB_X19<<3]ldp x21, x22, [x0, #JB_X21<<3]ldp x23, x24, [x0, #JB_X23<<3]ldp x25, x26, [x0, #JB_X25<<3]ldp x27, x28, [x0, #JB_X27<<3]#ifdef PTR_DEMANGLEldp x29, x4, [x0, #JB_X29<<3]PTR_DEMANGLE (30, 4, 3, 2)#elseldp x29, x30, [x0, #JB_X29<<3]#endif/* longjmp probe takes 3 arguments, address of jump buffer asfirst argument (8@x0), return value as second argument (-4@x1),and target address (8@x30), respectively. */LIBC_PROBE (longjmp, 3, 8@x0, -4@x1, 8@x30)ldp d8, d9, [x0, #JB_D8<<3]ldp d10, d11, [x0, #JB_D10<<3]ldp d12, d13, [x0, #JB_D12<<3]ldp d14, d15, [x0, #JB_D14<<3]/* Originally this was implemented with a series of.cfi_restore() directives.The theory was that cfi_restore should revert to previousframe value is the same as the current value. In practicethis doesn't work, even after cfi_restore() gdb continuesto try to recover a previous frame value offset from x0,which gets stuffed after a few more instructions. Thecfi_same_value() mechanism appears to work fine. */cfi_same_value(x19)cfi_same_value(x20)cfi_same_value(x21)cfi_same_value(x22)cfi_same_value(x23)cfi_same_value(x24)cfi_same_value(x25)cfi_same_value(x26)cfi_same_value(x27)cfi_same_value(x28)cfi_same_value(x29)cfi_same_value(x30)cfi_same_value(d8)cfi_same_value(d9)cfi_same_value(d10)cfi_same_value(d11)cfi_same_value(d12)cfi_same_value(d13)cfi_same_value(d14)cfi_same_value(d15)#ifdef PTR_DEMANGLEldr x4, [x0, #JB_SP<<3]PTR_DEMANGLE (5, 4, 3, 2)#elseldr x5, [x0, #JB_SP<<3]#endifmov sp, x5/* longjmp_target probe takes 3 arguments, address of jump bufferas first argument (8@x0), return value as second argument (-4@x1),and target address (8@x30), respectively. */LIBC_PROBE (longjmp_target, 3, 8@x0, -4@x1, 8@x30)cmp x1, #0mov x0, #1csel x0, x1, x0, ne/* Use br instead of ret because ret is guaranteed to mispredict */br x30END (__longjmp)
从longjmp的实现中,longjmp会还原之前的寄存器,并且重新返回之前保存的lr指针位置
risk of setjmp/longjmp usage
由于setjmp/longjmp的实现,以及编译器的优化,会导致某些情况下出现局部变量的值被修改的情况
- 由于编译器优化,不同的变量可能使用同一个寄存器/栈空间来实现,因次带来问题
- 由于编译器优化,代码顺序被调整而导致问题
使用时局部变量需要使用volatile关键字来修饰,防止编译器优化
详情请参考标准文档 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1494.pdf 7.13.1.1 The setjmp macro
- the entire controlling expression of a selection or iteration statement;
- one operand of a relational or equality operator with the other operand an integer constant expression, with the resulting expression being the entire controlling expression of a selection or iteration statement;
- the operand of a unary ! operator with the resulting expression being the entire controlling expression of a selection or iteration statement; or
- the entire expression of an expression statement (possibly cast to void).
详情可以参考:
https://rules.sonarsource.com/c/RSPEC-982
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56982