玩信号处理程序

2020-11-23 00:58:13

正如每个C和C ++程序员所了解的那样,如果您取消引用指向进程内存中映射空间之外的指针,则会出现分段错误,并且程序将崩溃。就语言本身而言,您没有第二次机会,也无法提前知道该取消引用操作是否会引爆炸弹。用技术术语来说,您正在调用未定义的行为,因此绝对不要这样做:您有责任事先知道指针是否有效,如果指针不正确,则必须保留这些指针。

但是,事实证明,尽管附带了很多精美的印刷品,但大多数实际的操作系统都会给您带来第二次机会。因此,我试图实现一个试图取消引用指针的函数:如果可以,它将为您提供值;如果不能,它会告诉您不能。我再次强调,除非是为了调试(或为了获得乐趣),否则这绝不应在实际程序中发生。

该函数基本上等效于返回* addr,不同之处在于,如果未映射addr不会崩溃,并且如果成功不是NULL,则将其设置为0或1表示未映射或映射addr。如果未映射addr,则返回值没有意义。

为了给您带来一些乐趣,我不会详细解释。基本上,想法是为SIGSEGV安装一个处理程序:如果地址无效,则调用该处理程序,该处理程序基本上是通过略微提高指令指针的位来修复所有内容,从而跳过故障指令。取消引用指令写为hardcodedAssembly字节,因此我确切地知道需要跳过多少个字节。

当然,这非常依赖于体系结构:我编写了i386和amd64变体(没有x32)。而且我不保证没有bugsor子字幕!

另一个解决方案是在解引用之前仅解析/ proc / self / maps并检查指针是否在被映射的区域中,但这会遇到TOCTTOUproblem的问题:另一个线程可能已更改了/ proc / self / maps之间的映射被解析以及指针被取消引用的时间(同样,解析该文件可能会花费相对较长的时间)。另一种不太依赖体系结构但仍不是纯C的方法是在尝试从信号处理程序进行取消引用和长跳转之前建立一个setjmp(但同样,您将需要在不同线程中使用不同的setjmp上下文以排除竞争条件)。

#define _GNU_SOURCE #include #include #include #include #include #include #ifdef __i386__ typedef uint32_t word_t; #define IP_REG REG_EIP #define IP_REG_SKIP 3 #define READ_CODE __asm__ __volatile__(“ .byte 0x8b,0x03 \ n” / * mov(%ebx),%eax * / \“ .byte 0x41 \ n” / * inc%ecx * / \:“ = a”(ret),“ = c”(tmp):“ b”(addr),“ c”(tmp)); #endif #ifdef __x86_64__ typedef uint64_t word_t; #定义IP_REG REG_RIP#定义IP_REG_SKIP 6#定义READ_CODE __asm__ __volatile__(“ .byte 0x48,0x8b,0x03 \ n” / * mov(%rbx),%rax * / \“ .byte 0x48,0xff,0xc1 \ n” / * inc%rcx * / \:“ = a”(ret),“ = c”(tmp):“ b”(addr),“ c”(tmp)); #endif静态void segv_action(int sig,siginfo_t * info,void * ucontext){(void)sig; (无效)信息; ucontext_t * uctx =(ucontext_t *)ucontext; uctx-> uc_mcontext gregs [IP_REG] + = IP_REG_SKIP; } struct sigaction peek_sigaction = {。 sa_sigaction = segv_action,。 sa_flags = SA_SIGINFO,。 sa_mask = 0,}; word_t偷看(word_t * addr,int *成功){word_t ret;内部tmp,res;结构签名prev_act; res = sigaction(SIGSEGV,&peek_sigaction,&prev_act);断言(res == 0); tmp = 0; READ_CODE res = sigaction(SIGSEGV,&prev_act,NULL);断言(res == 0);如果(成功){*成功= tmp;返回ret; } int main(){int成功; word_t数字= 22; word_t值;数字= 22;价值=偷看(&数字,&成功); printf(“%d%d \ n”,success,value);值= peek(NULL,&成功); printf(“%d%d \ n”,success,value);值=窥视((word_t *)0x1234,&成功); printf(“%d%d \ n”,success,value);返回0; }

暂时没有评论!