前面分析了kmemcheck的实现,那么现在就针对其功能进行试验,鉴于kmemcheck支持KMEMCHECK_SHADOW_UNALLOCATED、KMEMCHECK_SHADOW_UNINITIALIZED及KMEMCHECK_SHADOW_FREED的检测且检测上报信息大同小异,本文中的实验主要针对于未初始化的内存进行。
如果需要使能kmemcheck,需要进行一系列的内核参数设置,具体的配置项可以参考内核源码Document目录下的kmemcheck.txt文档描述(不过文档更新的节奏似乎跟不上源码的更新速度,有部分存在偏差)。至于修改配置项make menuconfig,内核编译的设置选项中设置即可。
主要的配置项有:
CONFIG_CC_OPTIMIZE_FOR_SIZE=n
——该项修改为关闭, 设置路径:”General setup” / “Optimize for size”。如果没有关闭该项,Gcc编译器将会进行优化,这将会导致错误地触发kmemcheck上报非问题。比如代码中访问16bit的数据,Gcc则会先加载32bit的数据,然后丢弃高16bit的信息,而kmemcheck只会看到操作了32bit的数据,并且高16bit可能未初始化,那么将会触发问题上报;
CONFIG_SLAB=y or CONFIG_SLUB=y
——选择了slub算法,设置路径:”General setup” / “Choose SLAB Allocator”。
CONFIG_FUNCTION_TRACER=n
——该选项设置为关闭,设置路径:”Kernel hacking” / “Tracers” / “Kernel Function Tracer”。当函数轨迹编译进去的时候,在每个函数调用之前,Gcc都将会向其他函数发出一个调用。这将导致缺页异常处理被调用的时候,ftrace的框架将会在kmemcheck之前调动并获得机会去处理该异常。如果ftrace修改了kmemcheck所依赖的内存信息,将会导致无限的缺页异常循环。
CONFIG_DEBUG_PAGEALLOC= n
——配置项设置为关闭,似乎不太影响。文档路径: “Kernel hacking” / “Debug page memory allocations”。实际设置路径:”Kernel hacking”/”Memory Debugging”/”Debug page memory allocations”。
CONFIG_DEBUG_INFO=y
——该配置项设置为开启,设置路径:”Kernel hacking”/”Compile-time checks and compiler options”/”Compile the kernel with debug info”。
经过前面的设置,将可以看到kmemcheck调试功能开启选项。具体路径:”Kernel hacking” / “Memory Debugging” / “kmemcheck: trap use of uninitialized memory”)。在该设置路径里面有详细的子选项可供设置。
具体子选项有:
CONFIG_KMEMCHECK_[DISABLED | ENABLED | ONESHOT]_BY_DEFAULT
——该设置选项,用于设置Kmemcheck的状态,其中DISABLED表示kmemcheck功能关闭(值为0),而ENABLED表示系统引导之初就使能kmemcheck功能(其值为1),最后的是ONESHOT表示kmemcheck功能仅捕获一次而后自动关闭(其值为2)。如果把握不准的话,可以默认设置功能关闭,因为在系统启动后通过修改/proc/sys/kernel/kmemcheck的值来进行动态调整的,将文件中的修改为对应的状态值即可。该配置项建议修改为DISABLED,后期系统运行后再修改配置文件进行开启,否则系统启动的时候会很慢很慢。
CONFIG_KMEMCHECK_QUEUE_SIZE
——在设置选项中描述为 “kmemcheck: error queue size”。
CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT
——在设置选项中描述为 “kmemcheck: shadow copy size (5 => 32 bytes, 6 => 64 bytes)”。
CONFIG_KMEMCHECK_PARTIAL_OK
——在设置选项中描述为“kmemcheck: allow partially uninitialized memory”。
CONFIG_KMEMCHECK_BITOPS_OK
——在设置选项中描述为“kmemcheck: allow bit-field manipulation”。
本文中的示例此几项的配置为:
其实这些配置项信息都无需记住路径在何处,只需要在make menuconfig的时候,使用右上至左下的斜杠 “/”进入搜索界面,直接搜索上面的配置项即可得到详细的路径信息,无需在乎内核版本如何更新。
将上述的配置项进行配置后,重新编译内核以及安装后,即可进行kmemcheck的测试实验了(由于pc的发展,32位环境已经无法接近于绝迹了,只好找了个64位的环境作为演示示例了)。具体的内核模块如何编写,这里就不赘述了,后期有机会再进行补充。示例实验测试代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/mm.h> #include <asm/page.h> struct page *pages; void kmemchk_access_uninitialized(char *addr) { int offset = 23; printk("[Kmemchk]: access mem page(%p) offset(%d) \n", addr, offset); if (*(addr + offset) == 'a') /*此处尝试访问未被初始化的内存*/ { printk("[Kmemchk]: Fail to hit a ramdon char \n"); } } static int __init kmemchk_uninitialized_init(void) { char *addr; printk("[Kmemchk]: kmemchk_uninitialized_init: \n"); pages = alloc_pages(GFP_KERNEL, 1); if (!pages) { printk("[Kmemchk]: alloc_pages() allocation failed!\n"); } else { addr = page_address(pages); kmemchk_access_uninitialized(addr); } return 0; } static void __exit kmemchk_uninitialized_exit(void) { if (pages) { __free_pages(pages,1); } printk("[Kmemchk]: kmemchk_uninitialized_exit now \n"); } module_init(kmemchk_uninitialized_init) module_exit(kmemchk_uninitialized_exit) MODULE_LICENSE("GPL");
Makefile编译脚本:
obj-m = kmemcheck_test.o all: make -C /lib/modules/`uname -r`/build M=`pwd` clean: rm -f *.o *.ko *.mod.c modules.order Module.symvers
通过Makefile编译出kmemcheck_test.ko后,通过insmod命令即可将该模块加载到环境中,然后通过dmesg即可看到执行结果:
详细的错误信息解析如下:
[ 2499.928090] [Kmemchk]: kmemchk_uninitialized_init: [ 2499.928094] [Kmemchk]: access mem page(ffff88036a4ac000) offset(23) [ 2499.929842] WARNING: kmemcheck: Caught 8-bit read from uninitialized memory (ffff88036a4ac017) #此行记录了告警类型,以及访问的未初始化内存空间地址0xffff88036a4ac017 [ 2499.929843] 80655b6b0388ffff0200000000000000000000000000000000f0ffffff7f0000 #此处是dump出来的内存数据,具体长度取决于CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT配置项 [ 2499.929851] u u u u u u u u u u u u u u u u u u u u u u u u u u u u u u u u [ 2499.929858] ^ #此处的u表示KMEMCHECK_SHADOW_UNINITIALIZED类型的错误,此外KMEMCHECK_SHADOW_UNALLOCATED和 #KMEMCHECK_SHADOW_FREED类型的错误则分别由a和f表示;而^则指出了引致错误的内存访问位置。 [ 2499.929859] RIP: 0010:[<ffffffffc078201c>] [<ffffffffc078201c>] kmemchk_access_uninitialized+0x1c/0x33 [kmemcheck_test] #这里指出了报出告警的指令地址以及函数和其函数内指令偏移。 [ 2499.929862] RSP: 0018:ffff88036a4cfc60 EFLAGS: 00010286 [ 2499.929862] RAX: 0000000000000038 RBX: ffff88036a4ac000 RCX: 0000000000000006 [ 2499.929863] RDX: 0000000000000000 RSI: 0000000000000246 RDI: ffff88037020dc30 [ 2499.929864] RBP: ffff88036a4cfc68 R08: 0000000000000002 R09: 00000000000003c8 [ 2499.929865] R10: 00003ffffffff000 R11: 00000000000003c8 R12: ffff88036a455a80 [ 2499.929865] R13: 0000000000000000 R14: ffffffffc0006000 R15: ffffffffc0784000 [ 2499.929866] FS: 0000000000000000(0000) GS:ffff880370200000(0000) knlGS:0000000000000000 [ 2499.929867] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 2499.929868] CR2: ffff880368e561d8 CR3: 0000000002c0a000 CR4: 00000000003406f0 [ 2499.929869] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 2499.929870] DR3: 0000000000000000 DR6: 00000000ffff4ff0 DR7: 0000000000000400 [ 2499.929870] [<ffffffffc000606f>] 0xffffffffc000606f [ 2499.929872] [<ffffffff8100211e>] do_one_initcall+0xae/0x1f0 [ 2499.929874] [<ffffffff8113b6e3>] do_init_module+0x55/0x1c1 [ 2499.929876] [<ffffffff810eba2a>] load_module+0x208a/0x26a0 [ 2499.929878] [<ffffffff810ec284>] SYSC_finit_module+0xb4/0xe0 [ 2499.929880] [<ffffffff810ec2c9>] SyS_finit_module+0x9/0x10 [ 2499.929882] [<ffffffff81755936>] entry_SYSCALL_64_fastpath+0x16/0x75 [ 2499.929884] [<ffffffffffffffff>] 0xffffffffffffffff
根据错误信息可以看到此处报出来的告警在kmemchk_access_uninitialized(),位于偏移0x1c的指令访问了未被初始化的内存空间。继而将编译生成的kmemcheck_test.ko进行反汇编,找到kmemchk_access_uninitialized()的该指令。
0000000000000000 <kmemchk_access_uninitialized>: 0: 55 push %rbp 1: 48 89 fe mov %rdi,%rsi 4: ba 17 00 00 00 mov $0x17,%edx 9: 48 89 e5 mov %rsp,%rbp c: 53 push %rbx d: 48 89 fb mov %rdi,%rbx 10: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 17: e8 00 00 00 00 callq 1c <kmemchk_access_uninitialized+0x1c> 1c: 80 7b 17 61 cmpb $0x61,0x17(%rbx) 20: 74 03 je 25 <kmemchk_access_uninitialized+0x25> 22: 5b pop %rbx 23: 5d pop %rbp 24: c3 retq 25: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 2c: e8 00 00 00 00 callq 31 <kmemchk_access_uninitialized+0x31> 31: eb ef jmp 22 <kmemchk_access_uninitialized+0x22>
即cmpb $0x61,0x17(%rbx)指令,可以分析得出该比较指令对应的即为函数中的if (*(addr + offset) == ‘a’)该条件判断操作。
由此一来便可以定位此类对未初始化内存进行访问的不合法的信息。