嵌入式Linux

您所在的位置:网站首页 access-eapexe应用程序错误 嵌入式Linux

嵌入式Linux

2024-07-15 18:49| 来源: 网络整理| 查看: 265

简介:

    很多时候我们会遇到段错误:segmentation fault,而段错误有时是由内核引起的,有时是由应用程序引起的。在内核态时,发生段错误时会打印oops信息,但是在用户态时,发生段错误却只会打印segmentation fault而并不会打印其他的信息。所以本文主要介绍在用户态时,通过修改内核设置和添加启动参数来打印引发segmentation fault的信息。

 

 Linux内核:linux-2.6.22.6

 所用开发板:JZ2440 V3(S3C2440A)

声明:

    本文是看完韦东山老师视频并结合其他网友文章所写,文中引用其他网友文章内容的位置我会标明。希望我的文章对你有所帮助。

segmentation fault:

    存储器区块错误(英语:Segmentation fault,经常被缩写为segfault),又译为存储器段错误,也称访问权限冲突(access violation),是一种程序错误。它会出现在当程序企图访问CPU无法定址的存储器区块时。当错误发生时,硬件会通知操作系统产生了存储器访问权限冲突的状况。操作系统通常会产生核心转储文件(core dump)以方便程序员进行除错。通常该错误是由于调用一个地址,而该地址为空(NULL)所造成的,例如链表中调用一个未分配地址的空链表单元的元素。数组访问越界也可能产生这个错误。

    发生segmentation fault时,MMU 产生内存保护异常 GPF(异常号 13)时,异常处理程序发送相应信号 SIGSEGV,SIGSEGV 的默认信号处理程序终止进程运行。如下图:

 

内核代码:

    介绍了segmentation fault,那么我们下面就要了解一下在内核中segmentation fault由什么引起。我们知道当发生段错误时异常处理函数会发送SIGSEGV信号来结束该进程,那么我们就要看看在哪里定义与SIGSEGV信号相关的函数。我们去内核中搜“SIGSEGV”,找到:arch\arm\mm\fault.c中的fsr_info结构体:

static struct fsr_info { int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs); //调用函数 int sig; //信号 int code; const char *name; //错误名称 } fsr_info[] = { /* * The following are the standard ARMv3 and ARMv4 aborts. ARMv5 * defines these to be "precise" aborts. */ { do_bad, SIGSEGV, 0, "vector exception" }, { do_bad, SIGILL, BUS_ADRALN, "alignment exception" }, { do_bad, SIGKILL, 0, "terminal exception" }, { do_bad, SIGILL, BUS_ADRALN, "alignment exception" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" }, };

    在fsr_info中大多数是调用do_bad函数,而do_bad函数其实就是简单的返回1,并不做其他的处理:

static int do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { return 1; }

    而下面我们主要分析的是: 

 "section translation fault" : do_translation_fault段转换错误,即找不到一级页表  "page translation fault"     : do_page_fault页表错误,即线性地址无效,没有对应的物理地址  "section permission fault" : do_sect_fault  段权限错误,即二级页表权限错误

 "page permission fault"     : do_page_fault页权限错误

 

 do_translation_fault函数:转化错误,一级页表中不含有一个有效地址值。

static int do_translation_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { unsigned int index; pgd_t *pgd, *pgd_k; pmd_t *pmd, *pmd_k;         /* 如果是用户空间地址,调用do_page_fault,转入和页表错误、页权限错误同样的处理流程。 */ if (addr < TASK_SIZE) return do_page_fault(addr, fsr, regs); index = pgd_index(addr);         /* * 如果是内核空间地址,会判断该地址对应的二级页表指针是否在init_mm中。 * 如果在init_mm里面,那么复制该二级页表指针到当前进程的一级页表;否则,调用do_bad_area处理(可能会调用到fixup) */ pgd = cpu_get_pgd() + index; pgd_k = init_mm.pgd + index; if (pgd_none(*pgd_k)) goto bad_area; if (!pgd_present(*pgd)) set_pgd(pgd, *pgd_k); pmd_k = pmd_offset(pgd_k, addr); pmd = pmd_offset(pgd, addr); if (pmd_none(*pmd_k)) goto bad_area; copy_pmd(pmd, pmd_k); return 0; bad_area: do_bad_area(addr, fsr, regs); return 0; }

