Solaris 模块调试器指南

检测内存损坏

分配器的主要调试功能之一是它包括了用于快速识别数据损坏的算法。当检测到损坏时,分配器会导致系统立即出现故障。

本节介绍分配器如何识别数据损坏;您必须了解这一点才能调试这些问题。内存误用通常分为以下种类:

阅读接下来的三节时,请牢记这些问题。 它们将有助于了解分配器的设计,并使您可以更有效地诊断问题。

检查已释放的缓冲区: 0xdeadbeef

如果在 kmem_cache 的 flags 字段中设置了 KMF_DEADBEEF (0x2) 位,则分配器会尝试通过将特殊模式写入所有已释放缓冲区中,从而使内存损坏易于检测。 此模式即是 0xdeadbeef。 由于典型的内存区域同时包含已分配的内存和已释放的内存,因此每种块的各个节将是分散的;以下是来自 "kmem_alloc_24" 高速缓存的一个示例:

0x70a9add8:     deadbeef        deadbeef

0x70a9ade0:     deadbeef        deadbeef

0x70a9ade8:     deadbeef        deadbeef

0x70a9adf0:     feedface        feedface

0x70a9adf8:     70ae3260        8440c68e

0x70a9ae00:     5               4ef83

0x70a9ae08:     0               0

0x70a9ae10:     1               bbddcafe

0x70a9ae18:     feedface        139d

0x70a9ae20:     70ae3200        d1befaed

0x70a9ae28:     deadbeef        deadbeef

0x70a9ae30:     deadbeef        deadbeef

0x70a9ae38:     deadbeef        deadbeef

0x70a9ae40:     feedface        feedface

0x70a9ae48:     70ae31a0        8440c54e

0x70a9add8 开始的缓冲区是使用 0xdeadbeef 模式填充的,这就直接表明了缓冲区当前是空闲的。 另一个空闲缓冲区从 0x70a9ae28 开始;一个已分配的缓冲区介于它们之间,位于 0x70a9ae00


注意 –

您可能已经注意到此内存区域布局中有一些空洞,3 个 24 字节区域应该仅占用 72 字节的内存,而不是此处显示的 120 字节。 此差异将在下一节Redzone(禁区): 0xfeedface中进行说明。


Redzone(禁区): 0xfeedface

模式 0xfeedface 频繁地在以上缓冲区中出现。 此模式称为 "redzone" 指示器。 通过此模式,分配器(以及调试问题的程序员)可以确定“错误”代码是否超出了缓冲区的边界。 redzone 后面是一些其他信息。 该数据的内容取决于其他因素(请参见内存分配日志记录)。 redzone 及其后缀统称为 buftag 区域。 图 9–1 概述了此信息。

图 9–1 Redzone

通过上下文说明的图形。

如果在高速缓存中设置了 KMF_AUDITKMF_DEADBEEFKMF_REDZONE 标志中的任何一个,则 buftag 会附加到该高速缓存的每个缓冲区。 buftag 的内容取决于是否设置了 KMF_AUDIT

现在,可以轻易将上述内存区域分解为不同的缓冲区:

0x70a9add8:     deadbeef        deadbeef  \

0x70a9ade0:     deadbeef        deadbeef   +- User Data (free)

0x70a9ade8:     deadbeef        deadbeef  /

0x70a9adf0:     feedface        feedface  -- REDZONE

0x70a9adf8:     70ae3260        8440c68e  -- Debugging Data



0x70a9ae00:     5               4ef83     \

0x70a9ae08:     0               0          +- User Data (allocated)

0x70a9ae10:     1               bbddcafe  /

0x70a9ae18:     feedface        139d    -- REDZONE

0x70a9ae20:     70ae3200        d1befaed  -- Debugging Data



0x70a9ae28:     deadbeef        deadbeef  \

0x70a9ae30:     deadbeef        deadbeef   +- User Data (free)

0x70a9ae38:     deadbeef        deadbeef  /

0x70a9ae40:     feedface        feedface  -- REDZONE

0x70a9ae48:     70ae31a0        8440c54e  -- Debugging Data

在位于 0x70a9add80x70a9ae28 的空闲缓冲区中,redzone 是使用 0xfeedfacefeedface 填充的。这是确定缓冲区是否空闲的便利方法。

在从 0x70a9ae00 开始的已分配缓冲区中,情况是不同的。请回想一下分配器基础知识,有两种分配类型:

