在device driver开发的过程中,有时候会碰到memory相关的问题,比如memory leak,use after free,out of boundary,double free等等。因为device driver运行在kernel环境,kernel是一个global的runtime,driver分配或者释放的memory都是由kernel负责管理,而且所有的device driver,kernel sub system共享这些memory,一旦device driver使用memory不当,就会导致整个kernel的memory管理出现问题。举个简单的例子,比如device driver写buffer发生了越界,那就可能把别人的memory写坏,如果正巧kernel或者别的driver正在使用这块memory,那就立刻会发生oops,或者kernel panic;如果不够幸运,被写坏的memory目前没人在用,而是将来的某个时刻才触发oops或者kernel panic,那就非常麻烦了,因为stack是当前正在运行的上下文的stack,真正的锅应该由写坏别人buffer的driver来背。
kernel中提供了这样一种机制,当访问memory出现问题时,通过记录的track,找到始作俑者,这个机制就是slub debug。
碰巧,最近测试unbind driver的时候,碰到了几个memory相关的问题,比如use after free,double free,甚至是free了栈里的memory :(,unbind driver是要把driver之前分配的所有resource全部释放,中间就会涉及到大量的memory free的操作,自然容易出现问题。
slub debug的实现原理很简单,就是在要分的memory周围放上围栏,正常的memory访问都只会集中在memory object里面,不可能在外面,这样只要监视memory object周围的读写,就能发现不正常的memory访问,尤其是oob,也就是out of boundary,越界访问,这个围栏就是red zone。此外,为了检查use after free,kernel把当前未被使用的object里的内容全部填成特殊值,一旦发现有人在没有分配的情况下,写了object的值,就会报错,从而发现肇事者。
下面介绍slub debug的使用方法,因为debug功能默认是关闭的,所以使用slub debug之前要build kernel。
1. 先下载kernel的source code,可以直接下载kernel official的code
2. 修改config,把自己机器里的config copy到kernel code根目录下,重命名为.config,然后make menuconfig
Kernel hacking -> Memory debugging -> Slub debugging on by default
General setup -> Enable SLUB debugging support
3. build kernel,推荐使用如下命令:
export DEB_BUILD_OPTIONS='nostrip noopt debug'
make -j8 bindeb-pkg LOCALVERSION=-debug
这样可以build出来linux headers,Linux image和linux kenrel symbol,这些debug用都是需要的。
4,安装刚刚build 出来的kernel,然后就可以安装driver运行了。
slub可以检测到double free,out of boundary等,但是并不是实时的,最开始的时候并不知道,所以当看到slub的call stack时一脸懵逼,这stack压根就不是现在跑的啊。没办法,slub的debug就是有它先天的滞后性,因为它是在free memory的时候去检查,而且不是free完马上去检查,而是等到后面有人再去使用的时候才检查,这就导致看不到出问题的现场。针对这个问题,kernel提供了自己的解决办法-slabinfo,这是kernel自己提供的工具,code在tools/vm下,通过slabinfo -v,可以马上对memory进行检查,一旦发现问题,立即打印,这就要求driver developer知道memory是在哪个阶段出现问题的,才能有针对性的使用slub debug。
所以,我一般在怀疑有可能出现问题的时候,手动运行一次slabinfo -v检查当前所有的slab object,下面看几个例子。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/slab.h>
static int oob(void)
{
char *buf = kmalloc(8, GFP_KERNEL);
printk(KERN_INFO "test oob.\n");
//这里写越界,分了8个byte,写了第9个byte
buf[8] = 'a';
kfree(buf);
return 0;
}
static int __init hello_init(void)
{
printk(KERN_INFO "module init.\n");
oob();
return 0;
}
static void hello_exit(void)
{
printk(KERN_INFO "module exit.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
在insmod这个module之后,手动执行slabinfo -v,在dmesg中就可以看到log了:
[ 204.459596] module init.
[ 204.462197] test oob.
[ 204.464700] =============================================================================
[ 204.473065] BUG kmalloc-8 (Tainted: P OE ): Redzone overwritten
[ 204.480077] -----------------------------------------------------------------------------
[ 204.489899] INFO: 0x00000000e7a736f4-0x00000000e7a736f4. First byte 0x61 instead of 0xcc
[ 204.498151] INFO: Allocated in hello_init+0x2c/0x1000 [hello] age=0 cpu=3 pid=2120
[ 204.505695] __slab_alloc+0x20/0x40
[ 204.509308] kmem_cache_alloc_trace+0x238/0x270
[ 204.513898] hello_init+0x2c/0x1000 [hello]
[ 204.518199] do_one_initcall+0x52/0x1d7
[ 204.522081] do_init_module+0x5f/0x221
[ 204.525799] load_module+0x2697/0x2b30
[ 204.529524] __do_sys_finit_module+0xe5/0x120
[ 204.534063] __x64_sys_finit_module+0x1a/0x20
[ 204.538428] do_syscall_64+0x5a/0x110
[ 204.542140] entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 204.547264] INFO: Slab 0x000000003ae1cdd1 objects=23 used=2 fp=0x000000001974d745 flags=0x17ffffc0008101
[ 204.556900] INFO: Object 0x000000003fa16067 @offset=1384 fp=0x (null)
[ 204.565545] Redzone 00000000c5553bcc: cc cc cc cc cc cc cc cc ........
[ 204.574360] Object 000000003fa16067: 6b 6b 6b 6b 6b 6b 6