do_page_fault函数:

 

    do_page_fault完成了真正的物理页面分配工作,另外栈扩展、mmap的支持等也都在这里。对于物理页面的分配,会调用到do_anonymous_page->。。。-> __rmqueue,__rmqueue中实现了物理页面分配的伙伴算法。     如果当前没有足够物理页面供内存分配,即分配失败:        内核模式下的abort会调用__do_kernel_fault,这与段权限错误中的处理一样。         用户模式下,会调用do_group_exit退出该任务所属的进程。     用户程序申请内存空间时,如果库函数本身的内存池不能满足分配,会调用brk系统调用向系统申请扩大堆空间。但此时扩大的只是线性空间,直到真正使用到那块线性空间时,系统才会通过data abort分配物理页面。所以,malloc返回不为NULL只能说明得到了线性空间的资源,真正物理内存分配失败时,进程还是会以资源不足为由,直接退出。

 

do_sect_fault函数:

static int do_sect_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { do_bad_area(addr, fsr, regs); return 0; }

    do_sect_fault函数直接调用do_bad_area作处理,并返回0。

    我们看到上面的函数都调用了do_bad_area,那么我们看看在do_bad_area函数里做了什么:

void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { struct task_struct *tsk = current; struct mm_struct *mm = tsk->active_mm; /* * 判断是在用户态还是内核态 */ if (user_mode(regs)) __do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs); else __do_kernel_fault(mm, addr, fsr, regs); }

    从上面可以看出,这里主要是判断在用户态还是在内核态,在用户态就调用__do_user_fault函数,而在内核态就调用:__do_kernel_fault函数。而user_mode宏为:

#define user_mode(regs) \ (((regs)->ARM_cpsr & 0xf) == 0)

    从中可以看出,通过当前状态寄存器的值与0xf做与运算。

 

M[4:0]处理器模式ARM模式可访问的寄存器THUMB模式可访问的寄存器0b10000用户模式PC,CPSR,R0~R14PC,CPSR,R0~R7,LR,SP0b10001FIQ模式PC,CPSR,SPSR_fiq,R14_fiq~R8_fiq,R0~R7PC,CPSR,SPSR_fiq,LR_fiq,SP_fiq,R0~R70b10010IRQ模式PC,CPSR,SPSR_irq,R14_irq~R13_irq,R0~R12PC,CPSR,SPSR_irq,LR_irq,SP_irq,R0~R70b10011管理模式PC,CPSR,SPSR_svc,R14_svc~R13_svc,R0~R12PC,CPSR,SPSR_svc,LR_svc,SP_svc,R0~R70b10111中止模式PC,CPSR,SPSR_abt,R14_abt~R13_abt,R0~R12PC,CPSR,SPSR_abt,LR_abt,SP_abt,R0~R70b11011未定义模式PC,CPSR,SPSR_und,R14_und~R13_und,R0~R12PC,CPSR,SPSR_und,LR_und,SP_und,R0~R70b11111系统模式PC,CPSR,R0~R14PC,CPSR,LR,SP,R0~R74

    从上面知道只有用户模式当前状态寄存器的值与0xf做与运算的值为0,而其他模式时都不为0 。

在内核态时:

    由于我们有内核态的oops信息,所以我们先分析在内核态时的函数,然后我们再分析在用户态时的函数就会好分析一些。

static void __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, struct pt_regs *regs) { /* * 如果可以修复这个错误,我们就修复他,并返回 */ if (fixup_exception(regs)) return; /* * 如果不能修复,结束进程,打印oops信息 */ bust_spinlocks(1); printk(KERN_ALERT "Unable to handle kernel %s at virtual address %08lx\n", (addr < PAGE_SIZE) ? "NULL pointer dereference" : "paging request", addr); show_pte(mm, addr); die("Oops", regs, fsr); bust_spinlocks(0); do_exit(SIGKILL); }

    这里我们主要分析当不能修复时,打印的oops信息。我想大家看到:

printk(KERN_ALERT "Unable to handle kernel %s at virtual address %08lx\n", (addr < PAGE_SIZE) ? "NULL pointer dereference" : "paging request", addr);

    是不是很熟悉啊,在我们内核的oops信息中为:

Unable to handle kernel paging request at virtual address 56000050

    而show_pte函数则是打印在mm中页表与地址的关系:

void show_pte(struct mm_struct *mm, unsigned long addr) { pgd_t *pgd; printk(KERN_ALERT "pgd = %p\n", mm->pgd); pgd = pgd_offset(mm, addr); printk(KERN_ALERT "[%08lx] *pgd=%08lx", addr, pgd_val(*pgd));         ······ printk("\n"); }

    而对应的打印信息为:

pgd = c3edc000 [56000050] *pgd=00000000

    而die函数则打印与oops和寄存器相关的信息:

int die(const char * str, struct pt_regs * fp, long err) { static int die_counter; int nl = 0; console_verbose(); spin_lock_irq(&die_lock); printk("Oops: %s, sig: %ld [#%d]\n", str, err, ++die_counter); if (nl) printk("\n"); show_regs(fp); spin_unlock_irq(&die_lock); do_exit(err); }

   对应的信息为:

Internal error: Oops: 5 [#1] Modules linked in: first_drv CPU: 0 Not tainted (2.6.22.6 #1) PC is at first_drv_open+0x18/0x3c [first_drv] LR is at chrdev_open+0x14c/0x164 pc : [] lr : [] psr: a0000013 sp : c3ed5e88 ip : c3ed5e98 fp : c3ed5e94 r10: 00000000 r9 : c3ed4000 r8 : c049a300 r7 : 00000000 r6 : 00000000 r5 : c3e700c0 r4 : c06a4540 r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000 Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user Control: c000717f Table: 33edc000 DAC: 00000015 Process firstdrvtest (pid: 783, stack limit = 0xc3ed4258) Stack: (0xc3ed5e88 to 0xc3ed6000) 5e80: c3ed5ebc c3ed5e98 c008d888 bf000010 00000000 c049a300 5ea0: c3e700c0 c008d73c c0474f20 c3e79724 c3ed5ee4 c3ed5ec0 c0089e48 c008d74c 5ec0: c049a300 c3ed5f04 00000003 ffffff9c c002c044 c3cf4000 c3ed5efc c3ed5ee8 5ee0: c0089f64 c0089d58 00000000 00000002 c3ed5f68 c3ed5f00 c0089fb8 c0089f40 5f00: c3ed5f04 c3e79724 c0474f20 00000000 00000000 c3edd000 00000101 00000001 5f20: 00000000 c3ed4000 c046de08 c046de00 ffffffe8 c3cf4000 c3ed5f68 c3ed5f48 5f40: c008a16c c009fc70 00000003 00000000 c049a300 00000002 bed00edc c3ed5f94 5f60: c3ed5f6c c008a2f4 c0089f88 00008520 bed00ed4 0000860c 00008670 00000005 5f80: c002c044 4013365c c3ed5fa4 c3ed5f98 c008a3a8 c008a2b0 00000000 c3ed5fa8 5fa0: c002bea0 c008a394 bed00ed4 0000860c 00008720 00000002 bed00edc 00000001 5fc0: bed00ed4 0000860c 00008670 00000001 00008520 00000000 4013365c bed00ea8 5fe0: 00000000 bed00e84 0000266c 400c98e0 60000010 00008720 4021a2cc 4021a2dc Backtrace: [] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164) [] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8) r8:c3e79724 r7:c0474f20 r6:c008d73c r5:c3e700c0 r4:c049a300 [] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48) [] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48) r4:00000002 [] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4) r5:bed00edc r4:00000002 [] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28) [] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c) Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)

在用户态时:

    有了对内核态的介绍,现在我们讲用户态,大家可能就更好理解了。

static void __do_user_fault(struct task_struct *tsk, unsigned long addr, unsigned int fsr, unsigned int sig, int code, struct pt_regs *regs) { struct siginfo si; #ifdef CONFIG_DEBUG_USER if (user_debug & UDBG_SEGV) { printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); show_pte(tsk->mm, addr); show_regs(regs); } #endif tsk->thread.address = addr; tsk->thread.error_code = fsr; tsk->thread.trap_no = 14; si.si_signo = sig; si.si_errno = 0; si.si_code = code; si.si_addr = (void __user *)addr; force_sig_info(sig, &si, tsk); }

    从上面代码看,在内核态时错误打印的代码主要在:

#ifdef CONFIG_DEBUG_USER if (user_debug & UDBG_SEGV) { printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); show_pte(tsk->mm, addr); show_regs(regs); } #endif

    所以我们要想打印出用户态的错误信息要满足两个条件:

1.  定义CONFIG_DEBUG_USER

2. 满足条件:user_debug & UDBG_SEGV 不为0

    我们先看第一个条件:定义CONFIG_DEBUG_USER,这里我们有两种方法

        1. 直接在这个文件中定义CONFIG_DEBUG_USER,或者直积去掉这个预编译判断。但是这样会修改内核代码,为我们以后使用其他模块时编译内核带来麻烦。

        2. 在make menuconfig时将这个选项选中。

            具体做法为:

                1. 在make menuconfig中搜DEBUG_USER,然后按着他指示的路径去设置。

                2. 在kernel hacking选项中将[*]Verbose user fault messages 选中。

    下面我们看第二个条件,这里是设置user_debug & UDBG_SEGV不为0,那么我们就要看看user_debug是在哪里设置了,我们在内核中搜user_debug,发现在arch\arm\kernel\traps.c中:

__setup("user_debug=", user_debug_setup);

    而这个就是要我们在uboot的bootargs中加上user_debug=XXX选项来为user_debug赋值,关于__setup的设置在:嵌入式Linux——printk:printk打印机制分析中有介绍。而至于XXX具体等于多少我们就要看UDBG_SEGV的值了。

#define UDBG_UNDEFINED (1


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3