Linux内核检测内存泄漏的工具(kmemcheck实例)

十一月 26, 2009 · Filed Under Linux and UNIX 

Linux内核检测内存泄漏的工具(kmemcheck实例)
1. 在本例中用 alloc_pages 分配了两个页面大小的内存,然后在未初始化的情况下对其中的内容进行访问,会发现 kmemcheck 会发出内存未初始化警告信息(即 KMEMCHECK_SHADOW_UNINITIALIZED 类型的错误信息)。

(1): kmemchk_uninitialized.c 部分代码示例
    

 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 打印的警告信息
(2). Unintialized 警告日志
    

 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+0×23/0×180
 [<c0471b71>] sys_init_module+0xb1/0×1f0
 [<c0403b14>] sysenter_do_call+0×12/0×28
 [<ffffffff>] 0xffffffff

下面对(2) 中的主要内容进行分析:
<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 语句位置:
通过如下命令得到模块在内核中的地址信息:
 #cat /proc/kallsyms | grep kmemchk_uninitialized_init
 d09d3000 t kmemchk_uninitialized_init [kmemchk_uninitialized]

通过 objdump 对模块进行反编译:
 #objdump –source -d kmemchk_uninitialized.ko

命令输出为 :

(3). objdump 输出
    
 …
 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+0×4c>
  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   $0×2b,0×8(%esp)
  59: 00
  5a: 89 44 24 04           mov    %eax,0×4(%esp)
  5e: c7 04 24 24 00 00 00 movl   $0×24,(%esp)
  65: e8 fc ff ff ff       call   66 <init_module+0×66>
          if(*(addr + offset) == ‘a’ )  /* access uninitialized memory */
  6a: 80 7b 2b 61           cmpb   $0×61,0×2b(%ebx)
  6e: 75 d3                 jne    43 <init_module+0×43>
            printk(”You hit a ramdon char \n”);
 …

由于错误发生在 0×6a 偏移处 (0xd09d306a 减去 0xd09d3000 为 0×6a),从而可以很清楚的看出是 if(*(addr + offset) == ‘a’ ) 这条语句引发了 kmemcheck 的警告,而该语句访问了一个未初始化的内存地址。值得一提的是,如果访问一个未初始化的局部变量(存储在内核栈中),kmemcheck 是不会报错的,其原因在于内核栈在分配的时候置位了 __GFP_NOTRACK 分配标志位,这个设计是很合理的,因为栈在函数调用中有很多进栈、出栈的操作,很难判断某一个地址是否是未初始化的非法访问。
2. 本例中(完整代码请参阅附件 1 中的 kmemchk_unallocated.c),我们先创建了一个 slab cache,然后对 slab 中未分配的对象进行访问,我们会发现 kmemcheck 会发出内存未分配警告信息(即 KMEMCHECK_SHADOW_UNALLOCATED 类型的错误信息)

(4). kmemchk_unallocated.c 部分代码示例
    

 static int __init kmemchk_unallocated_init(void)
 {
        …
        /* create a slab cache */
        cachep  = kmem_cache_create(
                      “kmemchk_test_cache”, /* the name of slab cache */
                      1024,  /* object size */
                      0,
                      SLAB_HWCACHE_ALIGN,    /* without SLAB_NOTRACK specifed,  \
                                     or else there would be kmemcheck warning messages */
                      NULL   /* no constructor */ 
                      );
       …
        /* allocate a object */
        object = kmem_cache_alloc(cachep,GFP_KERNEL);
        if(object) {
           offset = 1500;

           /* access unallocated memory, objects are allocated from high */
           /* addr to low addr, so we minus the offset to access the memory */
           /* with no objects allocated yet */
        if(*((char*)object – offset) == ‘a’)
             printk(”You hit a random char \n”);
        }        
      …
 }

加载模块后就会出现 kmemcheck 的警告信息

