Linux死机排查方法——内存日志

一般情况下,Linux系统在死机时会产生一些dump信息,例如oops,通过分析oops信息就可以基本定位问题所在,但有些特殊情况下死机时,没有任何的打印的信息。如果直接使用printk等打印排查问题,有可能会因为printk输出缓慢改变了系统运行的时序,导致问题无法复现,而且在中断里使用printk将大大降低系统性能。如果有DS-5等硬件调试工具,那是最好的,如果没有,那么这时候可以借助一种特殊手段来排查问题,也就是内存日志。

本文所描述的内存日志,并不是将内核的printk重定向到内存中,因为printk的打印太多了,如果将日志写入到内存中,那就比正常的串口printk快的多,对系统的影响最小。简而言之,这种方法就是将关键模块的日志保存在内存中,等到下一次启动时,再将这些日志全部dump出来。这里有两个需要注意的地方:

1、为了尽可能地减小日志大小,写入的日志为16进制格式,自己定义好协议即可,最后看日志的时候,再将16进制日志翻译成自己能看懂的格式。

2、这种方法对DDR有一定的要求,要求死机后复位重启(非断电重启)后DDR里的数据能保持。笔者的板子由于有PMIC给SOC供电,DDR是额外供电的,复位时只复位PMIC,因此DDR数据能保持。另外笔者试过,有些平台看门狗复位后DDR的数据能继续保持,有些则不行,可以做个小实验:在kernel中使用devmem命令在高地址中写入一个特殊数据,然后利用看门狗溢出进行复位,复位后在uboot里将这个地址的数据打印出来,看是否一致,如果一致就说明看门狗复位后DDR数据不会丢失,可以使用这种方法。

下面是我实现的一个mem_log模块,可以根据自己的需求适当修改,例如在每条日志里增加系统的jffies等。笔者板子内存为128M,物理地址空间为0x80000000 ~ 0x87FFFFFF,将最高1M地址空间给mem_log使用,但笔者实际只使用了其中的28KB,因为mem_log的核心是记录cpu的遗言,不需要太大的空间,这可以自行调整。下面是mem_record_t的核心成员的定义:

  1. index:日志的序号,每记录一条会自增1,最后排查时就是根据index的连续性找到最后一条日志。
  2. module:用户自定义的模块,例如中断、线程调度、各种外设驱动等。
  3. flag:标志位,可以用来记录函数进入和退出,是在哪个cpu核上运行等。
  4. args:参数,当记录的模块为中断时,args可以保存中断号;同理,当记录的模块为线程时,可以保存切入和切出的线程名;当记录的模块为外设驱动时,可以保存驱动名称。
#include "linux/mem_log.h"
#include <linux/spinlock.h>
#include <linux/kernel.h>
#include <asm/io.h>

#define MEM_LOG_START_ADDR  (0x87F00000)    /* mem_log的起始物理地址 */
#define MEM_LOG_SIZE        (28*1024L)      /* mem_log的大小 */

typedef struct
{
    unsigned int        index;
    unsigned char       module;
    unsigned char       flag;
    unsigned char		args[10];
}mem_record_t;

static volatile unsigned int *log_mem_addr = NULL;
static unsigned int mem_log_index = 0;
static mem_record_t *wrecord = NULL;

#ifdef CONFIG_SMP
static DEFINE_SPINLOCK(mem_log_spinlock);
#endif

void mem_log_init(void)
{
    log_mem_addr = ioremap(MEM_LOG_START_ADDR, MEM_LOG_SIZE);
    if(!log_mem_addr)
    {
        printk(KERN_EMERG"mem_log_init failed.");
        return;
    }
    wrecord = (mem_record_t*)log_mem_addr;
	
}
EXPORT_SYMBOL(mem_log_init);

void mem_log_clear(void)
{
   if(log_mem_addr)
   {
        memset((void*)log_mem_addr , 0xFF, MEM_LOG_SIZE);
   }
}
EXPORT_SYMBOL(mem_log_clear);

