目录
5.2、sym/rd + struct 命令解析xxx.ko中的全局变量
环境: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>