kdump+crash 解决死机问题 全局变量及malloc堆变量的获取

目录

1、实验目的

2、实验步骤

3、全局变量解析思路

4、kernel内部全局变量获取

5、模块化驱动全局变量

5.1、 p 命令打印模块化全局变量错误

5.2、sym/rd + struct 命令解析xxx.ko中的全局变量

5.3、使用rd命令解析xxx.ko中的全局变量

6、堆变量的获取

环境:arm64,Linux version 5.10.66

1、实验目的

 使用crash解析全局变量 KdumpStack_st stKdumpStack

        实验程序如下,当程序编译成ko并使用insmod加载到设备后,串口输入 echo kdump-3 > /proc/dbug/dump 命令之后执行我们的测试程序,kdump产生vmcore文件后使用crash命令来解析全局变量 KdumpStack_st stKdumpStack 的内容。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>

#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/version.h>

#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/kasan.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
/* 
写成功要返回写入的字节数,否则 linux 还会尝试写入
 */

#define PROC_DIR_NAME		"dbug"			//文件夹名
#define PROC_FILE_NAME		"kdump"			//文件名称
#define KBUFSIZE 40


#define NAMELEN 20

typedef struct kdump_stack {
	char cName[NAMELEN];
	int iNumbers;
	struct list_head list;
	void (*printk)(void);
} KdumpStack_st;

typedef struct kdump_node {
	char cName[NAMELEN];
	int iNumber;
	struct list_head list;
} KdumpNode_st;

typedef struct kdump_localval {
	char cName[NAMELEN];
	int iNumber;
} KdumpLkval_st;
static KdumpLkval_st *gpstKdLkval = NULL;
static KdumpStack_st stKdumpStack = {0};

void kdump_printk(void)
{
	printk("%s %s %d", __FILE__, __func__, __LINE__);
	/* 自定义触发kdump的办法 */
	panic("kdump test,Parse stack data");
}

int kdump_stack_test(int num, KdumpStack_st *pstKdStack, KdumpLkval_st *pstKdLkval)
{
	int iCycle = 0;
	char cNodeName[NAMELEN] = {0};
	KdumpNode_st *pstKdNode = NULL;
	/* 链表查看 */
	for(iCycle = 0; iCycle < num; iCycle++) {
		pstKdNode = kzalloc(sizeof(KdumpNode_st), GFP_KERNEL);
		snprintf(cNodeName, NAMELEN, "node_%d", iCycle);
		strncpy(pstKdNode->cName, cNodeName, NAMELEN);
		pstKdNode->iNumber = iCycle;
		
		list_add_tail(&pstKdNode->list, &pstKdStack->list);
	}
	
	/* 栈中结构体变量 */
	pstKdLkval->iNumber = 101;
	/* 堆中结构体变量 */
	gpstKdLkval->iNumber = 201;
	/* 全局结构体变量 */
	pstKdStack->printk();

	return 0;
}

int kdump_proc_write(int num)
{

	KdumpLkval_st stKdLkval = {0};
	/* 链表 */
	INIT_LIST_HEAD(&stKdumpStack.list);
	/* 全局结构体变量 */
	strncpy(stKdumpStack.cName, "StTest", NAMELEN);
	stKdumpStack.iNumbers = num;
	stKdumpStack.printk = kdump_printk;
	/* 栈中结构体变量 */
	strncpy(stKdLkval.cName, "local val", NAMELEN);
	stKdLkval.iNumber = 100;
	/* 堆中结构体变量 */
	gpstKdLkval = kzalloc(sizeof(KdumpLkval_st), GFP_KERNEL);
	strncpy(gpstKdLkval->cName, "heap val", NAMELEN);
	gpstKdLkval->iNumber = 200;
	
	kdump_stack_test(num, &stKdumpStack, &stKdLkval);
	return 0;
}

/* 以下为测试无关代码,只是在 /proc/dbug/ 目录下创建 kdump 的调试文件;
串口下输入 echo kdump-3 > /proc/dbug/dump 命令之后执行我们的测试程序 */

char kbuf[KBUFSIZE] = {0};		//保存用户层传进来的数捿
struct proc_dir_entry *proc_wrbuff_dir;

static int proc_wrbuff_open(struct inode *inode,struct file *file);  
static ssize_t proc_wrbuff_read(struct file *file, char __user *ubuf, size_t count, loff_t *offset);  
static ssize_t proc_wrbuff_write(struct file *file, const char __user *ubuf, size_t count, loff_t *offset);  


static int proc_wrbuff_open(struct inode *inode,struct file *file)  {  
	printk("open embedsky board device!\n");  
	return 0;  
}

