sfoolish / embedded

嵌入式开发中段错误触发过程

2013-09-04 posted in [嵌入式]

测试环境

  • TI-DM8167 + Linux 2.6.34

内核部分环境

内核配置选项

Kernel hacking  --->
  --> [*] Kernel debugging 
  --> [*] Verbose user fault messages   # CONFIG_DEBUG_USER

设置 user_debug

arch/arm/kernel/traps.c 代码修改。

 #ifdef CONFIG_DEBUG_USER
-unsigned int user_debug;
+unsigned int user_debug = 0xff;

 static int __init user_debug_setup(char *str)
 {
     get_option(&str, &user_debug);
     return 1;
 }
 __setup("user_debug=", user_debug_setup);
 #endif

arch/arm/mm/fault.c 代码片段。

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

user_debug 可以通过 bootcmd 参数进行设置,这里直接修改默认值。

do_page_fault 添加调试打印

arch/arm/mm/fault.c 代码修改。

 static int __kprobes
 do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 {
     struct task_struct *tsk;
     struct mm_struct *mm;
     int fault, sig, code;

+    if (strncmp(current->comm, "sig_test", strlen("sig_test")) == 0) {
+        printk("addr 0x%x fsr %x\n", addr, fsr);
+    }
     if (notify_page_fault(regs, fsr))
         return 0;

应用部分环境

测试一:非法地址访问

测试代码:

$ cat sig_test.c 
```
    int main(int argc, char const *argv[])
    {
        *(int *)0x5a = 0xa5;   // 这里用 0x5a / 0xa5 为了数值区分 
        
        return 0;
    }
```

交叉编译

$ arm-linux-uclibcgnueabi-gcc sig_test.c -o sig_test

反汇编测试程序

$ arm-linux-uclibcgnueabi-objdump -d sig_test
```
    000083c4 <main>:
        83c4:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
        83c8:       e28db000        add     fp, sp, #0      ; 0x0
        83cc:       e24dd00c        sub     sp, sp, #12     ; 0xc
        83d0:       e50b0008        str     r0, [fp, #-8]
        83d4:       e50b100c        str     r1, [fp, #-12]
        83d8:       e3a0305a        mov     r3, #90         ; 0x5a
        83dc:       e3a020a5        mov     r2, #165        ; 0xa5
        83e0:       e5832000        str     r2, [r3]        ; 注意这是异常点
        83e4:       e3a03000        mov     r3, #0          ; 0x0
        83e8:       e1a00003        mov     r0, r3
        83ec:       e28bd000        add     sp, fp, #0      ; 0x0
        83f0:       e8bd0800        pop     {fp}
        83f4:       e12fff1e        bx      lr
```

测试运行

# mount -t nfs -o nolock 172.9.21.107:/tftpboot/netra_rootfs /mnt
# /mnt/sig_test
```
    # do_page_fault 打印
    addr 0x5a fsr 817
    # __do_user_fault 打印
    sig_test: unhandled page fault (11) at 0x0000005a, code 0x817
    pgd = d3d34000
    [0000005a] *pgd=93d2d031, *pte=00000000, *ppte=00000000
    
    Pid: 77, comm:             sig_test policy 0 priority: 0
    CPU: 0    Not tainted  (2.6.34 #67)
    PC is at 0x83e0
    LR is at 0x4004a9c4
    pc : [<000083e0>]    lr : [<4004a9c4>]    psr: 20000010
    sp : bea6ee20  ip : 40057f30  fp : bea6ee2c
    r10: bea6ee30  r9 : 000083c4  r8 : 00008300
    r7 : bea6ef60  r6 : 00000001  r5 : bea6eeb4  r4 : 40057ed0
    r3 : 0000005a  r2 : 00000000  r1 : bea6eeb4  r0 : 00000001
    Flags: nzCv  IRQs on  FIQs on  Mode USER_32  ISA ARM  Segment user
    Control: 10c5387d  Table: 93d34019  DAC: 00000015
    Segmentation fault
```

注意这里的 PC is at 0x83e0 和上面 sig_test 反汇编 83e0: e5832000 str r2, [r3] 是对应的,也就是说 e5832000 指令时段错误的触发点。

原理分析

*(int *)0x5a = 0xa5; 对应以下汇编指令。

    83d8:       e3a0305a        mov     r3, #90         ; 0x5a
    83dc:       e3a020a5        mov     r2, #165        ; 0xa5
    83e0:       e5832000        str     r2, [r3]        ; 注意这是异常点

对 0x5a 地址进行写值操作触发缺页异常,内核执行 do_page_fault 进行缺页异常处理。

do_page_fault 执行过程

do_page_fault
    -> __do_page_fault
        -> find_vma            # 非法地址,返回 NULL
    -> __do_user_fault
        -> force_sig_info      # 向进程发送 SIGSEGV 信号,触发段错误

signal 处理过程

do_notify_resume
    -> do_signal
        -> get_signal_to_deliver
            -> do_coredump  if sig_kernel_coredump
            -> do_group_exit -> do_exit     # 进程退出

测试二:栈溢出

测试代码:

$ cat sig_test.c 
```
    int main(int argc, char const *argv[])
    {
        int *i;
    
        i = (int *)&i;
        while (1) {
            *i-- = 0;    // 栈向下增长
        }
    
        return 0;
    }
```

交叉编译

$ arm-linux-uclibcgnueabi-gcc sig_test.c -o sig_test

反汇编测试程序

$ arm-linux-uclibcgnueabi-objdump -d sig_test
```
    000083c4 <main>:
        83c4:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
        83c8:       e28db000        add     fp, sp, #0      ; 0x0
        83cc:       e24dd014        sub     sp, sp, #20     ; 0x14
        83d0:       e50b0010        str     r0, [fp, #-16]
        83d4:       e50b1014        str     r1, [fp, #-20]
        83d8:       e24b3008        sub     r3, fp, #8      ; 0x8
        83dc:       e50b3008        str     r3, [fp, #-8]
        83e0:       e51b3008        ldr     r3, [fp, #-8]
        83e4:       e3a02000        mov     r2, #0  ; 0x0
        83e8:       e5832000        str     r2, [r3]
        83ec:       e2433004        sub     r3, r3, #4      ; 0x4
        83f0:       e50b3008        str     r3, [fp, #-8]
        83f4:       eafffff9        b       83e0 <main+0x1c>
```

测试运行

# mount -t nfs -o nolock 172.9.21.107:/tftpboot/netra_rootfs /mnt
# /mnt/sig_test
```
    ... ...
    addr 0xbed30ffc fsr 817
    addr 0xbed2fffc fsr 817
    ... ...
    addr 0xbe532ffc fsr 817
    addr 0xbe531ffc fsr 817
    sig_test: unhandled page fault (11) at 0xbe531ffc, code 0x817
    pgd = d3d30000
    [be531ffc] *pgd=93d2c031, *pte=00000000, *ppte=00000000
    
    Pid: 77, comm:             sig_test policy 0 priority: 0
    CPU: 0    Not tainted  (2.6.34 #73)
    PC is at 0x83e8
    LR is at 0x4004a9c4
    pc : [<000083e8>]    lr : [<4004a9c4>]    psr: 20000010
    sp : bed31e18  ip : 40057f30  fp : bed31e2c
    r10: bed31e30  r9 : 000083c4  r8 : 00008300
    r7 : bed31f60  r6 : 00000001  r5 : bed31eb4  r4 : 40057ed0
    r3 : be531ffc  r2 : 00000000  r1 : bed31eb4  r0 : 00000001
    Flags: nzCv  IRQs on  FIQs on  Mode USER_32  ISA ARM  Segment user
    Control: 10c5387d  Table: 93d30019  DAC: 00000015
    sig_test/77: potentially unexpected fatal signal 11.
```

注意这里的 PC is at 0x83e8 和上面 sig_test 反汇编 83e8: e5832000 str r2, [r3] 是对应的,也就是说 e5832000 指令时段错误的触发点,访问地址 0xbe531ffc 出现异常。

原理分析

# ulimit -s
```
    8192
```

Linux 系统进程默认栈空间大小为 8M ,而上面地址 0xbed30ffc - 0xbe531ffc == 0x7FF000 因此是栈溢出导致段错误。

__do_page_fault 处理过程

__do_page_fault
    -> expand_stack
        -> expand_downwards
            -> acct_stack_growth
            {
                /* Stack limit test */
                if (size > ACCESS_ONCE(rlim[RLIMIT_STACK].rlim_cur))
                    return -ENOMEM;
                /*
                 * Overcommit..  This must be the final test, as it will
                 * update security statistics.
                 */
                if (security_vm_enough_memory_mm(mm, grow))
                    return -ENOMEM;
            }

当栈部分出现缺页异常时,系统会通过调用expand_stack进行扩展,扩展过程中会做一些安全监测,主要是栈大小限制判断和虚拟内存过量分配判断。

实际问题排查过程中,遇到类似 unhandled page fault (11) at 0xbe531ffc, code 0x817 的打印基本上可以怀疑是栈溢出导致异常的。具体原因定位的话,对于单线程的进程,进程起来之前后,通过 cat /proc/<pid>/maps 获取进程栈的其实地址,通过 getrlimit 系统调用获取进程的栈大小,最后计算一下就可以了。对于多线程,要根据线程库类型对于 linux thread 排查方法与单线程进程类似,但对于线程不停创建退出的场景就不太容易定位了;对于 NPTL 线程库暂时不太清楚。当然,可以暴力点,直接在内核中加打印。

嵌入式调试过程中交叉编译工具的使用

2013-09-04 posted in [嵌入式]

C 语言之 volatile

2013-09-04 posted in [嵌入式]

C 语言之函数栈结构分析

2013-09-04 posted in [嵌入式]

sfoolish

About

sfoolish

爱生活,爱代码,爱折腾。。。