C语言接口与实现【第四章】 setjmp/longjmp非局部跳转函数分析

来源:互联网 发布:上海数据交易中心工资 编辑:程序博客网 时间:2024/05/29 11:44

转自:http://blog.csdn.net/origin_lee/article/details/45057507


参考这篇文章,对比我所有的系统,分析正确,挺有价值,原理分析透彻。

我所有的系统为:

linux:

uname -a
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:45:15 UTC 2015 i686 i686 i686 GNU/Linux


glibc:

root@ubuntu:/home/cling60/src/linux/exception# ldd  --version
ldd (Ubuntu EGLIBC 2.19-0ubuntu6.5) 2.19
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.


之前就一直好奇setjmp()/longjmp()函数是怎么实现非局部跳转的,心中猜测应该是通过保存调用setjmp()函数处的栈上下文(stack context),之后通过函数longjmp()来恢复这个栈上下文来实现的,可是心中依然有疑惑,到底需要保存哪些东西呢,还有是怎么改变setjmp函数的返回值的呢。本文就通过实际程序调试以及glibc源码来一探究竟吧(本文针对i386平台)!

调制程序如下:

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <setjmp.h>  
  4.   
  5. jmp_buf env;  
  6.   
  7. int main(int argc, const char *argv[])  
  8. {  
  9.     if (0 == setjmp(env)) {  
  10.         printf("start to throw exception\n");  
  11.         longjmp(env, 1);  
  12.     } else {  
  13.         /* exeception */  
  14.         printf("in exception\n");  
  15.     }  
  16.     exit(EXIT_SUCCESS);  
  17. }  

首先看下/usr/include/setjmp.h头文件中jmp_buf的定义:

[cpp] view plain copy
 print?
  1. #include <bits/setjmp.h>    /* Get `__jmp_buf'.  */  
  2. #include <bits/sigset.h>    /* Get `__sigset_t'.  */  
  3. /* Calling environment, plus possibly a saved signal mask.  */  
  4. struct __jmp_buf_tag {  
  5.     /* NOTE: The machine-dependent definitions of `__sigsetjmp' 
  6.        assume that a `jmp_buf' begins with a `__jmp_buf' and that 
  7.        `__mask_was_saved' follows it.  Do not move these members 
  8.        or add others before it.  */  
  9.     __jmp_buf __jmpbuf;         /* Calling environment.  */  
  10.     int __mask_was_saved;       /* Saved the signal mask?  */  
  11.     __sigset_t __saved_mask;    /* Saved signal mask.  */  
  12. };  
  13.   
  14. __BEGIN_NAMESPACE_STD typedef struct __jmp_buf_tag jmp_buf[1];  

可以看到类型jmp_buf被定义为结构体struct __jmp_buf_tag的一维数组,这样做至少有两个好处:

  1. 在声明jmp_buf时,可以把数据分配到堆栈上
  2. 作为参数传递时则作为一个指针

我们目前只关心__jmp_buf __jmpbuf,也就是注释中提到的calling environment,其他的我们在后文中提到sigsetjmp()/siglongjmp()时再提。在/usr/include/i386-Linux-gnu/bits/setjmp.h中找到__jmp_buf的定义如下:

[cpp] view plain copy
 print?
  1. #ifndef _ASM  
  2. typedef int __jmp_buf[6];  
  3. #endif  

看来这里的整形数组就是保存栈上下文的地方了。现在开始gdb调试程序,先看main函数的反汇编代码如下:

