關於glibc中的記憶體配置管理

來源:互聯網
上載者:User

轉自:http://blog.chinaunix.net/u/30686/showart_265092.html

發表於: 2007-03-26 ,修改於: 2007-04-04

在glibc中,使用malloc分配記憶體時,實際上glibc自己做了相應的堆管理,它先使用brk系統調用,擴充了記憶體空間,一次最少一個頁面4k。進程的堆,並不是直接建立在Linux的核心的記憶體配置策略上的,而是建立在glibc的堆管理原則上的(也就是glibc的動態記憶體分配策略上),堆的管理是由glibc進行的。
所以我們調用free對malloc得到的記憶體進行釋放的時候,並不是直接釋放給作業系統,而是還給了glibc的堆管理實體,而glibc會在把實際的實體記憶體歸還給系統的策略上做一些最佳化,以便最佳化使用者任務的動態記憶體分配過程。
malloc/calloc/realloc/free這幾個函數,是用來分配或釋放動態記憶體的.
目前很多Linux系統所用的malloc實現(包括libc5和glibc)都是由Doug Lea完成的.我們下面所講的,都是指這一版本的實現.(需要注意的是目前高版本的glibc中的malloc實現是用了ptmalloc,比如RH8.0以上的版本中的glibc而不是我現在說的Doug Lea的版本,其中具體的區別等會再說,因為原理還是差不多的)
從Linux的Man手冊MALLOC(3)中看到這些函數原型如下:
void *calloc(size_t nmemb, size_t size);
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
calloc()用來分配nmemb個size大小的記憶體塊,並返回一個可用記憶體地址.
它會自動將得到的記憶體塊全部清零.

malloc()用來分配size大小的記憶體塊,並返回一個可用記憶體地址.
free()釋放ptr所指向的記憶體.

