【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
signal大概是linux和其他rtos之间最重要的区别之一。所谓signal,其实就是用户注册一个signal处理函数,系统会在收到这个signal的时候调用用户的注册函数。因为一般process都有内核堆栈和用户堆栈,而这个注册函数是用户注册的,所以这个signal函数只能在用户侧进行处理。同样,因为signal函数随时会被处理,所以怎么保护用户侧的堆栈也是一个问题。
一般来说,注册函数会在中断返回或者系统调用返回时进行处理。因为signal函数需要的参数、入口不同,所以这个时候需要kernel修改一下register。那么,之前的用户侧register就没有地方放了,这个时候kernel一般会选择用户侧的空间来做这件事情。通常,os会选择当前sp寄存器下面的一段空间来做这件事情。当然,等到信号处理结束,函数会默认进行syscall,kernel会进入sys_sigreturn。此时,只需要在sys_sigreturn的时候从用户侧恢复register就可以了。一切就像没有发生过一样。
为了验证我们的想法,可以编写一段测试代码。
#include <stdio.h> #include <stdlib.h> #include <signal.h> int sig_process(int sig){ exit(0); } int main(int argc, char* argv[]){ signal(SIGINT, sig_process); while(1); return 0; }
这个时候可以在while循环设置断点的同时,发送SIGINT信号。判断main中sp、sig_process的sp中间是不是对寄存器进行了保护。如果可以找到各个寄存器对应的值,那就可以验证我们判断的正确性了。
$ gdb hello GNU gdb (GDB) 7.12 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-apple-darwin14.5.0". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from hello...Reading symbols from /Users/feixiaoxing/Desktop/hello.dSYM/Contents/Resources/DWARF/hello...done. done. (gdb) b main Breakpoint 1 at 0x100000f52: file hello.c, line 15. (gdb) b sig_process Breakpoint 2 at 0x100000f1d: file hello.c, line 9. (gdb) r Starting program: /Users/feixiaoxing/Desktop/hello warning: `/BinaryCache/coreTLS/coreTLS-35.40.1~1/Objects/coretls.build/coretls.build/Objects-normal/x86_64/system_coretls_vers.o': can't open to read symbols: No such file or directory. warning: Could not open OSO archive file "/BinaryCache/coreTLS/coreTLS-35.40.1~1/Symbols/BuiltProducts/libcoretls_ciphersuites.a" warning: Could not open OSO archive file "/BinaryCache/coreTLS/coreTLS-35.40.1~1/Symbols/BuiltProducts/libcoretls_handshake.a" warning: Could not open OSO archive file "/BinaryCache/coreTLS/coreTLS-35.40.1~1/Symbols/BuiltProducts/libcoretls_record.a" warning: Could not open OSO archive file "/BinaryCache/coreTLS/coreTLS-35.40.1~1/Symbols/BuiltProducts/libcoretls_stream_parser.a" Breakpoint 1, main (argc=1, argv=0x7fff5fbffbd0) at hello.c:15 15 signal(SIGINT, sig_process); (gdb) n 16 while(1); (gdb) i r rax 0x0 0 rbx 0x0 0 rcx 0x0 0 rdx 0x0 0 rsi 0x7fff5fbffb38 140734799805240 rdi 0x2 2 rbp 0x7fff5fbffbb0 0x7fff5fbffbb0 rsp 0x7fff5fbffb90 0x7fff5fbffb90 r8 0x0 0 r9 0x7fff5fbfec78 140734799801464 r10 0x1 1 r11 0x202 514 r12 0x0 0 r13 0x0 0 r14 0x0 0 r15 0x0 0 rip 0x100000f60 0x100000f60 <main+48> eflags 0x302 [ TF IF ] cs 0x2b 43 ss <unavailable> ds <unavailable> es <unavailable> fs 0x0 0 gs 0x0 0 (gdb) signal SIGINT Continuing with signal SIGINT. Breakpoint 2, sig_process (sig=2) at hello.c:9 9 exit(0); (gdb) i r rax 0x0 0 rbx 0x7fff5fbffad8 140734799805144 rcx 0x7fff5fbffa70 140734799805040 rdx 0x7fff5fbffad8 140734799805144 rsi 0x7fff5fbffa70 140734799805040 rdi 0x2 2 rbp 0x7fff5fbff630 0x7fff5fbff630 rsp 0x7fff5fbff620 0x7fff5fbff620 r8 0x7fff5fbffad8 140734799805144 r9 0x7fff5fbfec78 140734799801464 r10 0x1 1 r11 0x202 514 r12 0x0 0 r13 0x0 0 r14 0x0 0 r15 0x0 0 rip 0x100000f1d 0x100000f1d <sig_process+13> eflags 0x246 [ PF ZF IF ] cs 0x2b 43 ss <unavailable> ds <unavailable> es <unavailable> fs 0x0 0 gs 0x0 0 (gdb) x /128xh $rsp 0x7fff5fbff620: 0x1018 0x0000 0x0001 0x0000 0x0002 0x0000 0x0001 0x0000 0x7fff5fbff630: 0xf640 0x5fbf 0x7fff 0x0000 0xdf1a 0x9101 0x7fff 0x0000 0x7fff5fbff640: 0xfbb0 0x5fbf 0x7fff 0x0000 0xa27c 0x5fc1 0x7fff 0x0000 0x7fff5fbff650: 0x2030 0x0000 0x0001 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff660: 0xb48a 0x5fc2 0x7fff 0x0000 0x0001 0x0000 0x0000 0x0000 0x7fff5fbff670: 0xc000 0x007f 0x0001 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff680: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff690: 0x0000 0x0000 0x0000 0x0000 0x0002 0x0000 0x0000 0x0000 0x7fff5fbff6a0: 0xfb38 0x5fbf 0x7fff 0x0000 0xfbb0 0x5fbf 0x7fff 0x0000 0x7fff5fbff6b0: 0xfb90 0x5fbf 0x7fff 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff6c0: 0xec78 0x5fbf 0x7fff 0x0000 0x0001 0x0000 0x0000 0x0000 0x7fff5fbff6d0: 0x0202 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff6e0: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x7fff5fbff6f0: 0x0000 0x0000 0x0000 0x0000 0x0f60 0x0000 0x0001 0x0000 0x7fff5fbff700: 0x0202 0x0000 0x0000 0x0000 0x002b 0x0000 0x0000 0x0000 0x7fff5fbff710: 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 (gdb) quit A debugging session is active. Inferior 1 [process 513] will be killed. Quit anyway? (y or n) y
明显,我们可以从sp指向的空间里面找到之前rsi、rdi、rbp、r8、rsp、rip的数值,这就可以达到验证的目的了。为了进一步说明问题,可以看看signal返回的汇编代码对不对。
(gdb) si 0x0000000100000f3b in sig_process (sig=-1862148326) at hello.c:9 9 } (gdb) si <signal handler called> (gdb) disassemble $pc,+20 Dump of assembler code from 0x7fff9101df1a to 0x7fff9101df2e: => 0x00007fff9101df1a <_sigtramp+26>: decl -0x1d28ea50(%rip) # 0x7fff73d8f4d0 0x00007fff9101df20 <_sigtramp+32>: mov %rbx,%rdi 0x00007fff9101df23 <_sigtramp+35>: mov $0x1e,%esi 0x00007fff9101df28 <_sigtramp+40>: callq 0x7fff9101f4d8 0x00007fff9101df2d <_sigtramp+45>: retq End of assembler dump. (gdb) si <signal handler called> (gdb) si <signal handler called> (gdb) si <signal handler called> (gdb) si 0x00007fff9101f4d8 in ?? () from /usr/lib/system/libsystem_platform.dylib (gdb) disassemble $pc,+20 Dump of assembler code from 0x7fff9101f4d8 to 0x7fff9101f4ec: => 0x00007fff9101f4d8: jmpq *-0x1d2901de(%rip) # 0x7fff73d8f300 0x00007fff9101f4de: jmpq *-0x1d2901dc(%rip) # 0x7fff73d8f308 0x00007fff9101f4e4: jmpq *-0x1d2901da(%rip) # 0x7fff73d8f310 0x00007fff9101f4ea: jmpq *-0x1d2901d8(%rip) # 0x7fff73d8f318 End of assembler dump. (gdb) si 0x00007fff86abf708 in __sigreturn () from /usr/lib/system/libsystem_kernel.dylib (gdb) disassemble $pc,+20 Dump of assembler code from 0x7fff86abf708 to 0x7fff86abf71c: => 0x00007fff86abf708 <__sigreturn+0>: mov $0x20000b8,%eax 0x00007fff86abf70d <__sigreturn+5>: mov %rcx,%r10 0x00007fff86abf710 <__sigreturn+8>: syscall 0x00007fff86abf712 <__sigreturn+10>: jae 0x7fff86abf71c <__sigreturn+20> 0x00007fff86abf714 <__sigreturn+12>: mov %rax,%rdi 0x00007fff86abf717 <__sigreturn+15>: jmpq 0x7fff86abac78 <cerror> End of assembler dump.
通过调试信号函数返回ip,可以看到最后cpu是走到syscall、准备调用sig_return,这和我们的判断是一样的。