Solaris中检测内存异常访问
By judy on 十一月 30, 2007
本文介绍了在solaris中如何利用核心内存分配的调试功能检测内存异常(corruption)。引起内存异常的常见操作包括:
核心缓存(Kernel Memory Cache)
首先回忆一下,为了发现内存泄漏运行mdb的::findleaks其输出为:
第一列是发生了内存泄漏的cache地址。solaris的核心内存分配机制把内存分成若干cache。每一cache由一组固定大小的buffer组成。 kmem_alloc(9F)或kmem_zalloc(9F)将从cache中获得所需内存。cache由数据结构kmem_cache_t (kmem_impl.h)定义。拿前文中的核心core文件做例子,用mdb的::kmastat命令看一下核心中有哪些cache。
其中,cache的名字kmem_alloc_后面的数字是该cache中buffer的大小。如kmem_alloc_8表示这个cache中的 buffer大小是8个字节。接下来我们用::kmem_cache命令简要查看上文中产生了内存泄漏的cache(也可以用宏%CONTENT%lt; kmem_cache打印数据结构kmem_cache_t)。
mdb的::walk freemem和::walk kmem可分别用来查看chane的空闲和被占用的缓冲。
空闲缓冲(0xdeadbeef)
随便查看一个空闲缓冲的内容
缓冲的内容并不是0,而是0xdeedbeef。当缓冲被释放后,其内容会被清成0xdeedbeef。这样,用户就可以很容易地判断出访问的是否是一个已经被释放的内存。
已分配缓冲(0xbaddcafe)
再随便查看一个被占用的缓冲
一个特殊字段"bb"紧跟在实际要求分配的内存的后面。注意上文中的"baddcabb"而不是"bbddcafe",这是由于x86系统是little endian的系统造成的。
Redzone (0xfeedface)
空闲缓冲和被占用缓冲有一个共同字段0xfeedface。0xfeedface是Redzone的标志。它标识了一个buffer的边界。这里所说的边界和上文bb标识的边界不同。bb表示的是用户请求分配的内存边界,而0xfeedface表示的是整个buffer的边界。0xfeedface和bb 都可用来判断是否有内存越界访问。紧跟Redzone的是一些调试数据,这些数据和redzone一起统称为buftag区(如下图所示)。当一个 cache的KMF_AUDIT、KMF_DEADBEEF或KMF_REDZONE标志位被设,buftag区就会被加到这个cache的每一 buffer后面。
则在上述例子中
注意,x86系统是little endian的。
bufctl 指针
Debugging Data中的两个指针,前一个是指向bufctl的bcp指针,后一个是bxstat指针。bxstat用于校验bcp指针的有效性。对于以分配的缓冲, bcp XOR bxstat = 0xa110c8ed(allocated);而对于已释放的缓冲,bcp XOR bxstat = 0xf4eef4ee(freefree)。同样在上面的例子中
当kmem_flags的KMF_AUDIT位被设置后,bcp指针指向一个kmem_bufctl_audit_t结构。该结构包含使该缓冲在 allocated和freed状态之间转换的操作的详细信息。
- 越界访问
- 访问未被初始化的数据
- 访问已被释放的内存
核心缓存(Kernel Memory Cache)
首先回忆一下,为了发现内存泄漏运行mdb的::findleaks其输出为:
> ::findleaks
CACHE | LEAKED | BUFCTL | CALLER |
... ... | |||
dac32030 | 1 | d4ec7748 | tleak_open+0x35 |
第一列是发生了内存泄漏的cache地址。solaris的核心内存分配机制把内存分成若干cache。每一cache由一组固定大小的buffer组成。 kmem_alloc(9F)或kmem_zalloc(9F)将从cache中获得所需内存。cache由数据结构kmem_cache_t (kmem_impl.h)定义。拿前文中的核心core文件做例子,用mdb的::kmastat命令看一下核心中有哪些cache。
> ::kmastat
cache name | buf size | buf in use | buf total | memory in use | alloc succeed | alloc fail |
---------- | ------ | ------- | ------- | -------- | ------ | ---- |
... ... ... | ||||||
kmem_alloc_8 | 8 | 110939 | 111010 | 2674688 | 205353 | 0 |
kmem_alloc_16 | 16 | 59421 | 59520 | 1904640 | 91402 | 0 |
kmem_alloc_24 | 24 | 25723 | 25806 | 1036288 | 79258 | 0 |
kmem_alloc_32 | 32 | 10552 | 10625 | 512000 | 28811 | 0 |
kmem_alloc_40 | 40 | 4288 | 4380 | 245760 | 17876 | 0 |
kmem_alloc_48 | 48 | 52219 | 52224 | 3342336 | 64754 | 0 |
kmem_alloc_56 | 56 | 653 | 672 | 49152 | 4127 | 0 |
kmem_alloc_64 | 64 | 337 | 352 | 45056 | 47603 | 0 |
kmem_alloc_80 | 80 | 50732 | 50736 | 4947968 | 60466 | 0 |
kmem_alloc_96 | 96 | 120 | 144 | 16384 | 1122 | 0 |
kmem_alloc_112 | 112 | 163 | 192 | 24576 | 1363 | 0 |
... ... ... |
其中,cache的名字kmem_alloc_后面的数字是该cache中buffer的大小。如kmem_alloc_8表示这个cache中的 buffer大小是8个字节。接下来我们用::kmem_cache命令简要查看上文中产生了内存泄漏的cache(也可以用宏%CONTENT%lt; kmem_cache打印数据结构kmem_cache_t)。
> dac32030::kmem_cache
其中重要的字段是name、bufsize和flag。从name和bufsize可以看出缓冲大小是112字节。flag的值定义在 kmem_impl.h中。0x20f表示(KMF_HASH | KMF_AUDIT | KMF_DEADBEEF | KMF_REDZONE | KMF_CONTENTS)。ADDR | NAME | FLAG | CFLAG | BUFSIZE | BUFTOTL |
dac32030 | kmem_alloc_112 | 020f | 200000 | 112 | 192 |
mdb的::walk freemem和::walk kmem可分别用来查看chane的空闲和被占用的缓冲。
> dac32030::walk freemem
d7c91980
d7c91900
d4db0280
d4f05900
d4f05880
... ...
> dac32030::walk kmem
d4db0000
d4db0080
d4db0100
d4db0180
... ...
d7c91980
d7c91900
d4db0280
d4f05900
d4f05880
... ...
> dac32030::walk kmem
d4db0000
d4db0080
d4db0100
d4db0180
... ...
空闲缓冲(0xdeadbeef)
随便查看一个空闲缓冲的内容
> d7c91980/32X
0xd7c91980: | deadbeef | deadbeef | deadbeef | deadbeef |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
deadbeef | deadbeef | deadbeef | deadbeef | |
feedface | feedface | d7c9dbf8 | 23272f16 |
缓冲的内容并不是0,而是0xdeedbeef。当缓冲被释放后,其内容会被清成0xdeedbeef。这样,用户就可以很容易地判断出访问的是否是一个已经被释放的内存。
已分配缓冲(0xbaddcafe)
再随便查看一个被占用的缓冲
> d4db0000/32X
缓冲的内容被初始化成0xbaddcafe。根据这个特殊的0xbaddcafe,用户可以判断出是否访问了未被初始化的内存。0xd4db0000: | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | |
0 | 0 | 0 | 0 | |
0 | 0 | d5077bc0 | 0 | |
0 | 0 | d20c2df8 | 0 | |
d20c2dd8 | d20c2dd8 | d20c2e00 | f5f00 | |
0 | 0 | baddcabb | baddcafe | |
feedface | 65f9 | d4ec7a18 | 75fcb2f5 |
一个特殊字段"bb"紧跟在实际要求分配的内存的后面。注意上文中的"baddcabb"而不是"bbddcafe",这是由于x86系统是little endian的系统造成的。
Redzone (0xfeedface)
空闲缓冲和被占用缓冲有一个共同字段0xfeedface。0xfeedface是Redzone的标志。它标识了一个buffer的边界。这里所说的边界和上文bb标识的边界不同。bb表示的是用户请求分配的内存边界,而0xfeedface表示的是整个buffer的边界。0xfeedface和bb 都可用来判断是否有内存越界访问。紧跟Redzone的是一些调试数据,这些数据和redzone一起统称为buftag区(如下图所示)。当一个 cache的KMF_AUDIT、KMF_DEADBEEF或KMF_REDZONE标志位被设,buftag区就会被加到这个cache的每一 buffer后面。
|<------------------------ buffer ----------------------->|<---------- buftag ----------->|
|<------------------- cache_bufsize字节 ------------------->|<--- 64位 ---->|<--- 2个指针 -->|
以kmem_alloc_8中的一段内存为例打印其内容User Data | bb | Unallocated | RedZone | Debugging Data |
RedZone: |
| ||
Debugging Data: |
|
> dec82b18,6/2Xna
RedZone的0xfeedface后面是经过编码的用户实际使用的缓冲大小,其计算方法是:0xdec82b18: | 75746572 | bb006e72 | -- User Data |
0xdec82b20: | feedface | 6de | -- RedZone |
0xdec82b28: | decd3150 | 7fddf9bd | -- Debugging Data |
0xdec82b30: | 73666e | baddcabb | -- User Data |
0xdec82b38: | feedface | 3ed | -- RedZone |
0xdec82b40: | decd30d8 | 7fddf835 | -- Debugging Data |
size = redzone_value / 251
则在上述例子中
size = 0x6de / 251 = 7 字节
注意,x86系统是little endian的。
bufctl 指针
Debugging Data中的两个指针,前一个是指向bufctl的bcp指针,后一个是bxstat指针。bxstat用于校验bcp指针的有效性。对于以分配的缓冲, bcp XOR bxstat = 0xa110c8ed(allocated);而对于已释放的缓冲,bcp XOR bxstat = 0xf4eef4ee(freefree)。同样在上面的例子中
decd3150 /^ 7fddf9bd = a110c8ed
当kmem_flags的KMF_AUDIT位被设置后,bcp指针指向一个kmem_bufctl_audit_t结构。该结构包含使该缓冲在 allocated和freed状态之间转换的操作的详细信息。
>decd3150%CONTENT%lt;bufctl_audit
ADDR | BUFADDR | TIMESTAMP | THREAD |
CACHE | LASTLOG | CONTENTS | |
decd3150 | dec82b18 | 8f2a260f73 | d5079980 |
dac2c030 | db00cfc0 | 0 | |
kmem_cache_alloc_debug+0x256 | |||
kmem_cache_alloc+0x1ac | |||
kmem_zalloc+0x4b | |||
dtrace_strdup+0x21 | |||
dtrace_probe_create+0x99 | |||
fbt_provide_module+0x306 |