本文介绍了在solaris中如何利用核心内存分配的调试功能检测内存异常(corruption)。引起内存异常的常见操作包括:
- 越界访问
- 访问未被初始化的数据
- 访问已被释放的内存
我们用前一篇blog中生成的核心core文件为例,一步步进行分析。
核心缓存(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
ADDR
|
NAME
|
FLAG
|
CFLAG
|
BUFSIZE
|
BUFTOTL
|
dac32030
|
kmem_alloc_112
|
020f
|
200000
|
112
|
192
|
其
中重要的字段是name、bufsize和flag。从name和bufsize可以看出缓冲大小是112字节。flag的值定义在
kmem_impl.h中。0x20f表示(KMF_HASH | KMF_AUDIT | KMF_DEADBEEF | KMF_REDZONE
| KMF_CONTENTS)。
mdb的::walk freemem和::walk kmem可分别用来查看chane的空闲和被占用的缓冲。
> dac32030::walk freemem
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
| 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 |
缓冲的内容被初始化成0xbaddcafe。根据这个特殊的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后面。
|<------------------------ buffer ----------------------->|<---------- buftag ----------->|
User Data
|
bb
|
Unallocated |
RedZone
|
Debugging Data
|
|<------------------- cache_bufsize字节 ------------------->|<--- 64位 ---->|<--- 2个指针 -->|
RedZone:
|
User data size = encoded_index / 251 字节
|
Debugging Data:
|
bcp pointer ^ bxstat pointer = a110c8ed | f4eef4ee
|
以kmem_alloc_8中的一段内存为例打印其内容
> dec82b18,6/2Xna
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
|
RedZone的0xfeedface后面是经过编码的用户实际使用的缓冲大小,其计算方法是:
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
|
|
Posted at 10:33上午 十一月 30, 2007
by judy in Solaris |
|