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);
#endifvoid 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_SMPunsigned long flags;
#endifstatic int print = 0;if(!wrecord){if(!print){print = 1;printk(KERN_EMERG"please use mem_log_init first.\n");			}return;}
#ifdef CONFIG_SMPspin_lock_irqsave(&mem_log_spinlock, flags);
#endif	wrecord->index = mem_log_index++;wrecord->module = module;
#ifdef CONFIG_SMPwrecord->flag = (flag << 4) | smp_processor_id();
#elsewrecord->flag = (flag << 4);
#endifmemcpy(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_SMPspin_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.mzph.cn/news/672436.shtml

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

相关文章

【数据库】详细说一下一条 MySQL 语句执行的步骤

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 Server 层按顺序执行 SQL 的步骤为&#xff1a; 我的其他博客 ​ 正文 Server 层按顺序执行 SQL 的步骤为&#xff1a; 客户端请求 -&g…

【C语言 - 哈希表 - 力扣 - 相交链表】

相交链表题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0…

【TCP】高频面试题

前言 在IT行业的求职过程中&#xff0c;传输控制协议&#xff08;TCP&#xff09;作为网络通信的核心协议之一&#xff0c;其相关面试题常常出现在各大公司面试中。TCP的稳定性和可靠性是支撑互联网数据传输的基石&#xff0c;因此&#xff0c;对TCP有深入理解不仅能够帮助求职…

网络协议与攻击模拟_15FTP协议

了解FTP协议 在Windows操作系统上使用serv-U软件搭建FTP服务 分析FTP流量 一、FTP协议 1、FTP概念 FTP&#xff08;文件传输协议&#xff09;由两部分组成&#xff1a;客户端/服务端&#xff08;C/S架构&#xff09; 应用场景&#xff1a;企业内部存放公司文件、开发网站时利…

centos7指定目录上传到google云盘

from datetime import datetime, timedelta from concurrent.futures import ThreadPoolExecutor import os,time,subprocess,tracebackdef run_cmd(command):"""运行命令并返回输出。"""shell Trueprint(command,command)result subprocess.r…

解密 ARMS 持续剖析:如何用一个全新视角洞察应用的性能瓶颈?

作者&#xff1a;饶子昊、杨龙 应用复杂度提升&#xff0c;根因定位困难重重 随着软件技术发展迭代&#xff0c;很多企业软件系统也逐步从单体应用向云原生微服务架构演进&#xff0c;一方面让应用实现高并发、易扩展、开发敏捷度高等效果&#xff0c;但另外一方面也让软件应…

[leetcode] 30. 串联所有单词的子串

文章目录 题目描述解题方法滑动窗口java代码复杂度分析 题目描述 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab"…

【芯片设计- RTL 数字逻辑设计入门 10 -- 奇偶校验实现】

文章目录 奇偶校验单目运算符&#xff08;|,^,&&#xff09;verilog codeverilog codetestbench code 问题总结 奇偶校验 现在需要对输入的32位数据进行奇偶校验,根据sel输出校验结果。 实际上这里做的是奇偶检测&#xff0c;如果是奇数个 1 则结果为 1&#xff0c;使用…

C#入门详解_01_课程简介、C#语言简介、开发环境和学习资料的准备

文章目录 1. 课程简介2. C#语言简介3.开发环境与学习资料 1. 课程简介 开设本课程的目的 传播C#开发的知识&#xff0c;让更多的人有机会接触到软件开发行业引导有兴趣或者想转行的朋友进入软件开发行业 课程内容 完整讲述C#语言在实际软件开发中的应用采用知识讲述加实例程序…

VR视频编辑解决方案,全新视频内容创作方式

随着科技的飞速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术正逐渐成为各个领域的创新力量。而美摄科技&#xff0c;作为VR技术的引领者&#xff0c;特别推出了一套全新的VR视频编辑方案&#xff0c;为企业提供了一个全新的视频内容创作方式。 美摄科技的VR视频编…

8.0 Zookeeper 四字命令教程详解

zookeeper 支持某些特定的四字命令与其交互&#xff0c;用户获取 zookeeper 服务的当前状态及相关信息&#xff0c;用户在客户端可以通过 telenet 或者 nc&#xff08;netcat&#xff09; 向 zookeeper 提交相应的命令。 安装 nc 命令&#xff1a; $ yum install nc …

thinkphp6入门(19)-- 中间件向控制器传参

可以通过给请求对象赋值的方式传参给控制器&#xff08;或者其它地方&#xff09;&#xff0c;例如 <?phpnamespace app\middleware;class Hello {public function handle($request, \Closure $next){$request->hello ThinkPHP;return $next($request);} } 然后在控制…

改进神经网络

Improve NN 文章目录 Improve NNtrain/dev/test setBias/Variancebasic recipeRegularizationLogistic RegressionNeural networkother ways optimization problemNormalizing inputsvanishing/exploding gradientsweight initializegradient checkNumerical approximationgrad…

python flask 魔术方法

魔术方法作用_init_对象的初始化方法_class_返回对象所属的类_module_返回类所在的模块_mro_返回类的调用顺序&#xff0c;可以找到其父类&#xff08;用于找父类&#xff09;_base_获取类的直接父类&#xff08;用于找父类&#xff09;_bases_获取父类的元组&#xff0c;按它们…

Adaptec RAID 控制器arcconf 管理命令的常见的查询操作

ARCCONF命令行工具可以在服务器正常运行过程中对Adaptec RAID卡进行带内在线查询配置操作&#xff0c;无需重启服务器&#xff0c;十分方便快捷&#xff0c;本文讲解常见的查询操作。 一、查询流程 二、常见指令 1、查询已安装的RAID卡清单 [rootlocalhost ~]# ./arcconf-lin…

DolphinScheduler本地安装

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

【Web - 框架 - Vue】随笔 - 通过`CDN`的方式使用`VUE 2.0`和`Element UI`

通过CDN的方式使用VUE 2.0和Element UI VUE 网址 https://cdn.bootcdn.net/ajax/libs/vue/2.7.16/vue.js源码 https://download.csdn.net/download/HIGK_365/88815507测试 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset&quo…

储氢材料行业调研:市场需求将不断增长

近年来&#xff0c;热度持续升温的碳中和、碳达峰话题&#xff0c;使得氢能及其相关产业被高度关注&#xff0c;而决定氢能应用关键的是安全、高效的氢能储运技术。在氢能需求不断增长的情况下&#xff0c;储氢材料行业是市场也将不断发展。在氢能需求不断增长的情况下,储氢材料…

如何实现Vuex本地存储

在前端开发中&#xff0c;Vuex是一款非常强大的状态管理工具&#xff0c;但是默认情况下&#xff0c;Vuex的数据是存储在内存中的&#xff0c;刷新页面后数据将会丢失。这往往会导致用户在刷新页面后需要重新登录等繁琐的操作。本篇文章将教会您如何实现Vuex的本地存储&#xf…

从零开始手写mmo游戏从框架到爆炸(一)— 开发环境

一、创建项目 1、首先创建一个maven项目&#xff0c;pom文件如下&#xff1a; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0…