2017-02-25

1. 什么情况下会调用fixup_exception

linux-3.10.86/arch/arm/mm/fault.c

static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{

    if (!user_mode(regs))
        goto no_context;


no_context:
    __do_kernel_fault(mm, addr, fsr, regs);
    return 0;
}

所以, 用户态(usermode(regs))发起的 读或写 , copy to/from user等不会调用到 _dokernelfault, 也就不会调用fixupexception的. (fixupexception 仅会被 _dokernel_fault调用.)

2. getuser 和 _ex_table

linux-3.10.86/arch/arm/include/asm/uaccess.h

#define __get_user_asm_byte(x,addr,err)             \
    __asm__ __volatile__(                   \
    "1: " TUSER(ldrb) " %1,[%2],#0\n"           \
    "2:\n"                          \
    "   .pushsection .fixup,\"ax\"\n"           \
    "   .align  2\n"                    \
    "3: mov %0, %3\n"               \
    "   mov %1, #0\n"               \
    "   b   2b\n"                   \
    "   .popsection\n"                  \
    "   .pushsection __ex_table,\"a\"\n"        \
    "   .align  3\n"                    \
    "   .long   1b, 3b\n"               \
    "   .popsection"                    \
    : "+r" (err), "=&r" (x)                 \
    : "r" (addr), "i" (-EFAULT)             \
    : "cc")

在 _extable section中定义了如下数据: .long 1b, 3b
3b是 1b处的修复指令
当1b处发生异常时, 系统根据情况, 会跳转到标号3的指令处继续执行.
上面的例子, 将函数返回值设置为-EFAULT, get到的值设置为0.

3. fixup_exception

linux-3.10.86/include/asm-generic/vmlinux.lds.h

/*
 * Exception table
 */
#define EXCEPTION_TABLE(align)                      \
    . = ALIGN(align);                       \
    __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {       \
        VMLINUX_SYMBOL(__start___ex_table) = .;         \
        *(__ex_table)                       \
        VMLINUX_SYMBOL(__stop___ex_table) = .;          \
    }

linux-3.10.86/arch/arm/mm/fault.c

__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
          struct pt_regs *regs)
{
    /*
     * Are we prepared to handle this kernel fault?
     */
    if (fixup_exception(regs))
        return;
    ...
}

linux-3.10.86/arch/arm/mm/extable.c

int fixup_exception(struct pt_regs *regs)
{
    const struct exception_table_entry *fixup;
    fixup = search_exception_tables(instruction_pointer(regs));
    if (fixup) {
        regs->ARM_pc = fixup->fixup;
        ...
    }

    return fixup != NULL;
}

这里修改了ptregs的pc值, 后续将ptregs还原, 也就是修改了程序的执行.

4. 例

4.1 断点

下断点, 看看 发生fixupexception的上下文. 我们需要仅在fixupexception()中if (fixup) 成立时的断点.