[cpp] view plain copy
 print?
  1. astrol@astrol:~$ gdb setjmp -q  
  2. Reading symbols from /home/astrol/setjmp...done.  
  3. (gdb) break main  
  4. Breakpoint 1 at 0x804844d: file setjmp.c, line 9.  
  5. (gdb) run  
  6. Starting program: /home/astrol/setjmp  
  7.   
  8. Breakpoint 1, main (argc=1, argv=0xbffff504) at setjmp.c:9  
  9. 9               if (0 == setjmp(env)) {  
  10. (gdb) disassemble  
  11. Dump of assembler code for function main:  
  12.    0x08048444 <+0>:     push   %ebp  
  13.    0x08048445 <+1>:     mov    %esp,%ebp  
  14.    0x08048447 <+3>:     and    $0xfffffff0,%esp  
  15.    0x0804844a <+6>:     sub    $0x10,%esp  
  16. => 0x0804844d <+9>:     movl   $0x804a040,(%esp)  
  17.    0x08048454 <+16>:    call   0x8048350 <_setjmp@plt>  
  18.    0x08048459 <+21>:    test   %eax,%eax  
  19.    0x0804845b <+23>:    jne    0x804847d <main+57>  
  20.    0x0804845d <+25>:    movl   $0x8048570,(%esp)  
  21.    0x08048464 <+32>:    call   0x8048360 <puts@plt>  
  22.    0x08048469 <+37>:    movl   $0x2,0x4(%esp)  
  23.    0x08048471 <+45>:    movl   $0x804a040,(%esp)  
  24.    0x08048478 <+52>:    call   0x8048340 <longjmp@plt>  
  25.    0x0804847d <+57>:    movl   $0x8048589,(%esp)  
  26.    0x08048484 <+64>:    call   0x8048360 <puts@plt>  
  27.    0x08048489 <+69>:    movl   $0x3,0x4(%esp)  
  28.    0x08048491 <+77>:    movl   $0x804a040,(%esp)  
  29.    0x08048498 <+84>:    call   0x8048340 <longjmp@plt>  
  30. End of assembler dump.  
  31. (gdb)  

最开始的三行是大家熟知的function prologue:将旧的函数帧指针ebp入栈,然后更新ebp为当前堆栈指针esp,接着使esp 16字节对齐。sub $0x10, %esp是在为main函数开辟栈空间,一般是用于局部变量以及后续的入出栈操作使用的。

[cpp] view plain copy
 print?
  1. (gdb) ptype env  
  2. type = struct __jmp_buf_tag {  
  3.     __jmp_buf __jmpbuf;  
  4.     int __mask_was_saved;  
  5.     __sigset_t __saved_mask;  
  6. } [1]  
  7. (gdb) ptype __jmp_buf  
  8. type = int [6]  
  9. (gdb) print $ebp  
  10. $18 = (void *) 0xbffff468  
  11. (gdb) print $esp  
  12. $19 = (void *) 0xbffff44c  
  13. (gdb) print env  
  14. $20 = {{__jmpbuf = {0, 0, 0, 0, 0, 0}, __mask_was_saved = 0, __saved_mask = {__val = {0 <repeats 32 times>}}}}  
  15. (gdb)  

配合print &env的结果我们发现,程序将参数env的地址入栈,接着就是调用函数setjmp函数。注意在调用call指令的同时,程序也将下一条指令的地址入栈了,也就是test %eax,%eax的地址0x08048459,此时的函数帧情况如下(图中一个格子代表4个字节):


好,接下来就是重点了,让我们来看看函数setjmp都做了什么。

[cpp] view plain copy
 print?
  1. (gdb) disassemble  
  2. Dump of assembler code for function _setjmp:  
  3. => 0x00160bb0 <+0>:     xor    %eax,%eax  
  4.    0x00160bb2 <+2>:     mov    0x4(%esp),%edx  
  5.    0x00160bb6 <+6>:     mov    %ebx,(%edx)  
  6.    0x00160bb8 <+8>:     mov    %esi,0x4(%edx)  
  7.    0x00160bbb <+11>:    mov    %edi,0x8(%edx)  
  8.    0x00160bbe <+14>:    lea    0x4(%esp),%ecx  
  9.    0x00160bc2 <+18>:    xor    %gs:0x18,%ecx  
  10.    0x00160bc9 <+25>:    rol    $0x9,%ecx  
  11.    0x00160bcc <+28>:    mov    %ecx,0x10(%edx)  
  12.    0x00160bcf <+31>:    mov    (%esp),%ecx  
  13.    0x00160bd2 <+34>:    xor    %gs:0x18,%ecx  
  14.    0x00160bd9 <+41>:    rol    $0x9,%ecx  
  15.    0x00160bdc <+44>:    mov    %ecx,0x14(%edx)  
  16.    0x00160bdf <+47>:    mov    %ebp,0xc(%edx)  
  17.    0x00160be2 <+50>:    mov    %eax,0x18(%edx)  
  18.    0x00160be5 <+53>:    ret  
  19. End of assembler dump.  

我们把glibc中的源码也贴出来看看(glibc-2.15):

glibc-2.15/sysdeps/i386/bsd-_setjmp.S:

[cpp] view plain copy
 print?
  1. /* This just does a tail-call to `__sigsetjmp (ARG, 0)'. 
  2.    We cannot do it in C because it must be a tail-call, so frame-unwinding 
  3.    in setjmp doesn't clobber the state restored by longjmp.  */  
  4.   
  5. #include <sysdep.h>  
  6. #include <jmpbuf-offsets.h>  
  7. #include "bp-sym.h"  
  8. #include "bp-asm.h"  
  9.   
  10. #define PARMS   LINKAGE     /* no space for saved regs */  
  11. #define JMPBUF  PARMS  
  12. #define SIGMSK  JMPBUF+PTR_SIZE  
  13.   
  14. ENTRY (BP_SYM (_setjmp))  
  15.     ENTER  
  16.   
  17.     xorl %eax, %eax  
  18.     movl JMPBUF(%esp), %edx  
  19.     CHECK_BOUNDS_BOTH_WIDE (%edx, JMPBUF(%esp), $(JB_SIZE+4))  
  20.   
  21.     /* Save registers.  */  
  22.     movl %ebx, (JB_BX*4)(%edx)  
  23.     movl %esi, (JB_SI*4)(%edx)  
  24.     movl %edi, (JB_DI*4)(%edx)  
  25.     /* Save SP as it will be after we return.  */  
  26.     leal JMPBUF(%esp), %ecx   
  27. #ifdef PTR_MANGLE  
  28.     PTR_MANGLE (%ecx)  
  29. #endif  
  30.     movl %ecx, (JB_SP*4)(%edx)  
  31.     /* Save PC we are returning to now.  */  
  32.     movl PCOFF(%esp), %ecx    
  33. #ifdef PTR_MANGLE  
  34.     PTR_MANGLE (%ecx)  
  35. #endif  
  36.     movl %ecx, (JB_PC*4)(%edx)  
  37.     LEAVE  
  38.     /* Save caller's frame pointer.  */  
  39.     movl %ebp, (JB_BP*4)(%edx)   
  40.   
  41.     movl %eax, JB_SIZE(%edx) /* No signal mask set.  */  
  42.     ret  
  43. END (BP_SYM (_setjmp))  
  44. libc_hidden_def (_setjmp)  

glibc-2.15/sysdeps/i386/jmpbuf-offsets.h

[cpp] view plain copy
 print?
  1. #define JB_BX   0  
  2. #define JB_SI   1  
  3. #define JB_DI   2  
  4. #define JB_BP   3  
  5. #define JB_SP   4  
  6. #define JB_PC   5  
  7. #define JB_SIZE 24  

从这里可以终于可以看出__jmp_buf整形数组存储的依次是:ebx,esi,edi,ebp,esp,eip

好,现在分析汇编代码。

一开始就将eax清零,这段汇编代码结束时,作为setjmp函数的返回值 -- 使用eax作为函数的返回值这种做法,是很常见的。

mov    0x4(%esp),%edx是将env的地址付给寄存器edx,即此时edx指向__jmpbuf首地址。如下图:

mov %ebx, (%edx)

mov %esi, 0x4(%edx)

mov %edi, 0x8(%edx)

三句分别将当前程序的寄存器ebx,esi,edi中的值存入__jmpbuf数组,下标依次为JB_BX,JB_SI,JB_DI。

lea  0x4(%esp),%ecx
xor  %gs:0x18,%ecx
rol  $0x9,%ecx
mov  %ecx,0x10(%edx)

这四句其实就是将当前程序的esp+4存入数组__jmpbuf下标JB_SP的位置,至于为什么不是直接存入,这里牵扯到glibc程序安全问题,我们后面再说,这里只要知道是存储数值esp+4就OK了。

mov (%esp),%ecx
xor %gs:0x18,%ecx
rol $0x9,%ecx
mov %ecx,0x14(%edx)

同样的道理,这四行代码是将eip存储到数组__jmpbuf下标JB_PC的位置。

mov %ebp,0xc(%edx)

这句将是寄存器ebp的值保存到数组__jmpbuf下标JB_BP位置。

mov %eax,0x18(%edx)就跟数组__jmpbuf无关了,而是之后的__mask_was_saved,这里将其赋值为0,表明不需要保存进程的信号屏蔽字。最后setjmp返回。

程序接着走,走到longjmp时首先进入的是glibc-2.15/setjmp/longjmp.c

[cpp] view plain copy
 print?
  1. /* Set the signal mask to the one specified in ENV, and jump 
  2.    to the position specified in ENV, causing the setjmp 
  3.    call there to return VAL, or 1 if VAL is 0.  */  
  4. void  
  5. __libc_siglongjmp (sigjmp_buf env, int val)  
  6. {  
  7.   /* Perform any cleanups needed by the frames being unwound.  */  
  8.   _longjmp_unwind (env, val);  
  9.   
  10.   if (env[0].__mask_was_saved)  
  11.     /* Restore the saved signal mask.  */  
  12.     (void) __sigprocmask (SIG_SETMASK, &env[0].__saved_mask,  
  13.               (sigset_t *) NULL);  
  14.   
  15.   /* Call the machine-dependent function to restore machine state.  */  
  16.   __longjmp (env[0].__jmpbuf, val ?: 1);  
  17. }  

可以看到如果env[0].__mask_was_saved非零的话,程序就会调用sigprocmask来恢复进程的信号屏蔽字。

同时我们也可以看到如果我们调用longjmp时第二个参数是0的话,那么程序会自动将其设置成1。

接着就真正进入longjmp的恢复栈上下文的代码了。

[cpp] view plain copy
 print?
  1. (gdb) disassemble  
  2. Dump of assembler code for function __longjmp:  
  3. => 0x00160c50 <+0>:     mov    0x4(%esp),%eax  
  4.    0x00160c54 <+4>:     mov    0x14(%eax),%edx  
  5.    0x00160c57 <+7>:     mov    0x10(%eax),%ecx  
  6.    0x00160c5a <+10>:    ror    $0x9,%edx  
  7.    0x00160c5d <+13>:    xor    %gs:0x18,%edx  
  8.    0x00160c64 <+20>:    ror    $0x9,%ecx  
  9.    0x00160c67 <+23>:    xor    %gs:0x18,%ecx  
  10.    0x00160c6e <+30>:    mov    (%eax),%ebx  
  11.    0x00160c70 <+32>:    mov    0x4(%eax),%esi  
  12.    0x00160c73 <+35>:    mov    0x8(%eax),%edi  
  13.    0x00160c76 <+38>:    mov    0xc(%eax),%ebp  
  14.    0x00160c79 <+41>:    mov    0x8(%esp),%eax  
  15.    0x00160c7d <+45>:    mov    %ecx,%esp  
  16.    0x00160c7f <+47>:    jmp    *%edx  
  17. End of assembler dump.  

glibc中的代码(glibc-2.15/sysdeps/i386/__longjmp.S):

[cpp] view plain copy
 print?
  1. #include <sysdep.h>  
  2. #include <jmpbuf-offsets.h>  
  3. #include <asm-syntax.h>  
  4.   
  5.     .text  
  6. ENTRY (__longjmp)  
  7. #ifdef PTR_DEMANGLE  
  8.     movl 4(%esp), %eax  /* User's jmp_buf in %eax.  */  
  9.   
  10.     /* Save the return address now.  */  
  11.     movl (JB_PC*4)(%eax), %edx  
  12.     /* Get the stack pointer.  */  
  13.     movl (JB_SP*4)(%eax), %ecx  
  14.     PTR_DEMANGLE (%edx)  
  15.     PTR_DEMANGLE (%ecx)  
  16.     cfi_def_cfa(%eax, 0)  
  17.     cfi_register(%eip, %edx)  
  18.     cfi_register(%esp, %ecx)  
  19.     cfi_offset(%ebx, JB_BX*4)  
  20.     cfi_offset(%esi, JB_SI*4)  
  21.     cfi_offset(%edi, JB_DI*4)  
  22.     cfi_offset(%ebp, JB_BP*4)  
  23.         /* Restore registers.  */  
  24.     movl (JB_BX*4)(%eax), %ebx  
  25.     movl (JB_SI*4)(%eax), %esi  
  26.     movl (JB_DI*4)(%eax), %edi  
  27.     movl (JB_BP*4)(%eax), %ebp  
  28.     cfi_restore(%ebx)  
  29.     cfi_restore(%esi)  
  30.     cfi_restore(%edi)  
  31.     cfi_restore(%ebp)  
  32.   
  33.     movl 8(%esp), %eax  /* Second argument is return value.  */  
  34.     movl %ecx, %esp  
  35. #else  
  36.     movl 4(%esp), %ecx  /* User's jmp_buf in %ecx.  */  
  37.     movl 8(%esp), %eax  /* Second argument is return value.  */  
  38.     /* Save the return address now.  */  
  39.     movl (JB_PC*4)(%ecx), %edx  
  40.         /* Restore registers.  */  
  41.     movl (JB_BX*4)(%ecx), %ebx  
  42.     movl (JB_SI*4)(%ecx), %esi  
  43.     movl (JB_DI*4)(%ecx), %edi  
  44.     movl (JB_BP*4)(%ecx), %ebp  
  45.     movl (JB_SP*4)(%ecx), %esp  
  46. #endif  
  47.     /* Jump to saved PC.  */  
  48.         jmp *%edx  
  49. END (__longjmp)  

mov 0x4(%esp), %eax 将数组__jmpbuf的地址复制为寄存器eax

mov 0x14(%eax), %edx

mov 0x10(%eax), %ecx

ror $0x9, %edx

xor %gs:0x18, %edx

ror $0x9, %ecx

xor %gs:0x18, %ecx

这六句就是把setjmp中经过特殊处理的eip和esp分别存储到寄存器edx和ecx中。

mov (%eax), %ebx

mov 0x4(%eax), %esi

mov 0x8(%eax), %edi

mov 0xc(%eax), %ebp

这四句分别在恢复寄存器ebx,esi,edi和ebp寄存器的值。

接下来的mov 0x8(%esp), %eax就解答了我文章一开始的疑问,就是通过这句来改变setjmp的返回值的,这里通过把压入栈的第二个参数复制给寄存器eax作为返回值。

倒数第二句mov %ecx, %esp是在恢复栈指针。

最后一句jmp *%edx就是调转到调用setjmp时压入到栈中的eip处继续执行指令。

 

好了,终于了解了setjmp()/longjmp()的实现细节了。

现在来了解下sigsetjmp()/siglongjmp()函数。为了了解为什么要有这两个函数,我们先来看一个有问题的程序。

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <signal.h>  
  5. #include <setjmp.h>  
  6.   
  7. jmp_buf env;  
  8.   
  9. static void sig_alrm(int signo)  
  10. {  
  11.     printf("in SIGALRM signal handler.\n");  
  12.     alarm(2);  
  13.     longjmp(env, 1);  
  14. }  
  15.   
  16. int main(int argc, const char *argv[])  
  17. {  
  18.     if (signal(SIGALRM, sig_alrm) == SIG_ERR) {  
  19.         perror("signal error: ");  
  20.         exit(EXIT_FAILURE);  
  21.     }  
  22.     alarm(2);  
  23.   
  24.     setjmp(env);  
  25.   
  26.     for ( ;; )  
  27.         pause();  
  28.   
  29.     exit(EXIT_SUCCESS);  
  30. }  

运行程序,我们会发现,尽管已经在信号处理程序sig_alrm中调用了alarm函数,但是该信号处理程序我们只调用了一次。是不是很奇怪?

默认情况下(不指定sa_flags为SA_NODEFER),信号处理时会自动阻塞正在被处理的信号。也就是说,和正在被处理同样的信号再次发生时,它会被阻塞到本次信号处理结束。在信号处理函数返回时,进程的信号屏蔽字会被恢复,即解除对当前信号的阻塞。在上面的程序中,并没有让信号处理函数正常返回,而是使用了longjmp()直接跳转,所以进程的信号屏蔽字在第一次收到信号后,就把信号设置为阻塞并且再也没有恢复,因而再也触发不了信号处理函数了,除非手动将进程的信号屏蔽字去除。

sigsetjmp()/siglongjmp()就解决了这个问题:sigsetjmp()会保存进程的当前屏蔽字(第二个参数非零的情况下),而siglongjmp()会恢复这个信号屏蔽字,所以不管你阻塞了什么信号,调用siglongjmp()时都会恢复。程序修改如下就可以了:

[cpp] view plain copy
 print?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <signal.h>  
  5. #include <setjmp.h>  
  6.   
  7. jmp_buf env;  
  8. static volatile sig_atomic_t canjump;  
  9.   
  10. static void sig_alrm(int signo)  
  11. {  
  12.     if (canjump == 0)  
  13.         return;  
  14.     printf("in SIGALRM signal handler.\n");  
  15.     alarm(2);  
  16.     canjump = 0;  
  17.     siglongjmp(env, 1);  
  18. }  
  19.   
  20. int main(int argc, const char *argv[])  
  21. {  
  22.     if (signal(SIGALRM, sig_alrm) == SIG_ERR) {  
  23.         perror("signal error: ");  
  24.         exit(EXIT_FAILURE);  
  25.     }  
  26.     alarm(2);  
  27.   
  28.     sigsetjmp(env, 1);  
  29.     canjump = 1;  
  30.   
  31.     for ( ;; )  
  32.         pause();  
  33.   
  34.     exit(EXIT_SUCCESS);  
  35. }  

看看glibc源码是怎么做的(glibc-2.15/sysdeps/i386/setjmp.S):

[cpp] view plain copy
 print?
  1. #include <sysdep.h>  
  2. #include <jmpbuf-offsets.h>  
  3. #include <asm-syntax.h>  
  4. #include "bp-sym.h"  
  5. #include "bp-asm.h"  
  6.   
  7. #define PARMS   LINKAGE     /* no space for saved regs */  
  8. #define JMPBUF  PARMS  
  9. #define SIGMSK  JMPBUF+PTR_SIZE  
  10.   
  11. ENTRY (BP_SYM (__sigsetjmp))  
  12.     ENTER  
  13.   
  14.     movl JMPBUF(%esp), %eax  
  15.     CHECK_BOUNDS_BOTH_WIDE (%eax, JMPBUF(%esp), $JB_SIZE)  
  16.   
  17.         /* Save registers.  */  
  18.     movl %ebx, (JB_BX*4)(%eax)  
  19.     movl %esi, (JB_SI*4)(%eax)  
  20.     movl %edi, (JB_DI*4)(%eax)  
  21.     leal JMPBUF(%esp), %ecx /* Save SP as it will be after we return.  */  
  22. #ifdef PTR_MANGLE  
  23.     PTR_MANGLE (%ecx)  
  24. #endif  
  25.     movl %ecx, (JB_SP*4)(%eax)  
  26.     movl PCOFF(%esp), %ecx  /* Save PC we are returning to now.  */  
  27. #ifdef PTR_MANGLE  
  28.     PTR_MANGLE (%ecx)  
  29. #endif  
  30.     movl %ecx, (JB_PC*4)(%eax)  
  31.     LEAVE /* pop frame pointer to prepare for tail-call.  */  
  32.     movl %ebp, (JB_BP*4)(%eax) /* Save caller's frame pointer.  */  
  33.   
  34. #if defined NOT_IN_libc && defined IS_IN_rtld  
  35.     /* In ld.so we never save the signal mask.  */  
  36.     xorl %eax, %eax  
  37.     ret  
  38. #else  
  39.     /* Make a tail call to __sigjmp_save; it takes the same args.  */  
  40.     jmp __sigjmp_save  
  41. #endif  
  42. END (BP_SYM (__sigsetjmp))  

以下是调试汇编:

[cpp] view plain copy
 print?
  1. (gdb) disassemble  
  2. Dump of assembler code for function __sigsetjmp:  
  3. => 0x00160ad0 <+0>:     mov    0x4(%esp),%eax   # 将数组__jmpbuf的地址保存在eax  
  4.    0x00160ad4 <+4>:     mov    %ebx,(%eax)      # 恢复ebx  
  5.    0x00160ad6 <+6>:     mov    %esi,0x4(%eax)   # 恢复esi  
  6.    0x00160ad9 <+9>:     mov    %edi,0x8(%eax)   # 恢复edi  
  7.    0x00160adc <+12>:    lea    0x4(%esp),%ecx  
  8.    0x00160ae0 <+16>:    xor    %gs:0x18,%ecx  
  9.    0x00160ae7 <+23>:    rol    $0x9,%ecx  
  10.    0x00160aea <+26>:    mov    %ecx,0x10(%eax)  # 恢复esp  
  11.    0x00160aed <+29>:    mov    (%esp),%ecx  
  12.    0x00160af0 <+32>:    xor    %gs:0x18,%ecx  
  13.    0x00160af7 <+39>:    rol    $0x9,%ecx  
  14.    0x00160afa <+42>:    mov    %ecx,0x14(%eax)  # 恢复eip  
  15.    0x00160afd <+45>:    mov    %ebp,0xc(%eax)   # 恢复ebp  
  16.    0x00160b00 <+48>:    jmp    0x160b10 <__sigjmp_save>  
  17. End of assembler dump.  

几乎和setjmp的源码是一样的,多了最后一句,调用了__sigjmp_save函数(glibc-2.15/setjmp/sigjmp.c):

[cpp] view plain copy
 print?
  1. #include <stddef.h>  
  2. #include <setjmp.h>  
  3. #include <signal.h>  
  4.   
  5. /* This function is called by the `sigsetjmp' macro 
  6.    before doing a `__setjmp' on ENV[0].__jmpbuf. 
  7.    Always return zero.  */  
  8.   
  9. int  
  10. __sigjmp_save (sigjmp_buf env, int savemask)  
  11. {  
  12.   env[0].__mask_was_saved = (savemask &&  
  13.                  __sigprocmask (SIG_BLOCK, (sigset_t *) NULL,  
  14.                         &env[0].__saved_mask) == 0);  
  15.   
  16.   return 0;  
  17. }  

可以很清楚的看到,当调用sigsetjmp时,第二个参数非零时会保存进程的当前屏蔽字于__saved_mask中,并将__mask_was_saved赋值为1,供siglongjmp函数使用。
调用siglongjmp时调用的也是__libc_siglongjmp函数,只不过在该函数中会因为env[0].__mask_was_saved为1而调用sigprocmask恢复了进程的信号屏蔽字。

此外,还需要注意到,信号是异步的,即信号可以在任何一个时间点送达,因此它完全可能在sigsetjmp()或者setjmp()设置之前就到达,那么在这种情况下进入信号处理函数,并在里面调用了siglongjmp()或者longjmp() ,那么就会使用一个未初始化的jmpbuf(跳转缓冲)。为了防止这种情况,在程序中引入了一个监控变量canjump,如果canjump没有被设置时,信号处理函数只是简单的退出,而不会进行非局部跳转;也只有在sigsetjmp()或setjmp()设置完后,才会将该变量设为1。当然,另外一个办法是也可以在建立信号处理函数之前就调用sigsetjmp()或setjmp() --- 实际上,在一个复杂的程序里,很难保证说一定能让这两个步骤按此顺序去执行 --- 因此较为简单的方法还是像上面使用一个监控变量。

至此,我们就彻底了解了setjmp()/longjmp()和sigsetjmp()/siglongjmp()的实现细节了。

 

接下来简单谈一谈它们在使用上的注意点吧。

 

未完待续!

参考链接:http://web.eecs.utk.edu/~plank/plank/classes/cs360/360/notes/Setjmp/lecture.html

http://blog.codingnow.com/2010/05/setjmp.html

http://www.cnblogs.com/archimedes/p/c-exception-assert.html

http://www.cnblogs.com/lienhua34/archive/2012/04/22/2464859.html

http://wenku.baidu.com/link?url=XigAOFO54xdlZsK9ADfCYrjINucJVBr_U0oJAV5lkTluoyGHd2g3q_UVLLzZa8yQ94RKUwy1UnlS8n9s__bJR_Mq53YwK51rss8tnmdxuOy

http://yanx730.blog.163.com/blog/static/1194421200791423334964/

http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/

http://blog.csdn.net/topasstem8/article/details/6004945

http://www.groad.net/bbs/forum.php?mod=viewthread&tid=919&highlight=setjmp

阅读全文
0 0
原创粉丝点击