Linux内核oops panic简析
源码基于:Linux 5.4
0. 前言
内核异常的级别大致分为三个:BUG、oops、panic。
BUG 是指那些不符合内核的正常设计,但内核能够检测出来并且对系统运行不会产生影响的问题,比如在原子上下文中休眠,在内核中用 BUG 标识。
Oops 就意外着内核出了异常,此时会将产生异常时出错原因,CPU的状态,出错的指令地址、数据地址及其他寄存器,函数调用的顺序甚至是栈里面的内容都打印出来,然后根据异常的严重程度来决定下一步的操作:杀死导致异常的进程或者挂起系统。
panic 本意是“恐慌”的意思,这里意旨 kernel 发生了致命错误导致无法继续运行下去的情况。根据实际情况 Oops最终也可能会导致panic 的发生。
本文将简单分析下这三种异常的流程。
1. BUG()
有过驱动调试经验的人肯定都知道这个东西,这里的 BUG 跟我们一般认为的 “软件缺陷” 可不是一回事,这里说的 BUG() 其实是linux kernel中用于拦截内核程序超出预期的行为,属于软件主动汇报异常的一种机制。这里有个疑问,就是什么时候会用到呢?一般来说有两种用到的情况:
- 一是软件开发过程中,若发现代码逻辑出现致命 fault 后就可以调用BUG()让kernel死掉(类似于assert),这样方便于定位问题,从而修正代码执行逻辑;
- 另外一种情况就是,由于某种特殊原因(通常是为了debug而需抓ramdump),我们需要系统进入kernel panic的情况下使用;
对于 arm64 来说 BUG() 定义如下:
arch/arm64/include/asm/bug.h#ifndef _ARCH_ARM64_ASM_BUG_H
#define _ARCH_ARM64_ASM_BUG_H#include <linux/stringify.h>#include <asm/asm-bug.h>#define __BUG_FLAGS(flags) \asm volatile (__stringify(ASM_BUG_FLAGS(flags)));#define BUG() do { \__BUG_FLAGS(0); \unreachable(); \
} while (0)#define __WARN_FLAGS(flags) __BUG_FLAGS(BUGFLAG_WARNING|(flags))#define HAVE_ARCH_BUG#include <asm-generic/bug.h>#endif /* ! _ARCH_ARM64_ASM_BUG_H */
注意最后的 define HAVE_ARCH_BUG ,对于arm64 架构来说,会通过 include asm-generict/bug.h 对 BUG() 进行重定义。
include/asm-generic/bug.h#ifndef HAVE_ARCH_BUG
#define BUG() do { \printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \barrier_before_unreachable(); \panic("BUG!"); \
} while (0)
#endif#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
#endif
也就是在 arm64 架构中 BUG() 和 BUG_ON() 都是执行的 panic()。
而对于 arm 32位架构来说,BUG() 会向CPU 下发一条未定义指令而触发ARM 发起未定义指令异常,随后进入 kernel 异常处理流程,通过调用die() 经历Oops 和 panic,下面会单独分析 die() 函数,详细看第 3 节。
2. oops
oops 意外着内核出了异常,此时会将产生异常时出错原因,CPU的状态,出错的指令地址、数据地址及其他寄存器,函数调用的顺序甚至是栈里面的内容都打印出来,然后根据异常的严重程度来决定下一步的操作:杀死导致异常的进程或者挂起系统。
例如,在编写驱动或内核模块时,常常会显示或隐式地对指针进行非法取值或使用不正确的指针,导致内核发生一个 oops 错误。当处理器在内核空间中访问一个分发的指针时,因为虚拟地址到物理地址的映射关系还没有建立,会触发一个缺页中断,在缺页中断中该地址是非法的,内核无法正确地为该地址建立映射关系,所以内核触发一个oops 错误。代码如下:
arch/arm64/mm/fault.cstatic void die_kernel_fault(const char *msg, unsigned long addr,unsigned int esr, struct pt_regs *regs)
{bust_spinlocks(1);pr_alert("Unable to handle kernel %s at virtual address %016lx\n", msg,addr);mem_abort_decode(esr);show_pte(addr);die("Oops", regs, esr);bust_spinlocks(0);do_exit(SIGKILL);
}
通过 die() 会进行oops 异常处理,详细的 die() 函数流程看第 3 节。
当出现 oops,并且如果有源码,可以通过 arm 的 arch64-linux-gnu-objdump 工具看到出错的函数的汇编情况,也可以通过 GDB 工具分析。如果出错的地方为内核函数,可以使用 vmlinux 文件。
如果没有源码,对于没有编译符号表的二进制文件,可以使用:
arch64-linux-gnu-objdump -d oops.ko
命令来转储 oops.ko 文件
内核也提供了一个非常好用的脚本,可以快速定位问题,该脚本位于 Linux 源码目录下的 scripts/decodecode 中,会把出错的 oops 日志信息转换成直观有用的汇编代码,并且告知具体出错的汇编语句,这对于分析没有源码的 oops 错误非常有用。
3. die()
arch/arm64/kernel/traps.cstatic DEFINE_RAW_SPINLOCK(die_lock);/** This function is protected against re-entrancy.*/
void die(const char *str, struct pt_regs *regs, int err)
{int ret;unsigned long flags;raw_spin_lock_irqsave(&die_lock, flags);oops_enter();console_verbose();bust_spinlocks(1);ret = __die(str, err, regs);if (regs && kexec_should_crash(current))crash_kexec(regs);bust_spinlocks(0);add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);oops_exit();if (in_interrupt())panic("Fatal exception in interrupt");if (panic_on_oops)panic("Fatal exception");raw_spin_unlock_irqrestore(&die_lock, flags);if (ret != NOTIFY_STOP)do_exit(SIGSEGV);
}
oops_enter() ---> oops_exit() 为Oops 的处理流程,获取console 的log 级别,并通过 __die() 通过对Oops 感兴趣的模块进行callback,打印模块状态不为 MODULE_STATE_UNFORMED 的模块信息,打印PC、LR、SP、x0 等寄存器信息,打印调用栈信息,等等。
3.1 __die()
arch/arm64/kernel/traps.cstatic int __die(const char *str, int err, struct pt_regs *regs)
{static int die_counter;int ret;pr_emerg("Internal error: %s: %x [#%d]" S_PREEMPT S_SMP "\n",str, err, ++die_counter);/* trap and error numbers are mostly meaningless on ARM */ret = notify_die(DIE_OOPS, str, regs, err, 0, SIGSEGV);if (ret == NOTIFY_STOP)return ret;print_modules();show_regs(regs);dump_kernel_instr(KERN_EMERG, regs);return ret;
}
- 打印 EMERG 的log,Internal error: oops.....;
- notify_die() 会通知所有对 Oops 感兴趣的模块并进行callback;
- print_modules() 打印模块状态不为 MODULE_STATE_UNFORMED 的模块信息;
- show_regs() 打印PC、LR、SP 等寄存器的信息,同时打印调用堆栈信息;
- dump_kernel_instr() 打印 pc指针和前4条指令;
这里不过多的剖析,感兴趣的可以查看下源码。
这里需要注意的是 notify_die() 会通知所有的Oops 感兴趣的模块,模块会通过函数 register_die_notifier() 将callback 注册到全局结构体变量 die_chain 中(多个模块注册进来形成一个链表),然后在通过 notify_die() 函数去解析这个 die_chain,并分别调用callback:
kernel/notifier.cstatic ATOMIC_NOTIFIER_HEAD(die_chain);int notrace notify_die(enum die_val val, const char *str,struct pt_regs *regs, long err, int trap, int sig)
{struct die_args args = {.regs = regs,.str = str,.err = err,.trapnr = trap,.signr = sig,};RCU_LOCKDEP_WARN(!rcu_is_watching(),"notify_die called but RCU thinks we're quiescent");return atomic_notifier_call_chain(&die_chain, val, &args);
}
NOKPROBE_SYMBOL(notify_die);int register_die_notifier(struct notifier_block *nb)
{vmalloc_sync_mappings();return atomic_notifier_chain_register(&die_chain, nb);
}
3.2 oops同时有可能panic
从上面 die() 函数最后看到,oops_exit() 之后也有可能进入panic():
arch/arm64/kernel/traps.cvoid die(const char *str, struct pt_regs *regs, int err)
{...if (in_interrupt())panic("Fatal exception in interrupt");if (panic_on_oops)panic("Fatal exception");...
}
处于中断 或 panic_on_oops 打开时进入 panic。
中断的可能性:
- 硬件 IRQ;
- 软件 IRQ;
- NMI;
panic_on_oops 的值受 CONFIG_PANIC_ON_OOPS_VALUE 影响。当然该值也可以通过节点
/proc/sys/kernel/panic_on_oops 进行动态修改。
4. panic()
panic 本意是“恐慌”的意思,这里意旨kernel发生了致命错误导致无法继续运行下去的情况。
kernel/panic.c/*** panic - halt the system* @fmt: The text string to print** Display a message, then perform cleanups.** This function never returns.*/
void panic(const char *fmt, ...)
{static char buf[1024];va_list args;long i, i_next = 0, len;int state = 0;int old_cpu, this_cpu;bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;//禁止本地中断,避免出现死锁,因为无法防止中断处理程序(在获得panic锁后运行)再次被调用paniclocal_irq_disable();//禁止任务抢占preempt_disable_notrace();//通过this_cpu确认是否调用panic() 的cpu是否为panic_cpu;//即,只允许一个CPU执行该代码,通过 panic_smp_self_stop() 保证当一个CPU执行panic时,//其他CPU处于停止或等待状态;this_cpu = raw_smp_processor_id();old_cpu = atomic_cmpxchg(&panic_cpu, PANIC_CPU_INVALID, this_cpu);if (old_cpu != PANIC_CPU_INVALID && old_cpu != this_cpu)panic_smp_self_stop();//把console的打印级别放开console_verbose();bust_spinlocks(1);va_start(args, fmt);len = vscnprintf(buf, sizeof(buf), fmt, args);va_end(args);if (len && buf[len - 1] == '\n')buf[len - 1] = '\0';//解析panic所携带的message,前缀为Kernel panic - not syncingpr_emerg("Kernel panic - not syncing: %s\n", buf);
#ifdef CONFIG_DEBUG_BUGVERBOSE/** Avoid nested stack-dumping if a panic occurs during oops processing*/if (!test_taint(TAINT_DIE) && oops_in_progress <= 1)dump_stack();
#endif//如果kgdb使能,即CONFIG_KGDB为y,在停掉所有其他CPU之前,跳转kgdb断点运行kgdb_panic(buf);if (!_crash_kexec_post_notifiers) {printk_safe_flush_on_panic();//会根据当前是否设置了转储内核(使能CONFIG_KEXEC_CORE)确定是否实际执行转储操作;//如果执行转储则会通过 kexec 将系统切换到新的kdump 内核,并且不会再返回;//如果不执行转储,则继续后面流程;__crash_kexec(NULL);//停掉其他CPU,只留下当前CPU干活smp_send_stop();} else {/** If we want to do crash dump after notifier calls and* kmsg_dump, we will need architecture dependent extra* works in addition to stopping other CPUs.*/crash_smp_send_stop();}//通知所有对panic感兴趣的模块进行回调,添加一些kmsg信息到输出atomic_notifier_call_chain(&panic_notifier_list, 0, buf);/* Call flush even twice. It tries harder with a single online CPU */printk_safe_flush_on_panic();//dump 内核log buffer中的log信息kmsg_dump(KMSG_DUMP_PANIC);/** If you doubt kdump always works fine in any situation,* "crash_kexec_post_notifiers" offers you a chance to run* panic_notifiers and dumping kmsg before kdump.* Note: since some panic_notifiers can make crashed kernel* more unstable, it can increase risks of the kdump failure too.** Bypass the panic_cpu check and call __crash_kexec directly.*/if (_crash_kexec_post_notifiers)__crash_kexec(NULL);#ifdef CONFIG_VTunblank_screen();
#endifconsole_unblank();/** We may have ended up stopping the CPU holding the lock (in* smp_send_stop()) while still having some valuable data in the console* buffer. Try to acquire the lock then release it regardless of the* result. The release will also print the buffers out. Locks debug* should be disabled to avoid reporting bad unlock balance when* panic() is not being callled from OOPS.*/debug_locks_off();console_flush_on_panic(CONSOLE_FLUSH_PENDING);panic_print_sys_info();if (!panic_blink)panic_blink = no_blink;//如果sysctl配置了panic_timeout > 0则在panic_timeout后重启系统if (panic_timeout > 0) {/** Delay timeout seconds before rebooting the machine.* We can't use the "normal" timers since we just panicked.*/pr_emerg("Rebooting in %d seconds..\n", panic_timeout);for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {touch_nmi_watchdog();if (i >= i_next) {i += panic_blink(state ^= 1);i_next = i + 3600 / PANIC_BLINK_SPD;}mdelay(PANIC_TIMER_STEP);}}if (panic_timeout != 0) {/** This will not be a clean reboot, with everything* shutting down. But if there is a chance of* rebooting the system it will be rebooted.*/if (panic_reboot_mode != REBOOT_UNDEFINED)reboot_mode = panic_reboot_mode;emergency_restart();}
#ifdef __sparc__{extern int stop_a_enabled;/* Make sure the user can actually press Stop-A (L1-A) */stop_a_enabled = 1;pr_emerg("Press Stop-A (L1-A) from sun keyboard or send break\n""twice on console to return to the boot prom\n");}
#endif
#if defined(CONFIG_S390)disabled_wait();
#endifpr_emerg("---[ end Kernel panic - not syncing: %s ]---\n", buf);/* Do not scroll important messages printed above */suppress_printk = 1;local_irq_enable();for (i = 0; ; i += PANIC_TIMER_STEP) {touch_softlockup_watchdog();if (i >= i_next) {i += panic_blink(state ^= 1);i_next = i + 3600 / PANIC_BLINK_SPD;}mdelay(PANIC_TIMER_STEP);}
}EXPORT_SYMBOL(panic);
详细信息见代码注释。
相关文章:

