kernel crash 发生后的那些事(一)

2024-06-03 16:32
文章标签 发生 kernel crash

本文主要是介绍kernel crash 发生后的那些事(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文根据 echo  c > /pro/sysrq-trigger 触发的 Kernel crash 为例,分析kernel crash 处理的过程。


当代码访问虚拟地址0时,会发生data abort,这是由MMU决定的,没有把virtual address 0 map 到任何有访问权限的物理地址。

当发生data abort时,PC 会跳转到W(b) vector_dabt + stubs_offset[arch/arm/kernel/entry-armv.S]。如果data abort 发生

在内核空间,会进而执行到__dabt_svc.

__dabt_svc

    .align    5
__dabt_svc:
    svc_entry
    mov    r2, sp
    dabt_helper

    @
    @ IRQs off again before pulling preserved data off the stack
    @
    disable_irq_notrace
    svc_exit r5                @ return from exception
 UNWIND(.fnend        )
ENDPROC(__dabt_svc)


__dabt_svc调用了宏dabt_helper

    .macro    dabt_helper

    @
    @ Call the processor-specific abort handler:
    @
    @  r2 - pt_regs
    @  r4 - aborted context pc
    @  r5 - aborted context psr
    @
    @ The abort handler must return the aborted address in r0, and
    @ the fault status register in r1.  r9 must be preserved.
    @

    bl    CPU_DABORT_HANDLER
    .endm

 CPU_DABORT_HANDLER的具体实现

dabt_helper调用了跳转指令bl    CPU_DABORT_HANDLER,标号CPU_DABORT_HANDLER有多个实现,可以根据  .config找到具体的实现。
在文件 arch/arm/include/asm/glue-df.h中找到:
#ifdef CONFIG_CPU_ABRT_EV7
# ifdef CPU_DABORT_HANDLER
#  define MULTI_DABORT 1
# else
#  define CPU_DABORT_HANDLER v7_early_abort
# endif
#endif
看上去挺特殊,为什么每行都以#开头,应该不是注释掉的意思吧。

到文件arch/arm/mm/abort-ev7.S:
    .align    5
ENTRY(v7_early_abort)
    /*
     * The effect of data aborts on on the exclusive access monitor are
     * UNPREDICTABLE. Do a CLREX to clear the state
     */
    clrex

    mrc    p15, 0, r1, c5, c0, 0        @ get FSR 【Fault Status Register (FSR)】
    mrc    p15, 0, r0, c6, c0, 0        @ get FAR 【Fault Address Register (FAR).】

    /*
     * V6 code adjusts the returned DFSR.
     * New designs should not need to patch up faults.
     */

    b    do_DataAbort
ENDPROC(v7_early_abort)
这里只关注 b    do_DataAbort,终于跳转到C 函数do_DataAbort

跳转到C函数 do_DataAbort

arch/arm/mm/fault.c
/*
 * 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;

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

请关注从汇编过来的入口参数:

do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
r0: mrc    p15, 0, r0, c6, c0, 0        @ get FAR 【Fault Address Register (FAR).】
r1: mrc    p15, 0, r1, c5, c0, 0        @ get FSR 【Fault Status Register (FSR)】
r2: @  r2 - pt_regs [r2是哪里]
这里涉及到CP15的几个寄存器:
The CP15 register c2 operations control the Translation Table Base (TTB).
The CP15 register c3 operations control the Domain Access Control (DAC) register.
The CP15 register c5 operations control the Fault Status Register (FSR).


根据fsr找到对应的处理函数

fsr_info是全局变量,类型为
struct fsr_info {
    int    (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs);
    int    sig;
    int    code;
    const char *name;
};
通过当前的fsr作为index 得到对应的出来函数。

/* FSR definition 对本平台而言,# CONFIG_ARM_LPAE is not set*/
#ifdef CONFIG_ARM_LPAE
#include "fsr-3level.c"
#else
#include "fsr-2level.c"
#endif

我们要根据 “const struct fsr_info *inf = fsr_info + fsr_fs(fsr);”得到具体的函数,因为do_DataAbort被多次调用去加载物理页,
如果在该函数do_DataAbort中打印,看上去一直在打印好像开不了机。请注意,do_DataAbort(unsigned long addr,--)的第一个参数,就是r0
r0: mrc    p15, 0, r0, c6, c0, 0        @ get FAR 【Fault Address Register (FAR).】,存放的是出问题是访问的CPU访问的地址.
如果触发的是空指针crash, 则r0,也就是do_DataAbort的 addr参数为0.可以加个条件去打印。

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;
    /*threre so many callbacks*/
    if(!addr)
        printk(KERN_ALERT "do_DataAbort: NULL index:0x%x\n",fsr_fs(fsr));
    if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
        return;

}
输出:do_DataAbort: NULL index:0x5,这样可知对应的函数为do_translation_fault。
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"           },
}