static ssize_t proc_wrbuff_read(struct file *file, char __user *ubuf, size_t count, loff_t *offset)  {  
	if (count > strlen(kbuf))
		count = strlen(kbuf);
	if (count < 0 )
		return -3;
	if (copy_to_user(ubuf, kbuf, count)) {
		printk(KERN_ERR "copy_to_user failed! \n");
		return -4;
	}
	return count;
}

static ssize_t proc_wrbuff_write(struct file *file, const char __user *ubuf, size_t count, loff_t *offset) {
	
	int num = 0;
	size_t cnt = min((int)count, KBUFSIZE - 1);
	if(copy_from_user(kbuf,ubuf,cnt)) {
		printk(KERN_ERR "copy_to_user failed! \n");
		return -EFAULT;
	}
	kbuf[cnt] = '\0';
	printk("printk kbuf %s \n",kbuf);

	if(sscanf(kbuf, "kdump-%d", &num)) {
		kdump_proc_write(num);
	}

	return cnt;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 10, 0)
static struct proc_ops fops_proc_wrbuffer = {

	.proc_open = proc_wrbuff_open,
	.proc_read = proc_wrbuff_read,
	.proc_write = proc_wrbuff_write,
 };
#else
static struct file_operations fops_proc_wrbuffer = {
	.owner = THIS_MODULE,
	.open = proc_wrbuff_open,
	.read = proc_wrbuff_read,
	.write = proc_wrbuff_write,
	.owner = THIS_MODULE,
 };
#endif


static int __init proc_wrbuff_init(void) {
	int ret = 0;  
	struct proc_dir_entry *proc_file;

	/* 1 create parent dir in /porc/dbug */
	proc_wrbuff_dir = proc_mkdir(PROC_DIR_NAME, NULL);
	if(!proc_wrbuff_dir){
		printk("create proc dir error!");
		return -1;
	}
	/* 2creata device file in /proc/parent dir*/
	proc_file = proc_create_data(PROC_FILE_NAME, 0666, proc_wrbuff_dir,&fops_proc_wrbuffer,0);
	if (!proc_file) {
		printk("create proc file error!");
		ret = -2;
		goto no_proc_file;
	}

	return 0;

no_proc_file:
	remove_proc_entry(PROC_FILE_NAME,proc_wrbuff_dir);
	return ret;
}

static void __exit proc_wrbuff_exit(void) {

	remove_proc_entry(PROC_FILE_NAME,proc_wrbuff_dir);
	remove_proc_entry(PROC_DIR_NAME, NULL);
}
late_initcall(proc_wrbuff_init);
module_exit(proc_wrbuff_exit);

MODULE_DESCRIPTION("debug");
MODULE_LICENSE("GPL");

2、实验步骤

前提:设备linux支持了kdump功能,支持方法参考此专题文章

a、编译测试模块:将上述源码编译成kdump.ko驱动模块,在设备串口中使用insmod命令加载此驱动到设备中。此时会生成 /proc/dbug/kdump 调试文件

b、触发panic:执行 echo kdump-4 > /proc/dbug/kdump 命令执行测试程序,测试程序会执行 panic 触发kdump机制,启动捕获内核,并在捕获内核中生成 /proc/vmcore 文件。

c、保存vmcore文件:执行 cd /proc;tar -czf /tmp/3588/vmcore.tar.gz ./vmcore 将捕获内核下的vmcore文件压缩并拷贝到u盘或者nfs挂载的目录中。

d、使用crash分析vmcore文件:执行 crash vmlinux vmcore 命令使用crash分析vmcore文件。

e、由于vmcore文件只会保留kdump.ko的代码部分,因此需要在crash中使用 mod加载kdump.ko 驱动模块的调试以及符号信息。这样执行 crash> dis -l kdump_proc_write 命令才会正确显示汇编代码对应的行号信息

mod -s kdump /kdump/demo/stack/kdump.ko

3、全局变量解析思路

        全局变量存储在静态数据段,生命周期是从程序开始到程序结束,全局作用域;因此crash工具有专门的命令 p 读取全局变量的内容;但实际测试发现 p 能够读取的全局变量只局限于kernel内部定义的全局变量,比如0号进程: struct task_struct init_task。

        针对模块化的驱动程序,比如 xxx.ko 中定义的全局变量,crash中的 p 指令无法正确解析 xxx.ko 中的全局变量(尽管使用 mod -s xxx xxx.ko 在crash中加载的驱动的调试信息);实测可以使用 sys/rd + struct  命令组合的方式去获取全局结构体的内容。 

4、kernel内部全局变量获取

crash中 p 命令用于打印制定符号的值,可以在crash中使用 help p 查看具体的帮助信息,简单说明如下:

