? ?我们在 shell 中启动一个进程,进程访问一个虚拟地址,MMU 将这个虚拟地址转化成物理地址的过程中,发现转化不了,于是产生一个中断。中断的过程即由 IDTR 找到 IDT,执行中断处理函数,最后调到 do_page_fault 函数。do_page_fault 在当前进程的 vma 中找这个地址(task_struct 维护一颗 vm_area 的红黑树),如果访问的虚拟地址没有落到任何一个 vma 中,那么 do_page_fault 会给进程发送一个 SIGSEGV 信号。
? ? SIGSEGV 信号的默认 handler 是 Core,终止这个进程,产生一个 core dump。终止的时候,这个进程把它的 status 给父进程 shell(segfault 的 status 是 139),shell 收到了之后,知道子进程是因 segfault 退出,就在屏幕上打出来一行字 Segmentation fault (core dumped)
链接:https://www.zhihu.com/question/338188059/answer/808073398
? ?涉及内容具体摘要:
- 虚拟内存:MMU 负责的是 PTE 页表项的转换,但是 mmap(lab: mmap) 的时候常常涉及的是 文件 等内容,所以需要使用 lazy allocation,或者 lazy load,具体就是先不 map pte(因为文件还没有加载,如果 map 了就会)而加载到 proc PCB(进程控制块) 的记录 vm 区域的 vm_area 结构体去。这个结构体可以是线性链式的但是导致检索速度慢,所以一般采用红黑树方案。每当真正访问到这个 virtual address 的时候,在 trap handler 里面再处理 lazy load 和真正的 map pte,这样 trap 返回后就能正常访问。
- 注册 handler:为了让 user space 能够从 kernel 里面获取信号,在 6.S081 里进行了 alarm 的编程练习 (lab: trap),具体实现是通过 register 的机制,user space 通过定义函数指针作为 signal handler 并 register 到 kernel 中,当 kernel trap 时候,查看 pointer 是否存在,从而 trap 跳转至 signal handler 指向的函数,所以这里 PCB 里肯定有一个函数指针表用于存储各种信号量的 handler 函数指针。
- 默认情况: 如果 OS 接受到 interrupt 而进入 trap handler,得知当前 signal 之后他会检索 proc 的 handler 列表有没注册这个 signal 的handler,从而进入默认的处理:即 dump (coredump文件包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等)和 kill。
内核 do_page_fault 函数源码阅读
? ? 以下附 Linux 内核 trap handler 转接的处理 page fault 的?do_page_fault 函数,可以发现 stack overflow 实际也会以 sigsegv 返回。
Stack Overflow
??? 了解 ptrace 后可以理解为什么 Stack Overflow 可以被 debugger 捕获。接下来理解为什么 Stack 空间需要设定。我们复习用户地址空间:
? 为了限制 stack,可行的方案有 canary space (类似 guard page)让 trap handler 来决定还能不能 grow。请理解栈溢出的实际行为是 sp 指针增加后访问了非法地址空间,即 SIGSEGV 信号的行为。此时如果无法 grow stack 的话就 core dump 和 kill 了。Linux 下限制 stack grow 的是 ulimit -s SIZE 可调整的,由于栈顶是 VM_MAX, 所以实际上是可以做很大的栈空间的,我野不太清楚为什么要限制栈的大小。
? ? 结合上述源码,我们知道有一个 first user address,这个东西是地址随机化的哪个,实际不是我们 limit 的部分,他是最底线的限制,不能超过为进程分布的虚拟空间。实际的涉及 ulimit 的部分则是由 expand_stack 函数内部判断的,如果超过限制将不进行分配从而返回错误。
如何在应用层捕获 SIGSEGV 判断栈溢出
??? 问题:为什么编写 SIGSEGV signal handler 无法捕获 stack overflow 的情况而还是 core dump 了呢?
? ? 明明都是 segmentation fault?答案是这里涉及的是,由于 handler 运行需要 stack !那么解决方案是 Signal Stack (The GNU C Library) 原理也很简单,就是用 signalstack syscall register 一个 stack 空间即可,这个栈建议设计的是一个堆上的空间。