realloc()用來將ptr指向的一塊記憶體的大小改變為size.
我們需要注意的是free()和realloc()函數.如果所提供的地址指標ptr所指向的記憶體是已經釋放的,或者不是由malloc類函數分配的話,就可能發生不可預料的情況.我們要利用的,也就是這些"不可預料"的情況.我們可以通過種種手段使其變為我們自己可以"預料"的,當然我們的目的還是為了拿Shell.
由於calloc()和malloc()差別不大,實際上都是調用的chunk_alloc()函數來進行分配的,區別只是calloc()在最後調用了一個宏 MALLOC_ZERO來將分配
的記憶體塊清零.因此後面除非特別指出,我們就只以malloc()為例.
malloc()定義了一個內部結構malloc_chunk來定義malloc分配或釋放的記憶體塊.
struct malloc_chunk
{
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
};
prev_size是上一個塊的大小,只在上一個塊閒置情況下才被填充
size是當前塊的大小,它包括prev_size和size成員的大小(8位元組)
fd是雙向鏈表的向前指標,指向下一個塊.這個成員只在空閑塊中使用
bk是雙向鏈表的向後指標,指向上一個塊.這個成員只在空閑塊中使用
對於已指派的記憶體,除了分配使用者指定大小的記憶體空間外,還在前面增加了
malloc_chunk結構的前兩個成員(8位元組).一段已指派的記憶體結構如所示:
0 16 32
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 上一個塊的位元組數(如果上一個塊閒置話) | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 當前塊的位元組數 (size) |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 使用者資料開始... .
. .
. (使用者可以用空間大小) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
這裡chunk指標是malloc()在內部使用的,而返回給使用者的是mem指標(chunk + 8),實際上向使用者隱藏了一個內部結構.也就是說,如果使用者要求分配size位元組記憶體,實際上至少分配size+8位元組,只是使用者可用的就是size位元組(這裡先不考慮對齊問題).nextchunk指向下一個記憶體塊.
對於空閑(或者說已經釋放的)塊,是存放在一個雙向迴圈鏈表(參見上面的
malloc_chunk結構)中的.在記憶體中的分布基本如所示:
0 16 32
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 上一個塊的位元組數(prev_size) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | 當前塊的位元組數 (size) |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 前指標(指向鏈表中的下一個塊) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 後指標(指向鏈表中的上一個塊) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 未被雙向鏈表使用的空間(也可能是0位元組長) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | 上一個塊的位元組數 (等於chunk->size) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
大家可能主要到兩個表中都有一個"P"標誌,它是"當前塊位元組數"(chunk->size)中的最低一位,表示是否上一塊正在被使用.如果P位置一,則表示上一塊正在被使用,這時chunk->prev_size通常為零;如果P位清零,則表示上一塊是空閑塊,這是chunk->prev_size就會填充上一塊的長度."M"位是表示此記憶體塊是不是由mmap()分配的,如果置一,則是由mmap()分配的,那麼在釋放時會由munmap_chunk()去釋放;否則,釋放時由chunk_free()完成.
這兩位標誌相關定義為:
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
由於malloc實現中是8位元組對齊的,size的低3位總是不會被使用的,所以在實際計算chunk大小時,要去掉標誌位.例如:
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
一次malloc最小分配的長度至少為16位元組,例如malloc(0).(上面說的長度是指chunk的長度)
瞭解了上面這些基本概念,我們再來看看free(mem)時做了些什麼:
首先將mem轉換為chunk(mem-8),並調用chunk_free()來釋放chunk所指的記憶體塊.然後程式會檢查其相鄰(包括前後)的記憶體塊是不是閒置:如果是空閑塊的話,就將該相鄰塊從鏈表中摘除(unlink),然後將這些相鄰的空閑塊合并;
如果不是空閑塊的話,就只是設定後一個相鄰塊的prev_size和size( 清PREV_INUSE標誌).最後將得到的空閑塊加入到雙向鏈表中去.
在進行unlink操作時,實際上就是執行了一個鏈表結點的刪除工作.比如,如果要從鏈表中刪除chunk結點,所要做得就是:
chunk0->fd fd
chunk1->bk bk
如下所示:
chunk0 chunk chunk1
+----------------------+..+----------------------+..+----------------------+
|prev_size|size|*fd|*bk| |prev_size|size|*fd|*bk| |prev_size|size|*fd|*bk|
+----------------^-----+..+----------------+---+-+..+--------------------^-+
|_________________________| |_________________________|
malloc實現中是使用了一個unlink宏來完成這個操作的,定義如下:
/* take a chunk off a list */
#define unlink(P, BK, FD) /
{ /
BK = P->bk; /
FD = P->fd; /
FD->bk = BK; /
BK->fd = FD; /
}
這裡有兩個寫記憶體的操作.如果我們能夠覆蓋chunk->fd和chunk->bk的話,那麼chunk->fd就會寫到(chunk->bk + 8)這個地址,而chunk->bk就會被寫到(chunk->fd + 12)這個地址!也就是我們可以將任意4個位元組寫到任意一個記憶體位址中去!!我們就可能改變程式的流程,比如覆蓋函數返回地址,覆蓋GOT,.dtor結構等等,這不正是我們所要的嗎 free()和realloc()中都有unlink操作,因此我們要做的就是要想辦法用合適的值來覆蓋空閑塊結構中的*fd和*bk,並讓unlink能夠執行.
下面我們來看free()是怎麼工作的,注意下面的代碼為了說明的方便做了一些簡化:
void fREe(Void_t* mem)
{
...
(a) if (chunk_is_mmapped(p)) /* 如果IS_MMAPPED位被設定,則調用munmap_chunk() */
{
munmap_chunk(p);
return;
}
...
p = mem2chunk(mem); /* 將使用者地址轉換成內部地址: p = mem - 8 */
...
chunk_free(ar_ptr, p);
}