(5). Unallocated 警告日志
    
 WARNING: kmemcheck: Caught 8-bit read from freed memory (c14000dc)
 ffffffff636f6e7374616e745f746573745f6269740000000000000061000000
 f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f
                                                                   ^
 Pid: 13144, comm: rsyslogd Tainted: G      D W  (2.6.31.1 #2) V71
 EIP: 0060:[<d0a5b044>] EFLAGS: 00010282 CPU: 0
 EIP is at kmemchk_freed_exit+0×44/0×5b [kmemchk_freed]
 EAX: c1400000 EBX: 00000000 ECX: 00000092 EDX: 00885000
 ESI: d0a5b4a0 EDI: 00000000 EBP: cfb9ff4c ESP: c09e29bc
 DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068
 CR0: 8005003b CR2: cf432070 CR3: 0eff0000 CR4: 000006d0
 DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
 DR6: ffff4ff0 DR7: 00000400
 [<c046fe62>] sys_delete_module+0×152/0×1f0
 [<c0403b14>] sysenter_do_call+0×12/0×28
 [<ffffffff>] 0xffffffff

对(5) 的分析可以参考清单 6。
3. 本例中(完整代码请参阅附件 1 中的 kmemchk_free.c),我们先用 kmalloc 分配内存,完毕后释放该内存,然后再去访问被释放了的内存空间,我们会发现 kmemcheck 会发出访问内存已释放警告信息(即 KMEMCHECK_SHADOW_FREED 类型的错误信息)

(6). Freed 警告日志
    

 static int __init kmemchk_freed_init(void)
 {
        …
        /* using kmalloc to allocate 4KB memory without GFP_NOTRACK flag*/
        kmalloc_base = kmalloc(4*1024*1024,GFP_KERNEL); 
        …
 }

 static void __exit kmemchk_freed_exit(void)
 {
        int offset; /* offset to the kmalloc base address */
       
        if(kmalloc_base) {
          kfree(kmalloc_base); /* free the memory */
          offset = 220;
        printk(”checkpoint:access mem -base: %p offset: %d \n\n”,kmalloc_base,offset);
         if(*(kmalloc_base + offset) == ‘a’) /*access freed memory */
            printk(”You hit a random char \n”);
        }

 }

该模块在卸载时会出现如下 kmemcheck 的警告信息

 Freed 警告日志
    
 WARNING: kmemcheck: Caught 8-bit read from unallocated memory (cef2c624)
 53050000000604a8040000103331000008070b7c0500000ef5440000070cfc00
 a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a
         ^
 Pid: 1490, comm: bash Tainted: G      D W  (2.6.31.1 #2) V71
 EIP: 0060:[<d0aca04e>] EFLAGS: 00010286 CPU: 0
 EIP is at 0xd0aca04e
 EAX: cef2cc00 EBX: d0aca000 ECX: 00000000 EDX: c1001000
 ESI: 00000000 EDI: b7efffc0 EBP: cf2a0f5c ESP: c09e2ae0
 DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068
 CR0: 8005003b CR2: 08100118 CR3: 0ec01000 CR4: 000006d0
 DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
 DR6: ffff4ff0 DR7: 00000400
 [<c0401123>] do_one_initcall+0×23/0×180
 [<c0471b71>] sys_init_module+0xb1/0×1f0
 [<c0403b14>] sysenter_do_call+0×12/0×28
 [<ffffffff>] 0xffffffff

在该例中,由于 kmalloc 是基于 slab cache 机制的,对一个对象的释放不会马上释放该对象所在的页面,因而会出现非法访问警告信息(但是如果对象所在的数据页面也被释放了,则不会出现警告,因为影子页面也同时被释放了)。

相关文章

Comments

One Response to “Linux内核检测内存泄漏的工具(kmemcheck实例)”

  1. 好名字要隐藏 on 十一月 30th, 2009 12:31

    你博客底部的安全认证,真牛。

Leave a Reply