Solaris 模块调试器指南

高级内存分析

本节介绍用于执行高级内存分析(包括查找内存泄漏和数据损坏原因)的工具。

查找内存泄漏

对于启用了完整 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 结构的内部。

使用 ::kmem_verify 查找损坏的缓冲区

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_AUDITKMF_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 记录,可以了解特定线程最近的活动。