static void
internal_function
chunk_free(arena *ar_ptr, mchunkptr p)
{
INTERNAL_SIZE_T hd = p->size; /* hd是當前塊地址 */
INTERNAL_SIZE_T sz; /* 當前塊大小 */
INTERNAL_SIZE_T nextsz; /* 下一個塊大小 */
INTERNAL_SIZE_T prevsz; /* 上一個塊大小 */

...

check_inuse_chunk(ar_ptr, p);
sz = hd & ~PREV_INUSE; /* 取得當前塊的真實大小 */
next = chunk_at_offset(p, sz); /* 得到下一個塊的地址 */
nextsz = chunksize(next); /* 得到下一個塊的真實大小
* #define chunksize(p) ((p)->size & ~(SIZE_BITS))
*/
if (next == top(ar_ptr)) /* 如果下一個塊是頭結點,則與之合并 */
{
sz += nextsz;
(b) if (!(hd & PREV_INUSE)) /* 如果上一個塊是閒置,則與之合并*/
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -prevsz);
sz += prevsz;
unlink(p, bck, fwd); /* 從鏈表中刪除上一個結點 */
}
set_head(p, sz | PREV_INUSE);
top(ar_ptr) = p;
.....
}
/* 如果下一個塊不是頭結點 */
(b) if (!(hd & PREV_INUSE)) /* 如果上一個塊是閒置,則與之合并*/
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -prevsz);
sz += prevsz;
if (p->fd == last_remainder(ar_ptr)) /* keep as last_remainder */
islr = 1;
else
unlink(p, bck, fwd); /* 從鏈表中刪除上一個結點 */
}
/* 根據我的判斷,剛才的程式,是在進行這個檢查時發生段錯誤的 */
(c)if (!(inuse_bit_at_offset(next, nextsz)))/* 如果下一個塊是閒置,則與之合并*/
{
sz += nextsz;
if (!islr && next->fd == last_remainder(ar_ptr))
/* re-insert last_remainder */
{
islr = 1;
link_last_remainder(ar_ptr, p);
}
else
unlink(next, bck, fwd);/* 從鏈表中刪除下一個結點 */
next = chunk_at_offset(p, sz);
}
else
set_head(next, nextsz); /* 如果前後兩個塊都不是閒置,則將下一個塊的size
中的PREV_INUSE位清零 */
set_head(p, sz | PREV_INUSE);
next->prev_size = sz; /* 將下一個塊的prev_size部分填成當前塊的大小 */
if (!islr)
frontlink(ar_ptr, p, sz, idx, bck, fwd); /* 將當前這個塊插入空閑塊鏈表中 */
.....
}
我們看到這裡面有3個地方調用了unlink.如果想要執行它們,需要滿足下列條件:
1. (a) 當前塊的IS_MMAPPED位必須被清零,否則不會執行chunk_free()
2. (b) 上一個塊是個空閑塊 (當前塊size的PREV_INUSE位清零)
或者
(c) 下一個塊是個空閑塊(下下一個塊(p->next->next)size的PREV_INUSE位清零)
我們的弱點程式發生溢出時,可以覆蓋下一個塊的內部結構,但是並不能修改當前塊的內部結構,因此條件(b)是滿足不了的.我們只能寄希望於條件(c).
所謂下下一個塊的地址其實是由下一個塊的資料來推算出來的.

RH8.0上卻不行,是因為在新版本的glibc庫中堆記憶體管理採用了Wolfram Gloger的ptmalloc/ptmalloc2
代碼.ptmalloc2代碼是從Doug Lea的代碼移植過來的,主要目的是增加對多線程(尤其是SMP系統)環境的支援,同時進一步最佳化了記憶體配置,回收的演算法.
由於在ptmalloc2中引入了fastbins機制,malloc()/free()溢出在某些條件下會受到更多的限制,雖然作者的本意並不是針對溢出攻擊.由於fastbins是單向鏈表數組,每一個fastbin是一個單向鏈表,滿足fastbins條件的記憶體塊回收時將被放入相應的fastbin鏈表中,以便在以後的記憶體申請時能更快地再被分配出去,從而提高效能.

 

關於fastbins理解如下:
這個程式是我看malloc_consolidate時的一個知識點驗證程式,並不是說有多實用,多精巧 :)fastbins的概念就是小於av->max_fast (最大為80,預設為64,可以通過mallopt指定)的小堆如果每次free時都進行和周圍空塊的合并工作並不能有效增大空塊的大小而且很可能緊接著會再次出現小空間的malloc請求,作為折中每次free這些小堆時並不進行合并工作,而是將他們記錄到av->fastbins裡邊,按大小組成多個等大小的單鏈表,每個鏈表頭就在fastbins[]數組中;當有堆malloc請求或free後空快的size >0xffff時就調用malloc_consolidate進行一次清理工作:將fastbins裡的各個鏈表遍曆一遍,對每個鏈表上的塊進行和周圍空閑塊的合并工作(如果前一個塊為空白閑則unlink前塊,如果後一個塊是空閑則unlink後一個塊),合并後將整個塊放到正常的unsorted_chunks中形成正常的空閑塊,並從fastbins的鏈表裡刪除。

 

詳細參見:http://www.zzdnyy.com/cyclopedia/memory/21632.shtml

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.