1) 客户机使用 kmem_cache_alloc() 请求的内存,在这种情况下所请求缓冲区的大小等于高速缓存的 bufsize。

2) 客户机使用 kmem_alloc (9F) 请求的内存,在这种情况下所请求缓冲区的大小小于或等于高速缓存的 bufsize。 例如,对 20 个字节的请求将从 kmem_alloc_24 高速缓存得到满足。 分配器会通过在紧邻客户机数据之后放置一个标记(即 redzone 字节)来强制设置缓冲区边界:

0x70a9ae00:     5               4ef83     \

0x70a9ae08:     0               0          +- User Data (allocated)

0x70a9ae10:     1               bbddcafe  /

0x70a9ae18:     feedface        139d    -- REDZONE

0x70a9ae20:     70ae3200        d1befaed  -- Debugging Data

位于 0x70a9ae180xfeedface 后跟一个 32 位的字,其中包含的内容看似一个随机值。 此数字实际上是缓冲区大小的编码表示形式。 要对此数字进行解码并获得已分配缓冲区的大小,请使用以下公式:

size = redzone_value / 251

因此,在本示例中,

size = 0x139d / 251 = 20 bytes.

这表明所请求缓冲区的大小为 20 个字节。 分配器将执行此解码操作,同时会发现 redzone 字节应该位于偏移量为 20 的位置。redzone 字节是十六进制模式 0xbb,如预期的那样存在于 0x729084e4 (0x729084d0 + 0t20)

图 9–2 kmem_alloc(9F) 缓冲区样例

此图形描述 kmem_alloc 缓冲区样例。redzone 字节、未初始化的数据和调试数据均已标记。

图 9–3 说明了此内存布局的常规形式。

图 9–3 Redzone 字节

此图形说明了在用户数据区域结尾后面写入的 redzone 字节。redzone 字节是通过解码索引确定的。

如果分配大小等于高速缓存的 bufsize,则 redzone 字节会覆写 redzone 本身的第一个字节,如图 9–4 中所示。

图 9–4 Redzone 开头的 Redzone 字节

通过上下文说明的图形。

此覆写操作会导致 redzone 的第一个 32 位字为 0xbbedface0xfeedfabb,具体取决于系统运行的硬件的字节存储顺序。


注意 –

为什么分配大小以该方式进行编码? 要对大小进行编码,分配器可使用公式(251 * 大小 + 1)。对大小进行解码时,整数除法将废弃余数 '+1'。 但是,加上 1 是有价值的,因为分配器可以通过测试(大小 % 251 == 1)是否成立来检查大小是否有效。 这样,分配器可防止损坏 redzone 字节索引。


未初始化的数据: 0xbaddcafe

您可能想知道在用 redzone 字节覆写字中的第一个字节之前,地址 0x729084d4 上的可疑 0xbbddcafe 是什么。 它是 0xbaddcafe。如果在高速缓存中设置了 KMF_DEADBEEF 标志,则使用 0xbaddcafe 模式填充已分配但未初始化的内存。 分配器执行分配时,会循环通过缓冲区的各个字并验证每个字是否包含 0xdeadbeef,然后使用 0xbaddcafe 填充该字。

系统可能会发出以下故障消息:

panic[cpu1]/thread=e1979420: BAD TRAP: type=e (Page Fault)

rp=ef641e88 addr=baddcafe occurred in module "unix" due to an

illegal access to a user address

在这种情况下,导致故障的地址是 0xbaddcafe: 出现故障的线程访问了一些从未初始化的数据。

将故障消息与失败关联

内核内存分配器会对应于之前所述的失败模式发出故障消息。 例如,系统可能会发出以下故障消息:

kernel memory allocator: buffer modified after being freed

modification occurred at offset 0x30

由于分配器会尝试验证是否使用 0xdeadbeef 填充了不确定的缓冲区,因此能够检测到此情况。如果偏移的位置为 0x30,则不符合此条件。 由于此条件表明内存损坏,因此分配器会导致系统出现故障。

以下是另一个故障消息示例:

kernel memory allocator: redzone violation: write past end of buffer

由于分配器会尝试验证 redzone 字节 (0xbb) 是否处于它通过 redzone 大小编码所确定的位置,因此能够检测到此情况。 它无法在正确的位置找到签名字节。 由于这表明内存损坏,因此分配器会导致系统出现紧急情况。 其他的分配器故障消息将在稍后讨论。