fixup_exception
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