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 |