(gdb) disass fixup_exception
Dump of assembler code for function fixup_exception:
   0xc001cf40 <+0>: mov r12, sp
   0xc001cf44 <+4>: push    {r3, r4, r11, r12, lr, pc}
   0xc001cf48 <+8>: sub r11, r12, #4
   0xc001cf4c <+12>:    push    {lr}        ; (str lr, [sp, #-4]!)
   0xc001cf50 <+16>:    bl  0xc000efa8 <__gnu_mcount_nc>
=> 0xc001cf54 <+20>:    mov r4, r0
   0xc001cf58 <+24>:    ldr r0, [r0, #60]   ; 0x3c
   0xc001cf5c <+28>:    bl  0xc00475c4 <search_exception_tables>
   0xc001cf60 <+32>:    cmp r0, #0
   0xc001cf64 <+36>:    ldrne   r3, [r0, #4] ;这个偏移量4, 指向了fixup, 对应代码fixup->fixup
   0xc001cf68 <+40>:    strne   r3, [r4, #60]   ; 0x3c
   0xc001cf6c <+44>:    subs    r0, r0, #0
   0xc001cf70 <+48>:    movne   r0, #1  ; fixup != NULL
   0xc001cf74 <+52>:    ldm sp, {r3, r4, r11, sp, pc}
End of assembler dump

(gdb) bt
#0  0xc001cf70 in fixup_exception (regs=0xc05706e8)
    at arch/arm/mm/extable.c:21
#1  0xc001d1a8 in __do_kernel_fault (mm=0x0, addr=0, fsr=5, 
    regs=0xc7827e78) at arch/arm/mm/fault.c:138
#2  0xc001d3d0 in do_page_fault (addr=0, fsr=5, regs=0xc7827e78)
    at arch/arm/mm/fault.c:393
#3  0xc001d6e4 in do_translation_fault (addr=<value optimized out>, 
    fsr=5, regs=<value optimized out>) at arch/arm/mm/fault.c:432
#4  0xc0008474 in do_DataAbort (addr=0, fsr=3226928864, 
    regs=0xc7827e78) at arch/arm/mm/fault.c:549
#5  0xc000e9d8 in __dabt_svc () at arch/arm/kernel/entry-armv.S:194
---Type <return> to continue, or q <return> to quit--- 
Backtrace stopped: frame did not save the PC



0xc001cf64 <+36>:   ldrne   r3, [r0, #4]
0xc001cf68 <+40>:   strne   r3, [r4, #60]   ; 0x3c

断点应该设置在 strne r3, [r4, #60] ; regs->ARMpc = fixup->fixup 这样才能通过ptregs知道异常发生前 发生了什么.

(gdb) b *0xc001cf68

4.2 异常发生前

b *0xc001cf68 停下来后,

(gdb) disass $pc
Dump of assembler code for function fixup_exception:
   0xc001cf40 <+0>: mov r12, sp
   0xc001cf44 <+4>: push    {r3, r4, r11, r12, lr, pc}
   0xc001cf48 <+8>: sub r11, r12, #4
   0xc001cf4c <+12>:    push    {lr}        ; (str lr, [sp, #-4]!)
   0xc001cf50 <+16>:    bl  0xc000efa8 <__gnu_mcount_nc>
   0xc001cf54 <+20>:    mov r4, r0
   0xc001cf58 <+24>:    ldr r0, [r0, #60]   ; 0x3c
   0xc001cf5c <+28>:    bl  0xc00475c4 <search_exception_tables>
   0xc001cf60 <+32>:    cmp r0, #0
   0xc001cf64 <+36>:    ldrne   r3, [r0, #4]
=> 0xc001cf68 <+40>:    strne   r3, [r4, #60]   ; 0x3c
   0xc001cf6c <+44>:    subs    r0, r0, #0
   0xc001cf70 <+48>:    movne   r0, #1
   0xc001cf74 <+52>:    ldm sp, {r3, r4, r11, sp, pc}

r3存储的是 fixup

(gdb) p /x $r3
$14 = 0xc044a79c
(gdb) info symbol $r3
__idmap_text_end + 1724 in section .text

[r4, #60]原先要继续执行的位置,

(gdb) p /x $r4+60
$4 = 0xc7827eb4
(gdb) p /x  *0xc7827eb4
$5 = 0xc006e5dc  #出错指令是
(gdb) i sym 0xc006e5dc
cmpxchg_futex_value_locked + 80 in section .text

(gdb) i sym (*($r4+56))  # 原本返回
futex_init + 36 in section .init.text

所以, 调用fixup_exception的上下文是:

futex_init
|--futex_detect_cmpxchg
|   |--cmpxchg_futex_value_locked  //这里面发生异常

4.3 fixup

(gdb) disass (*($r4+60))
Dump of assembler code for function cmpxchg_futex_value_locked:
   0xc006e58c <+0>: mov r12, sp
   0xc006e590 <+4>: push    {r3, r4, r5, r6, r11, r12, lr, pc}
   0xc006e594 <+8>: sub r11, r12, #4
   0xc006e598 <+12>:    push    {lr}        ; (str lr, [sp, #-4]!)
   0xc006e59c <+16>:    bl  0xc000efa8 <__gnu_mcount_nc>
   0xc006e5a0 <+20>:    mov r4, sp
   0xc006e5a4 <+24>:    bic r12, r4, #8128  ; 0x1fc0
   0xc006e5a8 <+28>:    bic r12, r12, #63   ; 0x3f
   0xc006e5ac <+32>:    ldr r5, [r12, #4]
   0xc006e5b0 <+36>:    add r4, r5, #1
   0xc006e5b4 <+40>:    str r4, [r12, #4]
   0xc006e5b8 <+44>:    ldr r4, [r12, #8]
   0xc006e5bc <+48>:    adds    r5, r1, #4
   0xc006e5c0 <+52>:    sbcscc  r5, r5, r4
   0xc006e5c4 <+56>:    movcc   r4, #0
   0xc006e5c8 <+60>:    cmp r4, #0
   0xc006e5cc <+64>:    mvnne   r4, #13  ;根据这里的13, 大概对应if !access_ok  return -EFAULT
   0xc006e5d0 <+68>:    bne 0xc006e5fc <cmpxchg_futex_value_locked+112>
   0xc006e5d4 <+72>:    dmb sy
   0xc006e5d8 <+76>:    mvn r5, #13   
   0xc006e5dc <+80>:    ldrex   r6, [r1] ;===引发异常===
   0xc006e5e0 <+84>:    teq r6, r2
   0xc006e5e4 <+88>:    strexeq r4, r3, [r1]
   0xc006e5e8 <+92>:    movne   r4, #0
   0xc006e5ec <+96>:    teq r4, #0
   0xc006e5f0 <+100>:   bne 0xc006e5dc <cmpxchg_futex_value_locked+80>
   0xc006e5f4 <+104>:   dmb sy
   0xc006e5f8 <+108>:   str r6, [r0]
   0xc006e5fc <+112>:   ldr r1, [r12, #4]  ;access_ok()失败后到这里
   0xc006e600 <+116>:   sub r0, r1, #1
   0xc006e604 <+120>:   str r0, [r12, #4]
   0xc006e608 <+124>:   ldr r3, [r12]
   0xc006e60c <+128>:   tst r3, #2
   0xc006e610 <+132>:   bne 0xc006e61c <cmpxchg_futex_value_locked+144>
   0xc006e614 <+136>:   mov r0, r4
   0xc006e618 <+140>:   ldm sp, {r3, r4, r5, r6, r11, sp, pc}
   0xc006e61c <+144>:   bl  0xc04488e8 <preempt_schedule>
   0xc006e620 <+148>:   b   0xc006e614 <cmpxchg_futex_value_locked+136>
---Type <return> to continue, or q <return> to quit---
End of assembler dump.

综合上面来看, pt_regs中的pc值是已经修正过(根据情况-8 -4)的值了, 对吗?

cmpxchg_futex_value_locked -> futex_atomic_cmpxchg_inatomic
{
...
: "=&r" (ret), "=&r" (val)
: "r" (oldval), "r" (newval), "r" (uaddr), "Ir" (-EFAULT)

}

#define EFAULT      14  /* Bad address */

被翻译为: mvn r5, #13

ldrex   r6, [r1] ;引发异常

static inline int
futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr,
                  u32 oldval, u32 newval)
{


}

r1是 u32 __user *uaddr

fixup代码是

0xc044a79c <+1724>: mov r4, r5
0xc044a7a0 <+1728>: b   0xc006e5f4 <cmpxchg_futex_value_locked+104>

r5是-EFAULT, r4是ret,
设置返回值为-EFAULT, 两 dmb sy 之间的代码就不执行了.

4.4 看下_extable的设置

linux-3.10.86/arch/arm/include/asm/futex.h

futex_atomic_cmpxchg_inatomic
{
    ...
    smp_mb();
    __asm__ __volatile__("@futex_atomic_cmpxchg_inatomic\n"
    "1: ldrex   %1, [%4]\n"
    "   teq %1, %2\n"
    "   ite eq  @ explicit IT needed for the 2b label\n"
    "2: strexeq %0, %3, [%4]\n"
    "   movne   %0, #0\n"
    "   teq %0, #0\n"
    "   bne 1b\n"
    __futex_atomic_ex_table("%5")
    : "=&r" (ret), "=&r" (val)
    : "r" (oldval), "r" (newval), "r" (uaddr), "Ir" (-EFAULT)
    : "cc", "memory");
    smp_mb();
...
}


#define __futex_atomic_ex_table(err_reg)            \
    "3:\n"                          \
    "   .pushsection __ex_table,\"a\"\n"        \
    "   .align  3\n"                    \
    "   .long   1b, 4f, 2b, 4f\n"           \
    "   .popsection\n"                  \
    "   .pushsection .fixup,\"ax\"\n"           \
    "   .align  2\n"                    \
    "4: mov %0, " err_reg "\n"          \
    "   b   3b\n"                   \
    "   .popsection"

这里1b, 4f,
1b指的是1: ldrex %1, [%4], 也就是引发异常的ldrex r6, [r1].
4f是4: mov %0, %5 和 b 3b
也就是mov r4, r5 和 b 0xc006e5f4

4.5 小结

linux-3.10.86/kernel/futex.c

static void __init futex_detect_cmpxchg(void)
{
#ifndef CONFIG_HAVE_FUTEX_CMPXCHG
    u32 curval;

    if (cmpxchg_futex_value_locked(&curval, NULL, 0, 0) == -EFAULT)
        futex_cmpxchg_enabled = 1;
#endif
}

这里要判断cmpxchgfutexvalue_locked的返回值, 正好, fixup代码完成了这任务. 内核这样, 完成了一些需要detect的初始化.

本文地址: https://awakening-fong.github.io/posts/mm/fixup_exception

转载请注明出处: https://awakening-fong.github.io


若无法评论, 请打开JavaScript, 并通过proxy.


blog comments powered by Disqus