Linux段错误Segfault内核层面分析

最近编写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段错误Segfault内核层面分析_第1张图片

linux-4.4.4:

Linux段错误Segfault内核层面分析_第2张图片

添加代码的位置有点不多,所以扰乱了报错信息,不过不影响判断。蓝色框中标出的内容就是我添加的打印信息,从中可以确定两个版本内核都是通过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

来进行调试排查即可!!

欢迎交流讨论~

你可能感兴趣的:(Linux内核开发)