Linux 内核内存检测工具 - Kmemcheck(3)
- UID
- 1066743
|
Linux 内核内存检测工具 - Kmemcheck(3)
Kmemcheck 使用示例下面通过三个例子分别展示了 kmemcheck 所能检测出的三种内存访问错误:
1. 在本例中(完整代码请参阅附件 1 中的 kmemchk_ uninitialized.c),我们先用 alloc_pages 分配了两个页面大小的内存,然后在未初始化的情况下对其中的内容进行访问,我们会发现 kmemcheck 会发出内存未初始化警告信息(即 KMEMCHECK_SHADOW_UNINITIALIZED 类型的错误信息)。
清单 6: kmemchk_uninitialized.c 部分代码示例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| …
static int __init kmemchk_uninitialized_init(void)
{
char * addr; /* used to store page struct addresses */
int offset; /* offset to the page */
…
pages = alloc_pages(GFP_KERNEL,1); /* allocate 2 pages, \
if __GFP_NOTRACK is specified, no kmemcheck warnings would be issued */
if(!pages)
printk("alloc_pages: allocation failed !\n");
else {
addr = page_address(pages); /* convert to virt addr */
offset = 43;
printk("checkpoint: access mem page: %p offset: %d \n",addr,offset);
if(*(addr + offset) == 'a' ) /* access uninitialized memory */
printk("You hit a ramdon char \n");
}
…
}
…
|
加载模块后终端会显示 kmemcheck 打印的警告信息(完整 log 信息请参阅附件 2):
清单 6. Unintialized 警告日志1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| …
checkpoint: access mem page: cef52000 offset: 43
WARNING: kmemcheck: Caught 8-bit read from uninitialized memory (cef5202b) --> a
0000000000000000000000000000000000000000000000000000000000000000 --> b
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 --> c
^ --> d
Pid: 13017, comm: insmod Tainted: G D W (2.6.31.1 #2) V71 --> e
EIP: 0060:[<d09d306a>] EFLAGS: 00010286 CPU: 0
EIP is at 0xd09d306a
EAX: 00000035 EBX: cef52000 ECX: 00000092 EDX: 00885000
ESI: 00000000 EDI: b8018fc0 EBP: cef25f5c ESP: c09e2898
DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068
CR0: 8005003b CR2: cfbd92e0 CR3: 0ef2c000 CR4: 000006d0
DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
DR6: ffff4ff0 DR7: 00000400
[<c0401123>] do_one_initcall+0x23/0x180
[<c0471b71>] sys_init_module+0xb1/0x1f0
[<c0403b14>] sysenter_do_call+0x12/0x28
[<ffffffff>] 0xffffffff
…
|
下面对清单 6 中的主要内容进行分析:
<a> WARNING: kmemcheck: Caught 8-bit read from uninitialized memory (cef5202b)
该行记录了非法访问的内存地址:0xcef5202b,以及错误类型: read from uninitialized memory。
<b> 0000000000000000000000000000000000000000000000000000000000000000
该行打印了数据页面中的 32 个字节(每个字节表示为二个十六进制的数字)的内容,实际上打印区间的大小是由上面配置的 CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT 来决定的(大小为 2 的 CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT 次幂,在本例中 CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT 采用的是系统默认值 5,所以打印出来的内存区间大小为 2^5=32)。另外,根据前文的介绍,可以计算出此内存区间的起始地址为 0xcef5202b & ~(2^5 - 1),即 0xcef52020。所以本行打印的是从地址 0xcef52020 开始的 32 个字节。
<c> 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
该行打印了上一行数据页面内存区间所对应的影子页面中的内容(相同的区间长度和页面偏移量),其中每一个字符对应一个字节('u'表示 KMEMCHECK_SHADOW_UNINITIALIZED 类型的错误,'a'表示 KMEMCHECK_SHADOW_UNALLOCATED 类型的错误,'f'表示 KMEMCHECK_SHADOW_FREED 类型的错误)。
<d> ^
该符号指示了非法访问的内存地址所在的位置,由于地址 0xcef5202b 相对于起始地址 0xcef52020 的偏移为 11,因此'^'指示到第 12 个'u'。
<e>及其以后的内容记录的是当时 stack trace 和寄存器信息,其中 EIP 地址为 0xd09d306a,它就是引发警告的指令地址,通过下面的过程可以找出该指令对应的 C 语句位置:
1
2
| #cat /proc/kallsyms | grep kmemchk_uninitialized_init
d09d3000 t kmemchk_uninitialized_init [kmemchk_uninitialized]
|
1
| #objdump --source -d kmemchk_uninitialized.ko
|
命令输出为 :
清单 7. objdump 输出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| ...
if(!pages)
printk("alloc_pages: allocation failed !\n");
else {
addr = page_address(pages); /* convert to virt addr */
4b: e8 fc ff ff ff call 4c <init_module+0x4c>
50: 89 c3 mov %eax,%ebx
offset = 43;
printk("checkpoint: access mem page: %p offset: %d \n",addr,offset);
52: c7 44 24 08 2b 00 00 movl $0x2b,0x8(%esp)
59: 00
5a: 89 44 24 04 mov %eax,0x4(%esp)
5e: c7 04 24 24 00 00 00 movl $0x24,(%esp)
65: e8 fc ff ff ff call 66 <init_module+0x66>
if(*(addr + offset) == 'a' ) /* access uninitialized memory */
6a: 80 7b 2b 61 cmpb $0x61,0x2b(%ebx)
6e: 75 d3 jne 43 <init_module+0x43>
printk("You hit a ramdon char \n");
...
|
由于错误发生在 0x6a 偏移处 (0xd09d306a 减去 0xd09d3000 为 0x6a),从而可以很清楚的看出是 if(*(addr + offset) == 'a' ) 这条语句引发了 kmemcheck 的警告,而该语句访问了一个未初始化的内存地址。值得一提的是,如果访问一个未初始化的局部变量(存储在内核栈中),kmemcheck 是不会报错的,其原因在于内核栈在分配的时候置位了 __GFP_NOTRACK 分配标志位,这个设计是很合理的,因为栈在函数调用中有很多进栈、出栈的操作,很难判断某一个地址是否是未初始化的非法访问。
2. 本例中(完整代码请参阅附件 1 中的 kmemchk_unallocated.c),我们先创建了一个 slab cache,然后对 slab 中未分配的对象进行访问,我们会发现 kmemcheck 会发出内存未分配警告信息(即 KMEMCHECK_SHADOW_UNALLOCATED 类型的错误信息) |
|
|
|
|
|