void mem_log_record(uint8_t module, uint8_t flag, uint8_t *args, uint8_t args_len)
{
#ifdef CONFIG_SMP
	unsigned long flags;
#endif
	static int print = 0;
	
	if(!wrecord)
	{
		if(!print)
		{
			print = 1;
			printk(KERN_EMERG"please use mem_log_init first.\n");			
		}

		return;
	}
#ifdef CONFIG_SMP
	spin_lock_irqsave(&mem_log_spinlock, flags);
#endif	
    wrecord->index = mem_log_index++;
    wrecord->module = module;
#ifdef CONFIG_SMP
    wrecord->flag = (flag << 4) | smp_processor_id();
#else
    wrecord->flag = (flag << 4);
#endif

	memcpy(wrecord->args, args, args_len);
	
    wrecord = wrecord + 1;
    /* 日志写满后从头覆盖写 */
    if((unsigned int)wrecord >= ((unsigned int)log_mem_addr + MEM_LOG_SIZE))
    {
        wrecord = (mem_record_t*)log_mem_addr;
    }
#ifdef CONFIG_SMP
	spin_unlock_irqrestore(&mem_log_spinlock, flags);
#endif	
}
EXPORT_SYMBOL(mem_log_record);

void mem_log_dump(void)
{
    mem_record_t *record = (mem_record_t*)log_mem_addr;
	uint32_t index_back = record->index;
	uint8_t found = 0;
	
    printk("mem log dump:\n");
	printk("record:%X, end:%X\n", (unsigned int)record, ((unsigned int)log_mem_addr + MEM_LOG_SIZE));
    for(; (unsigned int)record < ((unsigned int)log_mem_addr + MEM_LOG_SIZE) ; )
    {
        printk("%08X %02X %02X %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n", 
            record->index, record->module, record->flag,
            record->args[0],record->args[1],record->args[2],record->args[3],record->args[4],
            record->args[5],record->args[6],record->args[7],record->args[8],record->args[9]);

		record++;
		
		if(!found)
		{
			if((index_back+1) != record->index)
			{
				found = 1;
				continue;
			}
			index_back = record->index;				
		}
    }
    /* 找到最后一条index不连续的日志, 即死机前的最后一条日志 */
	printk("find last log index:%08X!\n", index_back);
	
}
EXPORT_SYMBOL(mem_log_dump);

以下是对应的头文件,我定义了三个模块:中断、线程退出、线程切入,当然还可以定义其他一些模块,例如我怀疑SD驱动有问题,可以定义SD模块。Flag只定义了函数进入和函数退出,如果最后的日志只有MEM_LOG_FLAG_FUNC_IN而没有MEM_LOG_FLAG_FUNC_OUT,那么恭喜,就是卡死在这个函数里了。

#ifndef _LINUX_MEM_LOG_H
#define _LINUX_MEM_LOG_H

#include "linux/string.h"

#define MEM_LOG_MODULE_IRQ			(0x11)
#define MEM_LOG_MODULE_THREAD_PRE	(0x22)
#define MEM_LOG_MODULE_THREAD_NEXT	(0x33)

#define MEM_LOG_FLAG_FUNC_IN		(0x01)
#define MEM_LOG_FLAG_FUNC_OUT		(0x02)

void mem_log_init(void);
void mem_log_clear(void);
void mem_log_record(uint8_t module, uint8_t flag, uint8_t *args, uint8_t args_len);
void mem_log_dump(void);

#endif

由于我的板子是直接从TF卡引导kernel启动,没有uboot阶段,因此重启的日志直接从kernel里打印,我将打印加在了内核启动时的start_kernel函数里:

asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
{
	char *command_line;
	char *after_dashes;

	set_task_stack_end_magic(&init_task);
	smp_setup_processor_id();
	debug_objects_early_init();
    
    ……

    console_init();
	if (panic_later)
		panic("Too many boot %s vars at `%s'", panic_later,
		      panic_param);

	lockdep_init();

    mem_log_init();    
    mem_log_dump();    
    mem_log_clear();

	/*
	 * Need to run this when irqs are enabled, because it wants
	 * to self-test [hard/soft]-irqs on/off lock inversion bugs
	 * too:
	 */
	locking_selftest();
    
    ……
}

首次上电时,由于先前没有记录任何日志,所以mem_log_dump会打印一堆脏数据,无需关心。此时mem_log已经初始化完成,在DDR高地址区域开辟了一块空间专门给mem_log使用,需要注意内核不能再使用这段内存,因此需要修改bootargs中的mem参数。此时已经可以在可疑的地方进行打桩,我们知道,程序的执行无外乎两个地方:线程和中断,因此我在这两个地方用mem_log_record函数进行打桩,下面是伪代码示意。

