内存踩踏分析:
搞底层开发的人一定听过内存踩踏这个令人可怕的专业词语,轻则功能错乱,重则死机崩溃。关键是排查起来还费劲,尤其是在内核驱动中。因为当我们发现异常的时候,往往已经不是第一案发现场了,可能是第二、第三、第N现场了,作案人员可能早已逃离作案现场,在案发现场,可能不曾留下任何蛛丝马迹。有时可能有些蛛丝马迹,但也极为隐秘,很难找出来。当然你可以打开内核KASAN重新编译,再重新复现问题看能不能抓到一些有用的信息。但这样重新编译后,工作环境发生了变化,问题不一定能够复现。我最近遇到一个奇怪的问题,看上去很像内存踩踏。我一个结构体中的成员变量,在初始化之后,就再也没动过它了,可后面某次访问时,它的值突然就变成一个异常值了。种种迹象看起来很像是内存踩踏,于是我在这个变量前后加一些变量想看看踩踏的数据有没有啥规律,但是改了代码之后就复现不到了。由于我的变量是只读的,我就找找看有没有办法监控下这个变量的地址,当CPU往这个地址写数据的时候,自动把调用堆栈打印出来,那么就知道哪里在搞鬼了。我找到了以下方法验证可行,读和写都可以监控到,分享给C厂的小伙伴,希望大家以后遇到类似问题时,手上又多了一件破案的工具。对于那些非只读的场合,自己修改变量的值时,要先去掉监控,修改完了之后再打开监控。
解决方案:
#include <linux/kallsyms.h>
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>
static struct perf_event * __percpu *sample_hbp;
static void sample_hbp_handler(struct perf_event *bp,
struct perf_sample_data *data,
struct pt_regs *regs) {
printk(KERN_INFO "sample_hbp_handler!\n");
dump_stack();
printk(KERN_INFO "Dump stack from sample_hbp_handler!\n");
BUG_ON(1); // 让系统停下来
}
static int my_hw_break_register(void *addr) {
int ret;
struct perf_event_attr attr;
hw_breakpoint_init(&attr);
attr.bp_addr = (__u64)addr; //64位系统
attr.bp_len = HW_BREAKPOINT_LEN_8; //64位系统地址8字节,32位系统4字节
attr.bp_type = HW_BREAKPOINT_W;// | HW_BREAKPOINT_R;
sample_hbp = register_wide_hw_breakpoint(&attr, sample_hbp_handler, NULL);
if (IS_ERR((void __force *)sample_hbp)) {
ret = PTR_ERR((void __force *)sample_hbp);
goto fail;
}
printk(KERN_INFO "HW Breakpoint for write %p installed.\n", addr);
return 0;
fail:
printk(KERN_INFO "HW Breakpoint registration failed for %p!\n", addr);
return ret;
}
static void my_hw_break_unregister(void) {
unregister_wide_hw_breakpoint(sample_hbp);
printk(KERN_INFO "HW Breakpoint for write uninstalled.\n");
}
//测试代码
void test(void) {
int a = 0;
my_hw_break_register(&a);
a = 1; //这样就会触发监控
my_hw_break_unregister(); //用完了释放
}