SYNOPSIS
  p [-x|-d][-u] [symbol[:cpuspec]]
        -x 十六进制显示
        -d 十进制显示
        symbol 显示全局变量的值
        symbol:x 针对percpu的全局变量,打印具体某个cpu的值

 使用举例如下:

/* 打印kernel中的全局变量 */
crash> p jiffies
jiffies = $1 = 4294947275
crash> p jiffies -x
jiffies = $2 = 0xffffb1cb
crash> p jiffies -d
jiffies = $3 = 4294947275

/* 打印percpu的全局变量 */
crash> p irq_stat
PER-CPU DATA TYPE:
  irq_cpustat_t irq_stat;
PER-CPU ADDRESSES:
  [0]: ffffff81fced2980
  [1]: ffffff81fcef0980
  [2]: ffffff81fcf0e980
  [3]: ffffff81fcf2c980
  [4]: ffffff81fcf4a980
  [5]: ffffff81fcf68980
  [6]: ffffff81fcf86980
  [7]: ffffff81fcfa4980
  
/* 打印制定某个cpu的percpu变量 */
crash> p irq_stat:1
per_cpu(irq_stat, 1) = $4 = {
  __softirq_pending = 0
}

5、模块化驱动全局变量

5.1、 p 命令打印模块化全局变量错误

针对模块化的驱动使用p命令则出错

/* 实际 cName=“StTest” iNumbers=4  */
crash> p stKdumpStack
stKdumpStack = $7 = {
  cName = "\001\005\200R\000\000\000\260\000P\002\221c\345\375\225\000\000", <incomplete sequence \324>,
  iNumbers = -1463714819,
  list = {
    next = 0xd65f03c0d50323bf,
    prev = 0xb9402c64d5384103
  },
  printk = 0xf9400462d503233f
}

5.2、sym/rd + struct 命令解析xxx.ko中的全局变量

a、 sym/rd 获取全局结构体的地址

使用 sym/rd 获取全局结构体的地址

crash> rd stKdumpStack
ffffffc008f0d4b0:  0000747365547453                    StTest..
crash> sym stKdumpStack
ffffffc008f0d4b0 (b) stKdumpStack [kdump]
crash>

 这里也可以使用 rd 命令将 全局变量的内容打印出来。

b、 struct 获取全局结构体内容

使用 struct 获取全局结构体内容

crash> struct KdumpStack_st ffffffc008f0d4b0
struct KdumpStack_st {
  cName = "StTest\000\000\000\000\000\000\000\000\000\000\000\000\000",
  iNumbers = 4,
  list = {
    next = 0xffffff8118002798,
    prev = 0xffffff8118002618
  },
  printk = 0xffffffc008f0b4f4 <kdump_printk>
}
crash>

5.3、使用rd命令解析xxx.ko中的全局变量

a、使用 struct KdumpStack_st -o 确认结构体构大小以及各成员的偏移地址

crash> struct KdumpStack_st -o
typedef struct kdump_stack {
   [0] char cName[20];
  [20] int iNumbers;
  [24] struct list_head list;
  [40] void (*printk)(void);
} KdumpStack_st;
SIZE: 48

b、使用 rd stKdumpStack 6 将48字节内容读出来

crash> rd stKdumpStack 6
ffffffc008f0d4b0:  0000747365547453 0000000000000000   StTest..........
ffffffc008f0d4c0:  0000000400000000 ffffff8118002798   .........'......
ffffffc008f0d4d0:  ffffff8118002618 ffffffc008f0b4f4   .&..............
crash>

6、堆变量的获取

        使用kzalloc去分配堆内存空间,堆内存的地址一般会使用全局变量去保存;获取对变量的内容,关键在于获取堆变量的指针。以 kdump_proc_write 函数为例,获取指针 gpstKdLkval 指向的堆内容。

int kdump_proc_write(int num)
{    ........................................................
	/* 堆中结构体变量 */
	gpstKdLkval = kzalloc(sizeof(KdumpLkval_st), GFP_KERNEL);
	strncpy(gpstKdLkval->cName, "heap val", NAMELEN);
	gpstKdLkval->iNumber = 200;
	.........................................................
	return 0;
}

 a、使用 rd 获取指针 KdumpLkval_st *gpstKdLkval 的内容

crash> rd gpstKdLkval
ffffffc008f0d4a8:  ffffff8118002900                    ......

b、 使用 struct 获取堆变量内容

ffffff8118002900 为指针指向的堆内存地址,通过 struct 命令将堆内容打印出来

crash> struct KdumpLkval_st ffffff8118002900
struct KdumpLkval_st {
  cName = "heap val\000\000\000\000\000\000\000\000\000\000\000",
  iNumber = 201
}
crash>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值