> 文章列表 > C语言 非本地跳转 实现native层TryCatch

C语言 非本地跳转 实现native层TryCatch

C语言 非本地跳转 实现native层TryCatch

前言

最近研究native hook的技术,了解到了这个非本地跳转,本文就是介绍他,对于解决native crash非常有用。

非本地跳转介绍

C语言的本地跳转是指goto、break、continue等语句,但是这个语句最大局限就是只能实现函数内部的跳转。
C 语言提供了一种用户级异常控制流形式,称为非本地跳转(non local jump),它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用—返回序列。
非本地跳转是通过 setjmp 和 longjmp 等成对的函数来提供的,siglongjmp() is similar to longjmp(), except for the optional capability of restoring the signal mask. The sigsetjmp()—siglongjmp() pair, the setjmp()—longjmp() pair, the _setjmp()—_longjmp() pair, and the getcontext()—setcontext() pair cannot be intermixed.

setjmp和 longjmp使用

setjmp函数主要用来保存当前执行状态,作为后续跳转的目标。调用setjmp时,当前状态会被存放在env指向的结构中,这个env将被 long_jmp 操作作为参数,以返回调用点,跳转的结果看起来就好像刚从setjmp返回一样,所以这个保存状态的jmp_buf变量env一般定义为全局变量。
setjmp第一次调用setjmp的时候返回值为0;而从long_jmp操作返回时,返回值是由longjmp传入的参数value决定。通过判断setjmp的返回值,就可以判断当前返回的状态。

jmp_buf jmpbuffer;static void print_0(int i) {printf("print_0 start : i = %d\\n", i);i += 10;printf("print_0 end : i = %d\\n", i);longjmp(jmpbuffer, 1);printf("print_0 return");
}static void print_1(int i) {printf("print_1 start : i = %d\\n", i);i += 11;printf("print_1 end : i = %d\\n", i);longjmp(jmpbuffer, 2);printf("print_1 return");
}static void print_2(int i) {printf("print_2 start : i = %d\\n", i);i += 12;printf("print_2 end : i = %d\\n", i);longjmp(jmpbuffer, 3);printf("print_2 return");
}static void print_3(int i) {printf("print_3 start : i = %d\\n", i);i += 13;printf("print_3 end : i = %d\\n", i);printf("print_3 return\\n");
}void testJump() {int i = 0;int flag;int start = av_gettime();flag = setjmp(jmpbuffer);int end = av_gettime();printf("cost %d\\n", end - start);if (flag == 0) {print_0(i);} else if (flag == 1) {print_1(i);} else if ((flag == 2)) {print_2(i);} else {print_3(i);}
}

sigaction函数介绍

#include<signal.h>struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};int sigaction(int sig, const struct sigaction *act, struct sigaction *old_act);

sigaction会依参数signum指定的信号编号来设置该信号的处理函数。
参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。

  • sigaction结构体
    信号处理函数可以采用void (*sa_handler)(int)或void (*sa_sigaction)(int, siginfo_t *, void *)。
    到底采用哪个要看sa_flags中是否设置了SA_SIGINFO位,如果设置了就采用void (*sa_sigaction)(int, siginfo_t *, void *),此时可以向处理函数发送附加信息;默认情况下采用void (*sa_handler)(int),此时只能向处理函数发送信号的数值。
    sa_handler:此参数和signal()的参数handler相同,代表新的信号处理函数,其他意义请参考signal();
    sa_mask:用来设置在处理该信号时暂时将sa_mask指定的信号集搁置;
    sa_restorer:此参数没有使用;
    sa_flags :用来设置信号处理的其他相关操作,下列的数值可用。sa_flags还可以设置其他标志:

    • SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
    • SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
    • SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。
    • SA_SIGINFO:在接收到信号时将额外信息传递给信号处理程序。
  • 常见的signal信号
    C语言 非本地跳转 实现native层TryCatch

sigsetjmp和siglongjmp介绍

#include <setjmp.h>int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
  • siglongjmp() is similar to longjmp(), except for the optional capability of restoring the signal mask. The sigsetjmp()—siglongjmp() pair, the setjmp()—longjmp() pair, the _setjmp()—_longjmp() pair, and the getcontext()—setcontext() pair cannot be intermixed.
  • A stack environment and signal mask saved by sigsetjmp() can be restored only by siglongjmp().
  • The stack environment and signal mask saved by sigsetjmp() can subsequently be restored by siglongjmp(). these functions provide a way to perform a nonlocal goto.
  • For a stack environment previously saved in env by sigsetjmp(), the siglongjmp() function restores all the stack environment and, optionally, the signal mask, depending on whether it was saved by sigsetjmp(). The sigsetjmp() and siglongjmp() functions provide a way to perform a nonlocal goto.

sigsetjmp

Saves the current stack environment including, optionally, the current signal mask.
A call to sigsetjmp() causes it to save the current stack environment in env.

  • env is an address for a sigjmp_buf structure.

  • savemask is a flag used to determine if the signal mask is to be saved.
    If the value of the savemask parameter is nonzero, it will also save the current signal mask in env.
    If it has a value of 0, the current signal mask is not to be saved or restored as part of the environment. Any other value means the current signal mask is saved and restored.

  • Returned value
    sigsetjmp() returns 0 when it is invoked to save the stack environment and signal mask.

siglongjmp

A subsequent call to siglongjmp() restores the saved environment and signal mask (if saved by sigsetjmp()), and returns control to a point corresponding to the sigsetjmp() call.

  • env is an address for a sigjmp_buf structure.
  • val is the return value from siglongjmp().

实现native层 try catch

static sigjmp_buf env;static void sa_handler(int sig) {LOG("sa_handler: %d, 捕获到异常信号,执行跳转", sig);if (sig == SIGSEGV) {siglongjmp(env, 1);  // 捕获到异常了,跳转到jmp地址,参数为1}
}void testNativeCrash() {auto clock_start = clock();int ret = sigsetjmp(env, 1);  // 设置setjmpauto clock_end = clock();LOG("sigsetjmp cost: %ld", clock_end - clock_start);if (ret == 0) {LOG("sigsetjmp return 0, 调用成功");raise(SIGSEGV); // 抛出一个异常} else {LOG("sigsetjmp return %d, catch住异常了", ret); // 捕获到异常}
}void initSigaction() {struct sigaction action{};action.sa_flags = SA_SIGINFO;action.sa_handler = sa_handler;   // 注册信号处理函数sigemptyset(&action.sa_mask);sigaction(SIGSEGV, &action, nullptr);
}

sigaction
Android SIGQUIT(3) 信号拦截与处理
SIGSEGV的段错误保护实现(基于sigsetjmp)
sigsetjmp
siglongjmp
C语言——非本地跳转