Linux用户态程序踩内存时可以用mprotect定位,mprotect本身是linux系统上的一个系统 调用,这个系统调用可以改变一段内存的读写属性, 当有非法的访问访问对应的内存的时候 会给进程发一个SIGSEGV信号,进程可以在信号处理函数中加调试信息进行定位。
mprotect的参数为要保护的虚拟地址,保护地址的大小,和保护地址空间的属性。这里 地址size必须是已页对齐的,地址空间的属性有读、写、执行和不可接入。
显然,当被踩内存本身就是只读的时候,我们一开始就可以用mprotect把这段内存保护起来, 别的执行流踩了这段内存就会触发信号。如果,被踩的内存是一段可读可写的内存,我们 可以在正常执行的时候调用mprotect设置为读写,正常执行完后用mprotect设置为只读。
在信号处理函数中,可以调用backtrace, backtrace_symbols相关函数把调用栈打出来。 如下的测试代码,在X86上用gcc -rdynamic test.c编译运行是OK的,可以打出调用栈, 加-rdynamic是为了打出调用栈里的函数名。但是在ARM64的环境下,需要用 gcc -rdynamic -funwind-tables test.c来编译测试代码,否则只能打出模块的名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <malloc.h> #include <sys/mman.h> /* * test in x86 with gcc -rdynamic test.c is OK. * * however, in aarch64, return of backtrace is alway 1. * use gcc -rdynamic -funwind-tables test.c to solve this problem. */ void fun_3(void); void handler(int sig, siginfo_t *si, void *unused) { fun_3(); } void fun_3(void) { #define SIZE 10 void *buffer[SIZE]; char **strings; int n, i; n = backtrace(buffer, SIZE); strings = backtrace_symbols(buffer, n); for (i = 0; i < n; i++) { printf("%s\n", strings[i]); } free(strings); exit(EXIT_FAILURE); } void fun_2(void) { fun_3(); } void fun_1(void) { fun_2(); } void fun_0(void) { fun_1(); } int main() { struct sigaction sa; char *buffer; int pagesize; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; sigaction(SIGSEGV, &sa, NULL); pagesize = sysconf(_SC_PAGE_SIZE); buffer = memalign(pagesize, 4 * pagesize); if (mprotect(buffer, pagesize, PROT_READ | PROT_WRITE) == -1) printf("fail to set mprotect\n"); printf("write a in buffer a\n"); *buffer = 'a'; printf("write a in buffer b\n"); sleep(2); printf("write a in buffer c\n"); if (mprotect(buffer, pagesize, PROT_READ) == -1) printf("fail to set mprotect\n"); *buffer = 'b'; //fun_0(); exit(EXIT_SUCCESS); }