最近编写Linux用户态程序,会涉及到一些对内存的操作,因此经常会遇到段错误。
segfault at 99ef469b ip 0000000099ef469b sp 00007ffff238e878 error 14 in zero (deleted)[7f6299ef4000+7a2000]
最近老是遇到一个错误码为14的段错误,通过网络查阅资料,发现很多资料都写的是错误码是由三个bit来表示,我还根据这些资料做了笔记如下:
error number是由三个bit组成的,从高到底分别为bit2 bit1和bit0,所以它的取值范围是0~7。
bit2: 值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界。
bit1: 值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界。
bit0: 值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址。
但是我现在遇到了错误码为:14,三个bit根本没办法表示出14,因此我决定去查看到底这样一个段错误是怎样打印出来的。
我们遇到段错误之后,经常做的就是通过dmesg打印出段错误信息,通过这里我觉得可以在内核源码中进行查找。
由于最近开发使用的内核版本是linux-3.10,所以我决定先在3.10中找,然后再到linux-4.4.4中进行比对。
最终找到在内核源码文件:/arch/x86/mm目录下fault.c文件。
linux-3.10 ——> linux-3.10/arch/x86/mm/fault.c
linux-4.4.4 ——> linux-4.4.4/arch/x86/mm/fault.c
在里面找到了,show_signal_msg函数,里面内容如下:
/*
* Print out info about fatal segfaults, if the show_unhandled_signals
* sysctl is set:
* 如果设置了show_unhandled_signals sysctl,请打印有关致命段错误的信息:
*/
static inline void
show_signal_msg(struct pt_regs *regs, unsigned long error_code,
unsigned long address, struct task_struct *tsk)
{
if (!unhandled_signal(tsk, SIGSEGV))
return;
if (!printk_ratelimit())
return;
printk("%s%s[%d]: segfault at %lx ip %p sp %p error %lx",
task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG,
tsk->comm, task_pid_nr(tsk), address,
(void *)regs->ip, (void *)regs->sp, error_code);
print_vma_addr(KERN_CONT " in ", regs->ip);
printk(KERN_CONT "\n");
}
通过里面的内容基本可以断定dmesg中打印出来的段错误信息就是出自这个函数,但是为了保证绝对的正确性,还是通过更改内核源码,在里面添加一个打印信息来判断是否由这个函数输出。
通过添加判断打印信息,重新编译内核,最后在两个版本内核中都看到了我们的打印信息:
linux-3.10:
linux-4.4.4:
添加代码的位置有点不多,所以扰乱了报错信息,不过不影响判断。蓝色框中标出的内容就是我添加的打印信息,从中可以确定两个版本内核都是通过show_signal_msg输出段错误信息的。
错误码是通过一个error_code的参数表示的,继续在fault.c中查找,发现如下内容:
/*
* Page fault error code bits:
*
* bit 0 == 0: no page found 1: protection fault
* bit 1 == 0: read access 1: write access
* bit 2 == 0: kernel-mode access 1: user-mode access
* bit 3 == 1: use of reserved bit detected
* bit 4 == 1: fault was an instruction fetch
*/
enum x86_pf_error_code {
PF_PROT = 1 << 0,
PF_WRITE = 1 << 1,
PF_USER = 1 << 2,
PF_RSVD = 1 << 3,
PF_INSTR = 1 << 4,
};
由此其实发现,错误码其实是由5个bit来进行表示的,而且每个bit取值不同的含义都进行了说明,通过这个我们就判断出了到底是发生了什么错误,然后结合
https://blog.csdn.net/SweeNeil/article/details/83926778
来进行调试排查即可!!
欢迎交流讨论~