本节介绍用于执行高级内存分析(包括查找内存泄漏和数据损坏原因)的工具。
对于启用了完整 kmem 调试功能集的内核崩溃转储,::findleaks dcmd 提供了提供了强大高效的内存泄漏检测。 首次执行 ::findleaks 时会处理转储中的内存泄漏(这可能需要几分钟),然后根据分配栈跟踪合并泄漏。 findleaks 报告会针对所识别的每个内存泄漏显示一个 bufctl 地址和最顶层的栈帧:
> ::findleaks CACHE LEAKED BUFCTL CALLER 70039ba8 1 703746c0 pm_autoconfig+0x708 70039ba8 1 703748a0 pm_autoconfig+0x708 7003a028 1 70d3b1a0 sigaddq+0x108 7003c7a8 1 70515200 pm_ioctl+0x187c ------------------------------------------------------ Total 4 buffers, 376 bytes
使用 bufctl 指针可以通过应用 bufctl_audit 宏获取分配的完整栈反向跟踪:
> 70d3b1a0$<bufctl_audit 0x70d3b1a0: next addr slab 70a049c0 70d03b28 70bb7480 0x70d3b1ac: cache timestamp thread 7003a028 13f7cf63b3 70b38380 0x70d3b1bc: lastlog contents stackdepth 700d6e60 0 5 0x70d3b1c8: kmem_alloc+0x30 sigaddq+0x108 sigsendproc+0x210 sigqkill+0x90 kill+0x28
程序员通常可以使用 bufctl_audit 信息和分配栈跟踪快速找到泄漏给定缓冲区的代码路径。
尝试诊断内存损坏问题时,应该知道其他哪些内核实体包含特定指针的副本。这一点非常重要,因为这可以表明哪个线程在被释放后访问了数据结构。 另外,还可以更轻松地了解哪些内核实体正在共享特定(有效)数据项的信息。 ::whatis 和 ::kgrep dcmd 可以用于回答这些问题。 可以对相关值应用 ::whatis:
> 0x705d8640::whatis 705d8640 is 705d8640+0, allocated from streams_mblk
在本示例中,表明 0x705d8640 是指向 STREAMS mblk 结构的指针。 要查看整个分配树,请改用 ::whatis -a:
> 0x705d8640::whatis -a 705d8640 is 705d8640+0, allocated from streams_mblk 705d8640 is 705d8000+640, allocated from kmem_va_8192 705d8640 is 705d8000+640 from kmem_default vmem arena 705d8640 is 705d2000+2640 from kmem_va vmem arena 705d8640 is 705d2000+2640 from heap vmem arena
这表明分配也会在 kmem_va_8192 高速缓存(即面向 kmem_va vmem 块的 kmem 高速缓存)中进行。 它还显示了 vmem 分配的完整栈。
kmem 高速缓存和 vmem 块的完整列表通过 ::kmastat dcmd 显示。 可以使用 ::kgrep 查找包含指向此 mblk 的指针的其他内核地址。这说明了系统中内存分配的分层性质;通常,可以根据最具体 kmem 高速缓存的名称确定给定地址所引用的对象类型。
> 0x705d8640::kgrep 400a3720 70580d24 7069d7f0 706a37ec 706add34
并通过再次应用 ::whatis 对其进行检查:
> 400a3720::whatis 400a3720 is in thread 7095b240's stack > 706add34::whatis 706add34 is 706add20+14, allocated from streams_dblk_120
在这里一个指针位于已知内核线程的栈上,另一个指针 mblk 位于对应的 STREAMS dblk 结构的内部。
MDB 的 ::kmem_verify dcmd 在运行时可实现大多数 kmem 分配器实现的检查。可以调用 ::kmem_verify 以便扫描每个具有相应 kmem_flags 的 kmem 内存,或者检查特定的高速缓存。
以下是使用 ::kmem_verify 确定问题的示例:
> ::kmem_verify Cache Name Addr Cache Integrity kmem_alloc_8 70039428 clean kmem_alloc_16 700396a8 clean kmem_alloc_24 70039928 1 corrupt buffer kmem_alloc_32 70039ba8 clean kmem_alloc_40 7003a028 clean kmem_alloc_48 7003a2a8 clean ...
在这里可以很容易地看到 kmem_alloc_24 高速缓存包含 ::kmem_verify 所确认的问题。使用显式的高速缓存参数,::kmem_verify dcmd 可以提供有关该问题的更详细信息:
> 70039928::kmem_verify Summary for cache 'kmem_alloc_24' buffer 702babc0 (free) seems corrupted, at 702babc0
下一步是检查 ::kmem_verify 确认已损坏的缓冲区:
> 0x702babc0,5/KKn 0x702babc0: 0 deadbeef deadbeef deadbeef deadbeef deadbeef feedface feedface 703785a0 84d9714e
::kmem_verify 标记此缓冲区的原因此时非常明显:缓冲区中的第一个字(位于 0x702babc0)可能应该使用 0xdeadbeef 模式而不是 0 填充。 此时,为此缓冲区检查 bufctl_audit 可能产生有关最近向缓冲区写入了哪些代码的线索,指明释放此缓冲区的位置和时间。
此情况下的另一种有用方法是使用 ::kgrep 在地址空间中搜索对地址 0x702babc0 的引用,以便发现哪些线程或数据仍然包含对此已释放数据的引用。
如果为高速缓存设置了 KMF_AUDIT,则内核内存分配器会对记录其最近活动历史的日志进行维护。此事务日志记录的是 bufctl_audit 记录。如果同时设置了 KMF_AUDIT 和 KMF_CONTENTS 标志,则分配器会生成一个内容日志,其中记录了已分配和已释放缓冲区的部分实际内容。 内容日志的结构和用法不在本文档的讨论范围之内。本节将讨论事务日志。
MDB 提供了用于显示事务日志的多种工具。最简单的工具是 ::walk kmem_log,用于将日志中的事务作为一系列 bufctl_audit_t 指针进行列显:
> ::walk kmem_log 70128340 701282e0 70128280 70128220 701281c0 ... > 70128340$<bufctl_audit 0x70128340: next addr slab 70ac1d40 70bc4ea8 70bb7c00 0x7012834c: cache timestamp thread 70039428 e1bd7abe721 70aacde0 0x7012835c: lastlog contents stackdepth 701282e0 7018f340 4 0x70128368: kmem_cache_free+0x24 nfs3_sync+0x3c vfs_sync+0x84 syssync+4
查看整个事务日志的更好方法是使用 ::kmem_log 命令:
> ::kmem_log CPU ADDR BUFADDR TIMESTAMP THREAD 0 70128340 70bc4ea8 e1bd7abe721 70aacde0 0 701282e0 70bc4ea8 e1bd7aa86fa 70aacde0 0 70128280 70bc4ea8 e1bd7aa27dd 70aacde0 0 70128220 70bc4ea8 e1bd7a98a6e 70aacde0 0 701281c0 70d03738 e1bd7a8e3e0 70aacde0 ... 0 70127140 70cf78a0 e1bd78035ad 70aacde0 0 701270e0 709cf6c0 e1bd6d2573a 40033e60 0 70127080 70cedf20 e1bd6d1e984 40033e60 0 70127020 70b09578 e1bd5fc1791 40033e60 0 70126fc0 70cf78a0 e1bd5fb6b5a 40033e60 0 70126f60 705ed388 e1bd5fb080d 40033e60 0 70126f00 705ed388 e1bd551ff73 70aacde0 ...
::kmem_log 的输出按时间标记进行降序排列。ADDR 列是对应于该事务的 bufctl_audit 结构;BUFADDR 指向实际缓冲区。
这些数字表示(分配和释放的)缓冲区中的事务。如果特定的缓冲区损坏,则在事务日志中找到该缓冲区,然后确定在其他哪些事务中涉及执行事务的线程可能会有所帮助。 这有助于对分配(或释放)缓冲区前后所发生的事件的顺序有一个完整的了解。
可以使用 ::bufctl 命令过滤遍历事务日志的输出。 ::bufctl -a 命令用于按缓冲区地址过滤事务日志中的缓冲区。 以下是对缓冲区 0x70b09578 进行过滤的示例:
> ::walk kmem_log | ::bufctl -a 0x70b09578 ADDR BUFADDR TIMESTAMP THREAD CALLER 70127020 70b09578 e1bd5fc1791 40033e60 biodone+0x108 70126e40 70b09578 e1bd55062da 70aacde0 pageio_setup+0x268 70126de0 70b09578 e1bd52b2317 40033e60 biodone+0x108 70126c00 70b09578 e1bd497ee8e 70aacde0 pageio_setup+0x268 70120480 70b09578 e1bd21c5e2a 70aacde0 elfexec+0x9f0 70120060 70b09578 e1bd20f5ab5 70aacde0 getelfhead+0x100 7011ef20 70b09578 e1bd1e9a1dd 70aacde0 ufs_getpage_miss+0x354 7011d720 70b09578 e1bd1170dc4 70aacde0 pageio_setup+0x268 70117d80 70b09578 e1bcff6ff27 70bc2480 elfexec+0x9f0 70117960 70b09578 e1bcfea4a9f 70bc2480 getelfhead+0x100 ...
本示例说明一个特定的缓冲区可以在许多事务中使用。
请记住,kmem 事务日志是内核内存分配器生成的事务不完整记录。会根据需要删除日志中的较旧项,以便保持日志大小不变。
::allocdby 和 ::freedby dcmd 提供了汇总与特定线程关联的事务的便利方法。 以下示例列出了线程 0x70aacde0 最近执行的分配:
> 0x70aacde0::allocdby BUFCTL TIMESTAMP CALLER 70d4d8c0 e1edb14511a allocb+0x88 70d4e8a0 e1edb142472 dblk_constructor+0xc 70d4a240 e1edb13dd4f allocb+0x88 70d4e840 e1edb13aeec dblk_constructor+0xc 70d4d860 e1ed8344071 allocb+0x88 70d4e7e0 e1ed8342536 dblk_constructor+0xc 70d4a1e0 e1ed82b3a3c allocb+0x88 70a53f80 e1ed82b0b91 dblk_constructor+0xc 70d4d800 e1e9b663b92 allocb+0x88
通过检查 bufctl_audit 记录,可以了解特定线程最近的活动。