kernel mode空指针处理函数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);
}

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 we're in an interrupt or have no user
     * context, we must not take the fault..
     */
    if (in_atomic() || !mm){
        printk(KERN_ALERT "do_page_fault:mm_struct_1 0x%x\n", mm);
        goto no_context;
    }

    if (!down_read_trylock(&mm->mmap_sem)) {
        if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc)){
            printk(KERN_ALERT "do_page_fault:mm_struct_2 0x%x\n", mm);
            goto no_context;
        }
        down_read(&mm->mmap_sem);
    }
    else{
        fault = __do_page_fault(mm, addr, fsr, flags, tsk);
        /*
         * If we are in kernel mode at this point, we
         * have no context to handle this fault with.
         */
        if (!user_mode(regs)){
            printk(KERN_ALERT "do_page_fault:mm_struct_3 0x%x\n", mm);
            goto no_context;
        }
    }
no_context:
    printk(KERN_ALERT "__do_kernel_fault: do_page_fault:index 0x%x\n", fsr_fs(fsr));
    __do_kernel_fault(mm, addr, fsr, regs);
    return 0;

}

如果是crash happened in kernel mode 最终会调到__do_kernel_fault。

对echo c > /proc/sysrq-trigger触发的空指针crash而言:所说的空指针虚拟地址为0, 0 < TASK_SIZE, 所以会调用到do_page_fault,找不到对应的VMA等信息,

且发生异常时在内核空间,所以 goto no_context.

最后调到 __do_kernel_fault。

这篇关于kernel crash 发生后的那些事(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1027513

相关文章

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

笔记整理—内核!启动!—kernel部分(2)从汇编阶段到start_kernel

kernel起始与ENTRY(stext),和uboot一样,都是从汇编阶段开始的,因为对于kernel而言,还没进行栈的维护,所以无法使用c语言。_HEAD定义了后面代码属于段名为.head .text的段。         内核起始部分代码被解压代码调用,前面关于uboot的文章中有提到过(eg:zImage)。uboot启动是无条件的,只要代码的位置对,上电就工作,kern

当你输入一个网址后都发生什么

原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/  作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等。 本文将更深入的研究当你输入一个网址的时候,后台到底发生了一件件什么样的事~

欧拉系统 kernel 升级、降级

系统版本  cat  /etc/os-release  NAME="openEuler"VERSION="22.03 (LTS-SP1)"ID="openEuler"VERSION_ID="22.03"PRETTY_NAME="openEuler 22.03 (LTS-SP1)"ANSI_COLOR="0;31" 系统初始 kernel 版本 5.10.0-136.12.0.

[Linux Kernel Block Layer第一篇] block layer架构设计

目录 1. single queue架构 2. multi-queue架构(blk-mq)  3. 问题 随着SSD快速存储设备的发展,内核社区越发发现,存储的性能瓶颈从硬件存储设备转移到了内核block layer,主要因为当时的内核block layer是single hw queue的架构,导致cpu锁竞争问题严重,本文先提纲挈领的介绍内核block layer的架构演进,然

Kernel 中MakeFile 使用if条件编译

有时需要通过if  else来选择编译哪个驱动,单纯的obj-$(CONFIG_)就不是很方便,下面提供两种参考案例: 案例一: 来源:drivers/char/tpm/Makefileifdef CONFIG_ACPItpm-y += tpm_eventlog.o tpm_acpi.oelseifdef CONFIG_TCG_IBMVTPMtpm-y += tpm_eventlog.o

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个 嫌疑犯的一个。以下为4个嫌疑犯的供词。

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个 嫌疑犯的一个。以下为4个嫌疑犯的供词。 A说:不是我。 B说:是C。 C说:是D。 D说:C在胡说 已知3个人说了真话,1个人说的是假话。 现在请根据这些信息,写一个程序来确定到底谁是凶手。  static void Main()         {             int killer = 0;             fo

笔记整理—内核!启动!—kernel部分(1)驱动与内核的关系

首先,恭喜完成了uboot部分的内容整理,其次补充一点,uboot第一部分和第二部分的工作不是一定的,在不同的版本中,可能这个初始化早一点,那个的又放在了第二部分,版本不同,造成的工作顺序不同,但终归是要完成基本内容初始化并传参给kernel的。         那么至于驱动与内核的关系,用一张图来说明最适合不过:         驱动位于OS层的中下层与硬件相接。驱动是内

一个瑞典游戏工作室决定离开索尼,之前和之后都发生了什么?

我们在前两篇中探究了国家政策、硬件基础与黑客文化如何让瑞典成为了游戏热土,而它充满地域特色的开发者社区与教育体系的构建,又是如何聚拢了游戏人才,让体系持续生长扩张。 除了大学、科技园和开发者之家外,我们此行从斯德哥尔摩到舍夫德到马尔默,还采访了三家知名工作室的创始人。它们一家产出如今罕见的双人合作游戏,还有一位特立独行的作者型开发者屡屡占据头条;一家贡献了现象级网红作品,当前在朝“正经向”大

关于Qt在子线程中使用通讯时发生无法接收数据的情况

在多线程应用中,串口通讯或TCP通讯的场景常常涉及到持续的读写操作,如果子线程处理不当,可能会导致信号阻塞问题。本文将通过串口通讯或TCP通讯为例,详细解释如何在多线程环境中避免信号阻塞,并提供代码示例。 1. 问题背景 假设我们在一个应用程序中使用多线程处理串口或TCP通讯,通常会在子线程中实现持续的数据读取。为了确保实时处理数据,常见的做法是在子线程的 run() 方法中使用 while