Linux内核oops panic简析
源码基于:Linux 5.4 0. 前言 内核异常的级别大致分为三个:BUG、oops、panic。 BUG 是指那些不符合内核的正常设计,但内核能够检测出来并且对系统运行不会产生影响的问题,比如在原子上下文中休眠,在内核中用 BUG 标识。…...

Spark大数据处理讲课笔记4.8 Spark SQL典型案例
文章目录 零、本讲学习目标一、使用Spark SQL实现词频统计(一)数据源 - words.txt(二)创建Maven项目(三)添加依赖和构建插件(四)修改源目录名称(五)创建日志属…...

WhatsApp Business 多人使用终极指南
今时今日,几乎每个人的手机上都安装了 WhatsApp,不少电商更会依赖 WhatsApp作为和客户沟通的主要渠道。但对有一定规模的店铺来说,WhatsApp绑定一个号码和设备的设定实在很不方便。如何才能用WhatsApp Business批量处理客户查询呢?…...
布局和视图的常用属性
0、用户界面由布局和GUI组件组成 之前的项目中都是使用线性布局LinearLayout,LinearLayout将视图显示在一行或一列中。其使用方法如下所示: <LinearLayoutxmlns:android"http://schemas.android.com/apk/res/android"android:layout_width…...

解说天下之操作系统
解说天下之操作系统 本文由桌案drawon (https://www.drawon.cn),云晶(https://www.yunjingxz.com)创始人根据多年从业经验, 从操作系统的起源,应用分类, 设计分类,以及资源使用角度对操作系统进…...
Pruning 系列 (八)layer常用简枝(torch)方法
环境 python 3.9numpy 1.24.1pytorch 2.0.0+cu117一、prune.random_unstructured pytorch:文档地址 用法: torch.nn.utils.prune.random_unstructured(module, name, amount) 参数: module(torch.nn.Module) -包含要修剪的张量的模块 name(str) -module 中的参数名称,将…...

Gigabyte Z490 Vision D i9-10900k电脑 Hackintosh 黑苹果efi引导文件
原文来源于黑果魏叔官网,转载需注明出处。(下载请直接百度黑果魏叔) 硬件型号驱动情况 主板Gigabyte Z490 Vision D 处理器Intel i9-10900k已驱动 内存64GB G.Skill Trident Z 3600Mhz CL18已驱动 硬盘西数 WDS250G3X0C-00SJG0 ( SN750) …...

UWB智慧工厂人员定位系统源码,人员在岗监控、车辆实时轨迹监控源码
近年来人员定位系统在工业领域的发展势头迅猛,工业识别与定位成为促进制造业数字化的关键技术。通过实时定位可以判断所有的人、物、车的位置。实时定位系统要适用于复杂工业环境,单一技术是很难实现的,需要融合多种不同的定位技术࿰…...
从认识元注解到使用元注解
前言:注解是Java语言中的一种特殊语法,它可以为代码提供更加灵活的元数据信息,方便代码的处理和使用。而元注解则是用于定义注解的注解,它可以为注解提供更多的元数据信息和特性。本文将介绍如何自定义元注解,包括认识…...

【C++从0到王者】第六站:类和对象(下)
文章目录 一、再谈构造函数1.构造函数体赋值2.初始化列表1>初始化列表的使用2>初始化列表的注意事项 3.explicit关键词 二、static成员1.如何统计当前程序中变量的个数2.static的特性3.从1加到n4.设计一个类,只能在栈或者堆上开辟空间 三、友元1.友元函数2.友…...

AJax和Axios的讲解
目录 Ajax Ajax基本介绍 同步异步 原生Ajax 原生的Ajax使用方式 Axios 基本介绍 Axios的基本使用 发送 get 请求 发送 post 请求 Axios快速入门 请求方法的别名 练习 Ajax Ajax基本介绍 Ajax: 全称Asynchronous JavaScript And XML,异步的JavaScript和XML…...

企业落地数字化转型,如何部署战略规划
当前环境下,各领域企业通过数字化相关的一切技术,以数据为基础、以用户为核心,创建一种新的,或对现有商业模式进行重塑就是数字化转型。这种数字化转型给企业带来的效果就像是一次重构,会对企业的业务流程、思维文化、…...

新的网络钓鱼即服务平台让网络犯罪分子生成令人信服的网络钓鱼页面
至少从2022年中期开始,网络犯罪分子就利用一个名为“伟大”的新型网络钓鱼即服务(PhaaS或PaaS)平台来攻击微软365云服务的企业用户,有效地降低了网络钓鱼攻击的门槛。 思科Talos研究员蒂亚戈佩雷拉表示:“目前,Greatness只专注于微软365钓鱼…...

MySQL的隐式转换
隐式转换 若字符串是以数字开头,并且全部都是数字,则转换的数字结果是整个字符串;部分是数字,则转换的数字结果是截止到第一个不是数字的字符为止 若字符串不是以数字开头,则转换的数字结果是 0 varchar str "…...

LeetCode:23. 合并 K 个升序链表
23. 合并 K 个升序链表 1)题目2)过程3)代码1. 最开始2.初步优化 4)结果1. 最开始2. 初步优化 1)题目 给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合…...