/* 在线程调度的地方打桩 */
static void __sched notrace __schedule(bool preempt)
{
    ……
 	mem_log_record(MEM_LOG_MODULE_THREAD_PRE, MEM_LOG_FLAG_FUNC_IN, (uint8_t*)prev->comm, 10);
	mem_log_record(MEM_LOG_MODULE_THREAD_NEXT, MEM_LOG_FLAG_FUNC_IN, (uint8_t*)next->comm, 10);
	rq = context_switch(rq, prev, next, &rf);
	mem_log_record(MEM_LOG_MODULE_THREAD_PRE, MEM_LOG_FLAG_FUNC_OUT, (uint8_t*)prev->comm, 10);
	mem_log_record(MEM_LOG_MODULE_THREAD_NEXT, MEM_LOG_FLAG_FUNC_OUT,(uint8_t*)next->comm, 10);   
    ……
}

/* 在中断入口打桩 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
	……
	irq_enter();
	mem_log_record(MEM_LOG_MODULE_IRQ, MEM_LOG_FLAG_FUNC_IN, (uint8_t*)&hwirq, 4);
    ……
	generic_handle_irq(irq);
    ……
	mem_log_record(MEM_LOG_MODULE_IRQ, MEM_LOG_FLAG_FUNC_OUT, (uint8_t*)&hwirq, 4);
    irq_exit();
	……
}

下面是我的板子死机的实际样例,下面是死机复位后dump的日志,mem_log会找到最后一个不连续的index日志:

可以看到最后截断的日志序号是6F7278BD,将上述日志翻译一下,如下:

 可以看到,最后是切换到arecord进程后卡死了,但具体是里面操作哪个模块卡死的,还需要进一步打桩进行定位。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

生成式人工智能攻击的一年:2024

趋势科技最近公布了其关于预期最危险威胁的年度研究数据。生成人工智能的广泛可用性和质量将是网络钓鱼攻击和策略发生巨大变化的主要原因。 趋势科技宣布推出“关键可扩展性”&#xff0c;这是著名年度研究的新版本&#xff0c;该研究分析了安全形势并提出了全年将肆虐的网络…

以管理员权限删除某文件夹

到开始菜单中找到—命令提示符—右击以管理员运行 使用&#xff1a;del /f /s /q “文件夹位置” 例&#xff1a;del /f /s /q "C:\Program Files (x86)\my_code\.git"

动态SQl简单创建

创建pojo实体类&#xff0c;使用lombok注解 package com.example.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.time.LocalDate; import java.time.LocalDateTime;Data NoArgsConstructor AllArgsConstructor pu…

记:STM32F4参考手册-存储器和总线架构

STM32F4参考手册-存储器和总线架构 系统架构 主系统由32位多层AHB总线矩阵构成&#xff0c;可实现以下部分部分的互连&#xff1a; 八条主控总线&#xff1a; Cortex-M4F内核I总线、D总线和S总线 DMA1存储器总线 DMA2存储器总线 DMA2外设总线 以太网DMA总线 USB OTG HS DMA总线…

秒杀相关问题解决

秒杀 超卖问题 如下,我们先来复现问题,抢购秒杀券的代码逻辑也是很简单, 先判断优惠券是否开始了,是的化,判断库存是否充足,如果是的化,扣减库存,最后创建订单 如下是代码 Override Transactional public Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVo…

力扣刷题之旅:进阶篇(六)—— 图论与最短路径问题

力扣&#xff08;LeetCode&#xff09;是一个在线编程平台&#xff0c;主要用于帮助程序员提升算法和数据结构方面的能力。以下是一些力扣上的入门题目&#xff0c;以及它们的解题代码。 --点击进入刷题地址 引言 在算法的广阔天地中&#xff0c;图论是一个非常重要的领域。…

linux 07 存储管理

02. ext4是一种索引文件系统 上面是索引节点inode&#xff0c;存放数据的元数据 下面是存储块block&#xff0c;主要存放有关的信息 03.linux上的inode 查看文件中的inode ll -i 文件名 磁盘中的inode与文件数量 向sdb2中写文件&#xff1a; 结果&#xff1a; df -i 磁…

blender几何节点中样条线参数中的系数(factor)是个什么概念?

一根样条线&#xff0c;通常由两个及以上的控制点构成。 每个控制点的系数&#xff0c;其实相当于该点处位于整个样条线的比值。 如图&#xff0c;一根样条线有十一个控制点。相当于把它分成了十段&#xff0c;那每一段可以看到x、y都是0&#xff0c;唯独z每次增加0.1&#xff…

JVM-双亲委派机制

双亲委派机制定义 双亲委派机制指的是&#xff1a;当一个类加载器接收到加载类的任务时&#xff0c;会自底向上查找是否加载过&#xff0c; 再由顶向下进行加载。 详细流程 每个类加载器都有一个父类加载器。父类加载器的关系如下&#xff0c;启动类加载器没有父类加载器&am…

NIS服务器搭建(管理账户密码验证)

理解&#xff1a;新进100台服务器&#xff0c;通过nis服务器设置各个服务器的用户和密码&#xff0c;而不是分别到100台机器前设置用户名密码&#xff0c;服务器可以统一管理用户名密码&#xff0c;更新等操作 第一&#xff1a;服务器端设置 1.域名设置&#xff1a;dongfang …

MyBatis 实现动态 SQL

MyBatis 中的动态 SQL 就是SQL语句可以根据不同的情况情况来拼接不同的sql。 本文会介绍 xml 和 注解 两种方式的动态SQL实现方式。 XML的实现方式 先创建一个数据表&#xff0c;SQL代码如下&#xff1a; DROP TABLE IF EXISTS userinfo; CREATE TABLE userinfo (id int(1…

二维差分---三维差分算法笔记

文章目录 一.二维差分构造差分二维数组二维差分算法状态dp求b[i][j]数组的二维前缀和图解 二.三维前缀和与差分三维前缀和图解:三维差分核心公式图解:模板题 一.二维差分 给定一个原二维数组a[i][j],若要给a[i][j]中以(x1,y1)和(x2,y2)为对角线的子矩阵中每个数都加上一个常数…

代码随想录|Day 14

Day 14 新年将至 一、理论学习 BFS 的使用场景总结&#xff1a;层序遍历、最短路径问题(https://leetcode.cn/problems/binary-tree-level-order-traversal/solutions/244853/bfs-de-shi-yong-chang-jing-zong-jie-ceng-xu-bian-l/) BFS 的应用一&#xff1a;层序遍历 BFS …

开发JSP应用程序

开发JSP应用程序 问题陈述 TecknoSoft Pvt Ltd.公司的首席技术官(CTO)John Barrett将创建一个应用程序的任务委托给了开发团队,该应用程序应在客户访问其账户详细信息前验证其客户ID和密码。客户ID应是数字形式。John希望如果所输入的客户ID或密码不正确,应向客户显示错误…

面试经典150题 -- 栈(总结)

总的链接 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 关于栈 -- stack 的学习链接 c的STL中的栈 -- stack-CSDN博客 20 . 有效的括号 这题直接用栈模拟就好了; 这里用一种取巧的方法 , 当遇见左括号&#xff0c;加入右…

MATLAB环境下基于同态滤波方法的医学图像增强

目前图像增强技术主要分为基于空间域和基于频率域两大方面&#xff0c;基于空间域图像增强的方法包括了直方图均衡化方法和 Retinex 方法等&#xff0c;基于频率域的方法包括同态滤波方法。其中直方图均衡化方法只是根据图像的灰度概率分布函数进行简单的全局拉伸&#xff0c;没…

containerd中文翻译系列(十九)cri插件

cri插件包含的内容比较多&#xff0c;阅读之前请深呼吸三次、三次、三次。 CRI 插件的架构 本小节介绍了 containerd 的 cri 插件的架构。 该插件是 Kubernetes 容器运行时接口&#xff08;CRI&#xff09; 的实现。Containerd与Kubelet在同一个节点上运行。containerd内部的…

修改SpringBoot中默认依赖版本

例如SpringBoot2.7.2中ElasticSearch版本是7.17.4 我希望把它变成7.6.1

IOS破解软件安装教程

对于很多iOS用户而言&#xff0c;获取软件的途径显得较为单一&#xff0c;必须通过App Store进行下载安装。 这样的限制&#xff0c;时常让人羡慕安卓系统那些自由下载各类版本软件的便捷。 心中不禁生出疑问&#xff1a;难道iOS世界里&#xff0c;就不存在所谓的“破解版”软件…

C++Linux网络编程day02:select模型

本文是我的学习笔记&#xff0c;学习路线跟随Github开源项目&#xff0c;链接地址&#xff1a;30dayMakeCppServer 文章目录 select模型fd_set结构体 timeval结构体文件描述符的就绪条件带外数据与普通数据socket的状态 select模型 select是Linux下的一个IO复用模型&#xff…
最新文章