C调试时段异常后不复位方法
C调试时段异常后不复位方法
在C语言调试时,经常会遇到非法访问导致段异常复位,为方便调试,可以通过sigsetjmp结合信号处理挂接绕过复位机制。
段异常复位
我们都知道访问非法地址会导致程序跑死,那程序为什么会挂呢?
这是因为访问非法地址后,会触发异常信号(SIGSEGV 信号11),默认的信号处理函数会杀死本进程,进而表现出来就是程序跑挂了。下面是用简单的程序示例:
#include <stdio.h>
#define DEBUG do {printf("%s<%d> enter!\n", __FUNCTION__, __LINE__); fflush(stdout);} while (0)
static void ErrFunc(void) {
int *addr = (int *) 0x1234;
int temp;
DEBUG;
temp = *addr;
DEBUG;
return ;
}
int main() {
ErrFunc();
return 0;
}
程序输出:
ErrFunc<9> enter!
Segmentation fault (core dumped)
siglongjmp && sigsetjmp
DESCRIPTION
The functions described on this page are used for performing "nonlocal gotos": transferring execution from one function to a predetermined
location in another function. The setjmp() function dynamically establishes the target to which control will later be transferred, and
longjmp() performs the transfer of execution.
The setjmp() function saves various information about the calling environment (typically, the stack pointer, the instruction pointer, possi‐
bly the values of other registers and the signal mask) in the buffer env for later use by longjmp(). In this case, setjmp() returns 0.
The longjmp() function uses the information saved in env to transfer control back to the point where setjmp() was called and to restore
("rewind") the stack to its state at the time of the setjmp() call. In addition, and depending on the implementation (see NOTES), the values
of some other registers and the process signal mask may be restored to their state at the time of the setjmp() call.
Following a successful longjmp(), execution continues as if setjmp() had returned for a second time. This "fake" return can be distinguished
from a true setjmp() call because the "fake" return returns the value provided in val. If the programmer mistakenly passes the value 0 in
val, the "fake" return will instead return 1.
sigsetjmp() and siglongjmp()
sigsetjmp() and siglongjmp() also perform nonlocal gotos, but provide predictable handling of the process signal mask.
If, and only if, the savesigs argument provided to sigsetjmp() is nonzero, the process's current signal mask is saved in env and will be
restored if a siglongjmp() is later performed with this env.
简而言之,sigsetjmp的作用就类似于goto,但作用不局限于一个函数内。实际作用时就是如果函数直接往下走,遇到sigsetjmp接口了,那么该接口返回0,此时将环境信息保存在一个变量里,然后程序继续执行;如果函数是走到setlongjmp,则此时程序会跳转到之前保存的环境信息处,而且sigsetjmp接口返回非0值。
信号处理函数
Linux下提供sigaction函数用于修改信号处理函数,可以自定义信号处理函数,通过sigaction挂接,这样产生信号时就不是进入系统默认的处理函数,而是进入自定义函数。结合sigsetjmp和siglongjmp即可实现我们的目标----段异常后不复位。
用简单的代码示例:用数字标明进入顺序
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <ucontext.h>
#define DEBUG do {printf("%s<%d> enter!\n", __FUNCTION__, __LINE__); fflush(stdout);} while (0)
static sigjmp_buf s_sigEnv; /* 保存环境信息 */
static void SignalHandle_Sigsegv(int signal, siginfo_t *info, void *ucontext) { /* 自定义的信号处理函数,只做简单打印和跳转 */
DEBUG; /* 2 */
printf("sig:%d, error addr:%p\n", signal, info->si_addr);
siglongjmp(s_sigEnv, 0);
return ;
}
static void SignalHandleCallBackInit(void) {
struct sigaction actInfo;
actInfo.sa_sigaction = SignalHandle_Sigsegv; /* 挂接自定义的信号处理函数 */
actInfo.sa_flags = SA_SIGINFO; /* 传递参数到此函数中 */
sigaction(SIGSEGV, &actInfo, NULL);
return ;
}
static void ErrFunc(void) {
int *addr = (int *) 0x1234;
int temp;
if (sigsetjmp(s_sigEnv, 0) == 0) {
DEBUG; /* 1 */
temp = *addr; /* 发生异常 */
DEBUG;
} else {
DEBUG; /* 3 */
}
return ;
}
int main() {
SignalHandleCallBackInit();
ErrFunc();
return 0;
}
运行结果如下:可见确实不会因为段异常而复位,而且函数执行顺序如我们期待。
ErrFunc<36> enter!
SignalHandle_Sigsegv<13> enter!
sig:11, error addr:0x1234
ErrFunc<40> enter!
至此,即可实现段异常后不复位,当然,还是应该完善后处理函数来寻找及修复段异常原因,此文不表。