js:正则表达式常用方法总结test、exec、match、matchAll、replace、replaceAll、search
文章目录 正则使用testmatch/matchAll不加g加ggroup 的使用 matchAll不加g加g exec不加g加g searchreplace 正则使用 常用的几种方法有:test、exec、match、matchAll、replace、replaceAll、search test // 匹配返回true,不匹配false /e/.test("…...

分析车载蓝牙通话只有前喇叭声音,后面喇叭无声背后原因
车载蓝牙通话只有前喇叭声音,后面喇叭无声背后原因 大家有没有注意到车载蓝牙连接后通话的时候只有前喇叭的有声音,后面喇叭没声音呢?特别是后装的车载多媒体上基本都是这样,细思下为什么这样的原因, 采访后装车载技术…...

高性能ADC/DAC FMC子卡推出-FMC164
FMC164 子卡集成 4 通道 1Gsps 采样率,16 位 高性能ADC采样,板载4 通道1.25Gsps 16 位DA。板载时钟芯片 HMC7044,可以提供 JESD204B所需要的各种时钟。具有同步/触发功能,模拟信号采用 SSMC 射频连接器输入和输出。板载时钟芯片为…...

Agisoft Metashape 红外影像处理
系列文章目录 文章目录 系列文章目录前言一、加载红外影像二、对齐照片三、构建 DEM四、生成 DOM五、温度值可视化前言 Agisoft Metashape 专业版支持处理来自 AscTec(ARA 格式)、WIRIS(TIFF 格式)热成像仪和以 R-JPEG(FLIR 数据)格式保存数据的热成像数据。 在本文中,…...

Mybatis从入门到入土
一、什么是Mybatis 1)MyBatis 是一款优秀的持久层(DAO层)框架 2)MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程 3)MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...
python打卡day49@浙大疏锦行
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...