memory 异常处理

Memory 异常入口点

/*

 * Data abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
    vector_stub    dabt, ABT_MODE, 8

    .long    __dabt_usr            @  0  (USR_26 / USR_32)
    .long    __dabt_invalid            @  1  (FIQ_26 / FIQ_32)
    .long    __dabt_invalid            @  2  (IRQ_26 / IRQ_32)
    .long    __dabt_svc            @  3  (SVC_26 / SVC_32)
    .long    __dabt_invalid            @  4
    .long    __dabt_invalid            @  5
    .long    __dabt_invalid            @  6
    .long    __dabt_invalid            @  7
    .long    __dabt_invalid            @  8
    .long    __dabt_invalid            @  9
    .long    __dabt_invalid            @  a
    .long    __dabt_invalid            @  b
    .long    __dabt_invalid            @  c
    .long    __dabt_invalid            @  d
    .long    __dabt_invalid            @  e
    .long    __dabt_invalid            @  f

crash> dis __dabt_svc
0xc000dba0 <__dabt_svc>:        sub     sp, sp, #68     ; 0x44
0xc000dba4 <__dabt_svc+4>:      tst     sp, #4
0xc000dba8 <__dabt_svc+8>:      subeq   sp, sp, #4
0xc000dbac <__dabt_svc+12>:     stm     sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}
0xc000dbb0 <__dabt_svc+16>:     ldm     r0, {r3, r4, r5}
0xc000dbb4 <__dabt_svc+20>:     add     r7, sp, #48     ; 0x30
0xc000dbb8 <__dabt_svc+24>:     mvn     r6, #0
0xc000dbbc <__dabt_svc+28>:     add     r2, sp, #68     ; 0x44
0xc000dbc0 <__dabt_svc+32>:     addeq   r2, r2, #4
0xc000dbc4 <__dabt_svc+36>:     push    {r3}            ; (str r3, [sp, #-4]!)
0xc000dbc8 <__dabt_svc+40>:     mov     r3, lr
0xc000dbcc <__dabt_svc+44>:     stm     r7, {r2, r3, r4, r5, r6}
0xc000dbd0 <__dabt_svc+48>:     mov     r2, sp
0xc000dbd4 <__dabt_svc+52>:     bl      0xc0019360 <v7_early_abort>
0xc000dbd8 <__dabt_svc+56>:     cpsid   i
0xc000dbdc <__dabt_svc+60>:     msr     SPSR_fsxc, r5
0xc000dbe0 <__dabt_svc+64>:     clrex
0xc000dbe4 <__dabt_svc+68>:     ldm     sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, sp, lr, pc}^

crash> dis v7_early_abort
0xc0019360 <v7_early_abort>:    clrex
0xc0019364 <v7_early_abort+4>:  mrc     15, 0, r1, cr5, cr0, {0} @ get FSR
0xc0019368 <v7_early_abort+8>:  mrc     15, 0, r0, cr6, cr0, {0} @ get FAR

0xc001936c <v7_early_abort+12>: b       0xc000838c <do_DataAbort>


arm架构的linux内核中,clrex指令的作用是什么,内核中什么时候才会用到?

先看clrex指令的作用:《arm architecture reference manual》B2-1292以下简称arm arm手册

The ClearExclusiveLocal() procedure takes as arguments the processor identifier processorid . The procedure clears the local record of processor processorid for which an address has had a request for an exclusive access. It is IMPLEMENTATION DEFINED whether this operation also clears the global record of processor processorid that an address has had a request for an exclusive access
该指令的作用就是在独占访问结束时,清除cpu中本地处理器针对某块内存区域的独占访问标志(核中的某个状态寄存器),以防在未清除时的其他操作,对系统产生影响。对于是否同时清除全局的独占访问标志,需要在设计cpu时的架构师决定。
这指令的作用很独特,在linux内核中用在什么地方呢?如下:
1、数据中止异常、指令预取中止异常的处理时调用(调用linaro-aarch64/arch/arm/mm/abort-ev7.s v7_early_abort==》clrex)
2、从svc模式下的irq异常、未定义指令异常、数据中止异常、指令预取中止异常,处理结束返回时调用 (调用宏:linaro-aarch64/arch/arm/kernel/entry-header.s svc_exit)
3、返回到用户层的快速系统调用慢速系统调用(ret_slow_syscall,ret_fast_syscall==》调用宏:linaro-aarch64/arch/arm/kernel/entry-header.s restore_user_regs==》clrex)
4、run_all_tests 函数调用(==》kprobe_arm_test_cases==》TEST_UNSUPPORTED("clrex") ==》clrex),该函数是一个驱动模块,可以动态加载。

如上所示:基本所有的异常都要用到该指令,系统调用的返回也能用到。虽然异常和系统调用的代码在内核中不多,但是当内核运行起来时,异常和系统调用的执行频率特别高!所以该指令还是非常有用的。

Fault Address and Fault Status Registers (FAR & FSR)


Aborts resulting from data accesses (data aborts) are acted upon by the CPU immediately, and the MMU places an encoded 4-bit value FS[3:0], along with the 4‑bit encoded Domain number, in the Fault Status Register (FSR).

In addition, the virtual processor address which caused the data abort is latched into the Fault Address Register (FAR). If an access violation simultaneously generates more than one source of abort, they are encoded in the priority given in Table 7.2.



/*

 * Dispatch a data abort to the relevant handler.

 */
asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
    const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
    struct siginfo info;
    //pr_err("do_DataAbort:fn %x\n", inf->fn);
    if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
        return;

    printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
        inf->name, fsr, addr);

    info.si_signo = inf->sig;
    info.si_errno = 0;
    info.si_code  = inf->code;
    info.si_addr  = (void __user *)addr;
    arm_notify_die("", regs, &info, fsr, 0);
}

static struct fsr_info 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,        SIGBUS,     BUS_ADRALN,    "alignment exception"           },
    { do_bad,        SIGKILL, 0,        "terminal exception"           },
    { do_bad,        SIGBUS,     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"           },
}

/*

 * First Level Translation Fault Handler

 *
 * We enter here because the first level page table doesn't contain
 * a valid entry for the address.
 *
 * If the address is in kernel space (>= TASK_SIZE), then we are
 * probably faulting in the vmalloc() area.
 *
 * If the init_task's first level page tables contains the relevant
 * entry, we copy the it to this task.  If not, we send the process
 * a signal, fixup the exception, or oops the kernel.
 *
 * NOTE! We MUST NOT take any locks for this case. We may be in an
 * interrupt or a critical region, and should only copy the information
 * from the master page table, nothing more.
 */

1. do_translation_fault如果发生异常时地址为内核空间:

1.1 主内存映射表已有该地址

do_translation_fault(unsigned long addr, unsigned int fsr,
             struct pt_regs *regs)
{
    unsigned int index;
    pgd_t *pgd, *pgd_k;
    pud_t *pud, *pud_k;
    pmd_t *pmd, *pmd_k;

    index = pgd_index(addr);

    /*
     * FIXME: CP15 C1 is write only on ARMv3 architectures.
     */
    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);

    pud = pud_offset(pgd, addr);
    pud_k = pud_offset(pgd_k, addr);

    if (pud_none(*pud_k))
        goto bad_area;
    if (!pud_present(*pud))
        set_pud(pud, *pud_k);

    pmd = pmd_offset(pud, addr);
    pmd_k = pmd_offset(pud_k, addr);
    index = (addr >> SECTION_SHIFT) & 1;

    if (pmd_none(pmd_k[index]))
        goto bad_area;

    copy_pmd(pmd, pmd_k);
    return 0;
}

1.2 如果主内存映射表也没有那?

    do_bad_area(addr, fsr, regs);

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;【这里用的是active_mm】

    /*
     * If we are in kernel mode at this point, we
     * have no context to handle this fault with.
     */
    if (user_mode(regs))
        __do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
    else
        __do_kernel_fault(mm, addr, fsr, regs);
}

/*
 * Oops.  The kernel tried to access some page that wasn't present.
 */
static void
__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;

    /*
     * No handler, we'll have to terminate things with extreme prejudice.
     */
    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);
}

2. do_translation_fault如果发生异常时地址为用户空间:

do_translation_fault(unsigned long addr, unsigned int fsr,
             struct pt_regs *regs)
{
    unsigned int index;
    pgd_t *pgd, *pgd_k;
    pud_t *pud, *pud_k;
    pmd_t *pmd, *pmd_k;

    if (addr < TASK_SIZE)
        return do_page_fault(addr, fsr, regs);
}

2.1 do_page_fault在用户空间

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;
    int write = fsr & FSR_WRITE;
    unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE |
                (write ? FAULT_FLAG_WRITE : 0);

    if (notify_page_fault(regs, fsr))
        return 0;

    tsk = current;
    mm  = tsk->mm;

    fault = __do_page_fault(mm, addr, fsr, flags, tsk);
    后面还有一些处理,可以return 0;不必到下面。

    __do_user_fault(tsk, addr, fsr, sig, code, regs);
    return 0;
}

/*
 * Something tried to access memory that isn't in our memory map..
 * User mode accesses just cause a SIGSEGV
 */
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;

    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);
}

do_translation_fault 如果发生在内核空间只是有可能从主内存中拷贝pgd entry,
而用户空间只是force_sig_info, 并没有分配内存啊?

__do_page_fault会做


static int __kprobes
__do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
        unsigned int flags, struct task_struct *tsk)
{
    struct vm_area_struct *vma;
    int fault;

    vma = find_vma(mm, addr);
    fault = VM_FAULT_BADMAP;
    if (unlikely(!vma))
        goto out;
    if (unlikely(vma->vm_start > addr))
        goto check_stack;

    /*
     * Ok, we have a good vm_area for this
     * memory access, so we can handle it.
     */
good_area:
    if (access_error(fsr, vma)) {
        fault = VM_FAULT_BADACCESS;
        goto out;
    }

    return handle_mm_fault(mm, vma, addr & PAGE_MASK, flags);

check_stack:
    /* Don't allow expansion below FIRST_USER_ADDRESS */
    if (vma->vm_flags & VM_GROWSDOWN &&
        addr >= FIRST_USER_ADDRESS && !expand_stack(vma, addr))
        goto good_area;
out:
    return fault;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值