Linux内核panic简析

news2024/11/18 12:24:26

源码基于: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 ,会 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

也就是在Linux 5.4版本 BUG() 和 BUG_ON() 都是执行的 panic()。

而若没有定义 HAVE_ARCH_BUG 这个宏,BUG() 会向CPU 下发一条未定义指令而触发ARM 发起未定义指令异常,随后进入 kernel 异常处理流程,通过调用die() 经历Oops 和 panic,下面会单独分析 die() 函数,详细看第 2 节。

2. die()

arch/arm64/kernel/traps.c

static 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 等寄存器信息,打印调用栈信息,等等。

 

2.1 __die()

arch/arm64/kernel/traps.c

static 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.c

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

2.2 oops同时有可能panic

从上面 die() 函数最后看到,oops_exit() 之后也有可能进入panic():

arch/arm64/kernel/traps.c

void 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 进行动态修改。

3. 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锁后运行)再次被调用panic
	local_irq_disable();
    //禁止任务抢占
	preempt_disable_notrace();

	//只允许一个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 syncing
	pr_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 we have crashed and we have a crash kernel loaded let it handle
	 * everything else.
	 * If we want to run this after calling panic_notifiers, pass
	 * the "crash_kexec_post_notifiers" option to the kernel.
	 *
	 * Bypass the panic_cpu check and call __crash_kexec directly.
	 */
	if (!_crash_kexec_post_notifiers) {
		printk_safe_flush_on_panic();
		__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();

    //输出panic的kernel 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_VT
	unblank_screen();
#endif
	console_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();
#endif
	pr_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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/541875.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux——线程3|线程互斥和同步

加锁保护 我们上一篇提到过,多个线程执行下面代码可能会出错,具体原因可查看上一篇Linux博客。

Vue3相关知识点笔记(持续更新中。。。。)

目录标题 1、Vue3中的组合式Api有哪些? 和Vue2的Options Api有什么不同?Vue3中的组合式API主要包括以下几个&#xff1a;与Vue2的option Api有什么不同 2、Vue3.0的设计目标是什么&#xff1f;做了哪些优化&#xff1f;设计目标是什么&#xff1f;哪些优化&#xff1f; 3、Vu…

python:随机森林分类器的性能评估(决策树数量的影响)

作者:CSDN @ _养乐多_ 随机森林(Random Forest)是一种强大的机器学习算法,常用于分类和回归任务。它由多个决策树构成,通过集成学习的方式进行预测。在本篇博客中,我们将探讨随机森林分类器在不同决策树数量下的性能,并绘制相应的图表进行可视化分析。OOB误差,0被误判为…

C++ 实现堆排序

时空复杂度 时间复杂度 排序复杂度 O ( n l o g n ) O(nlogn) O(nlogn) 建堆复杂度 O ( n ) O(n) O(n) 空间复杂度 由于堆排序是一种就地设计的排序算法&#xff0c;空间需求是恒定的&#xff0c;所以是O(1) 稳定性 不稳定。 C代码&#xff08;大根堆&#xff09; cla…

Jenkins持续集成之修改jenkins工作目录

修改jenkins工作目录 一般不建议把工作目录放到默认的C盘&#xff0c;故可以更改到其他盘中 前置条件&#xff1a;先在其他盘中新建工作目录的文件&#xff1b;如下图 1、首先打开任务管理器&#xff0c;找到服务中的Jenkins进程 2、右击点击转到详细信息&#xff1b; 3、再右…

洛谷P1706全排列问题题解(两种方法+详解)

问题引出 全排列问题 题目描述 按照字典序输出自然数 1 1 1 到 n n n 所有不重复的排列&#xff0c;即 n n n 的全排列&#xff0c;要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 n n n。 输出格式 由 1 ∼ n 1 \sim n 1∼n 组成的所有不重…

成为Jmeter大师:从入门到精通的环境搭建教程

一、JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序&#xff0c;被设计为用于测试客户端/服务端结构的软件(例如web应用程序)。它可以用来测试静态和动态资源的性能&#xff0c;例如&#xff1a;静态文件&#xff0c;Java Servlet,CGI Scripts,Java Object,数据库和FTP服…

什么是 http 代理,为什么需要 http 代理?

前言&#xff1a; 在我们进行软件测试工作的时候&#xff0c;会有很多地方需要去设置代理的。 比如&#xff1a;fiddler抓包&#xff0c;jmeter录制脚本等等。 甚至于&#xff0c;在某些公司&#xff0c;去访问某些内部网址的时候&#xff0c;都需要通过连接vpn才能成功访问。…

Meaning of life :Do not wait too long and miss the opportunity

生命的意义在于机会在眼前&#xff0c;我们就要去抓住他&#xff0c;错过了可能会遗憾终身&#xff0c;不要去等待机会的到来&#xff0c;很多时候真是应一句老话, "机不可失时不再来!" 。 Dont wait too long and miss the opportunity Each spring brings a blos…

瑞吉外卖 - 修改菜品功能(18)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

Flink第四章:水位线和窗口

系列文章目录 Flink第一章:环境搭建 Flink第二章:基本操作. Flink第三章:基本操作(二) Flink第四章:水位线和窗口 文章目录 系列文章目录前言一、水位线二、窗口二、实际案例1.自定义聚合函数2.全窗口函数3.水位线窗口4.统计用户点击数据5.处理迟到数据 总结 前言 这次博客记…

ChatGPT、GPT4、AutoGPT 和 MemoryGPT:初学者指南

人工智能 (AI) 不仅在改变行业&#xff0c;也在改变我们的日常生活。借助人工智能&#xff0c;我们可以改善我们的组织和生产力&#xff0c;让我们能够专注于真正重要的事情。在本文中&#xff0c;我们将探讨一些适用于日常生活的 AI 工具&#xff0c;以及它们如何帮助您保持井…

【Spring框架】--02.容器IoC、原理(手写IoC)

文章目录 3.容器&#xff1a;IoC3.1 IoC容器3.1.1 控制反转&#xff08;IoC&#xff09;3.1.2 依赖注入3.1.3 IoC容器在Spring的实现 3.2 基于XML管理Bean3.2.1搭建子模块spring6-ioc-xml3.2.2 获取bean①方式一&#xff1a;根据id获取②方式二&#xff1a;根据类型获取③方式三…

【Jmeter第三章】Jmeter给请求添加请求头

给请求加上请求头最常见的场景就是在请求头上添加token了&#xff0c;这里也拿添加token来举例 1、添加某个请求的请求头 1、选中HTTP请求&#xff0c;右键添加 2、添加请求头 2、添加公共的请求头信息 其实步骤和上面是一样的&#xff0c;只不过是选择&#xff1a;线程组…

极客的git常用命令手册

极客的git常用命令手册 1.1 权限配置篇1.1.1 创建ssh key1.1.2 本地存在多个密钥时&#xff0c;如何根据目标平台自动选择用于认证的密钥&#xff1f; 1.2 基础信息配置篇1.2.1 配置用户名1.2.2 配置用户邮箱1.2.3 设置文件名大小写区分1.2.4 设置命令行显示颜色1.2.5 检查git全…

MySQL高级_第11章_数据库的设计规范

MySQL高级_第11章_数据库的设计规范 1. 为什么需要数据库设计 2. 范 式 2.1 范式简介 在关系型数据库中&#xff0c;关于数据表设计的基本原则、规则就称为范式。 可以理解为&#xff0c;一张数据表的设计结构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关…

如何用postman进行http接口测试?好好看好好学

目录 优点&#xff1a; 1、什么是POSTMAN 2、新建一个项目 2、新增一个用例 3、添加请求信息 4、post请求参数 5、添加头信息 6、预处理和结果检查 7、全局变量与环境变量 8、导出用例为代码 9、批量执行用例 HTTP的接口测试工具有很多&#xff0c;可以进行http请求…

打死都千万不要进外包...

我18年毕业于一个普通二本学校&#xff0c;是一个很不出名的小本科。大学期间专业知识也没有去认真的学习&#xff0c;所以毕业的时候就随便找了一份工作&#xff0c;在一个小公司做功能测试。 记得那时候薪资大概是6k左右&#xff0c;因为是在工厂&#xff0c;工作环境很差&a…

终极猜想 |欧科云链研究院揭秘货币未来形态

前言 5月18日&#xff0c;澎湃科技联合欧科云链研究院重磅发布的《从Web3“去美元化”看货币未来形态的终极猜想》文章&#xff0c;通过分析Web3.0“去美元化”的两大路径&#xff0c;对货币未来形态进行了前沿性的猜想。其中&#xff0c;Web3行业盛会Consensus2023&#xff0…

补充点【机器学习部分】

0518机器学习 身高和体重为特征&#xff0c;和标签训练阔以得到一个式子。 1.有监督学习&#xff1a; 2.训练集和测试集 3.模型学习 4.模型评估 5.基本术语 数据集&#xff1a;记录数据的集合 样本&#xff1a;每条记录关于一个事件或者对象的描述 特征&#xff1a;反映对…