CDH叢集部署最佳實踐(轉)
目錄
前言
原來只知道 house of orange 打 unsorted bin ,碰到題目發現還可以打 fast bin ,今天就具體研究一下原始碼(glibc-2.23)。
分析
當所有的 bins 和 top chunk 都不滿足分配要求,且 fast bin 合併後,再次迴圈中也找不到滿足分配要求的 bin ,就會呼叫 sysmalloc 函式來分配空間。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) //使用 top chunk 分配 { ...... } else if (have_fastchunks (av)) //合併 fast bin 後再次迴圈看是否有滿足分配要求的 bin { ...... } else { void *p = sysmalloc (nb, av); if (p != NULL) alloc_perturb (p, bytes); return p; }
跟進 sysmalloc 函式看看
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max))) //使用 mmap 分配記憶體
{
......
}
首先如果所需分配的大小滿足 mmap 的閾值,而且 mmap 分配的記憶體塊數小於最大值,就會使用 mmap() 向作業系統申請記憶體進行分配。
old_top = av->top; old_size = chunksize (old_top); old_end = (char *) (chunk_at_offset (old_top, old_size)); brk = snd_brk = (char *) (MORECORE_FAILURE);
儲存當前 top chunk 的指標,大小和結束地址到臨時變數中。
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
檢查 top chunk 的合法性,如果第一次呼叫本函式, top chunk 可能沒有初始化,可能 old_size 為 0 ,如果 top chunk 已經初始化,則 top chunk 的大小必須大於等於 MINSIZE 。 prev_inuse 位必須置 1 ,top chunk 的結尾地址必須是頁對齊的。
top chunk 的大小需要小於所需分配大小 + MINSIZE ,這是因為如果 top chunk 的大小需要大於等於所需分配大小 + MINSIZE ,那麼在 _int_malloc 中就應該已經切割 top chunk 來分配 chunk 了。
if (av != &main_arena) //當前分配區不是主分配區時,進入這個分支分配記憶體
{
......
}
else //當前分配區為主分配區,進入這個分支分配記憶體
{
......
if (brk != (char *) (MORECORE_FAILURE)) // brk 合法,說明使用 sbrk() 或 mmap() 分配成功。
{
if (mp_.sbrk_base == 0) //如果 sbrk_base 還沒有初始化,更新 sbrk_base 和當前分配區的記憶體分配總量。
mp_.sbrk_base = brk;
av->system_mem += size;
}
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE)) //如果 sbrk()分配成功,更新 top chunk 的大小,
//並設定 top chunk 的前一個 chunk 處於 inuse 狀態。
set_head (old_top, (size + old_size) | PREV_INUSE);
else if (contiguous (av) && old_size && brk < old_end) //如果當前分配區可分配連續虛擬記憶體,原 top chunk 的大小大於 0,
{ //但新的 brk 值小於原 top chunk 的結束地址,則報錯。
/* Oops! Someone else killed our space.. Can't touch anything. */
malloc_printerr (3, "break adjusted to free malloc space", brk,av);
}
else //執行到這個分支,意味著 sbrk()返回的 brk 值大於原 top chunk 的結束地址,
{ //那麼新的地址與原 top chunk 的地址不連續。
......
if (snd_brk != (char *) (MORECORE_FAILURE) //如果 brk 的結束地址合法
{
......
if (old_size != 0) //如果原來 top chunk 的大小不為 0
{
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);
chunk_at_offset (old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE;
chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size = (2 * SIZE_SZ) | PREV_INUSE;
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);
}
}
}
}
}
關鍵程式碼在於 if (old_size != 0) 分支,將 top chunk 分為空閒 chunk 和 fencepost 兩部分, fencepost 為 MINISIZE 大小,設定 fencepost 裡的內容,空閒 chunk 部分呼叫 _int_free 函式釋放。
檢查繞過
orange 的技術就是為了在程式沒有 free 功能下實現 free ,所以利用思路很明顯,就是讓程式最終進入 if (old_size != 0) 分支呼叫 free 。
在 _int_malloc 中,要求所需分配的大小不能從任何 bins 中分配,
然後需要繞過判斷:
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
top chunk 的 size 要小於所需分配的大小 + MINISIZE 否則程式就會進入其中使用 top chunk 切割出所需大小的 chunk 。
else if (have_fastchunks (av))
fast bin 合併後也不能有任何滿足所需分配大小的 bin ,否則會使用該 bin 分配。
繞過以上兩個判斷後,程式進入 sysmalloc
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
所需分配的大小不能超過 mmap 的閾值,否則就使用 mmap 分配。
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)
檢查 top chunk 的合法性,以及 top chunk 的大小需要小於所需分配大小 + MINSIZE 。所以構造 top chunk 的 fake size 時需要注意,size 大小必須是如 0x0fe1、0x1fe1、0x2fe1 等頁對齊的大小,因為要保證 top chunk 的 end 是頁對齊的。同時記得將 prev_inuse 位置 1 。
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE)) //如果 sbrk()分配成功,更新 top chunk 的大小,
//並設定 top chunk 的前一個 chunk 處於 inuse 狀態。
set_head (old_top, (size + old_size) | PREV_INUSE);
else if (contiguous (av) && old_size && brk < old_end) //如果當前分配區可分配連續虛擬記憶體,原 top chunk 的大小大於 0,
{ //但新的 brk 值小於原 top chunk 的結束地址,則報錯。
/* Oops! Someone else killed our space.. Can't touch anything. */
malloc_printerr (3, "break adjusted to free malloc space", brk,av);
}
else //執行到這個分支,意味著 sbrk()返回的 brk 值大於原 top chunk 的結束地址,
{ //那麼新的地址與原 top chunk 的地址不連續。
......
_int_free (av, old_top, 1);
}
最終進入 else 分支,需要程式呼叫 sbrk() 擴充套件的新 top chunk 與原來的舊 top chunk 不相鄰,這步要怎麼實現呢?
我們看看新的 top chunk 是如何分配的。
在主分配區的情況下:
size = nb + mp_.top_pad + MINSIZE; //需要分配的 size
if (contiguous (av))
size -= old_size; //減去原來 top chunk 的大小
size = ALIGN_UP (size, pagesize); //頁對齊
if (size > 0)
{
brk = (char *) (MORECORE (size)); //呼叫 sbrk() 擴充套件 top chunk
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
}
mp_.top_pad 預設大小為 128 * 1024 ,也就是 0x20000 。
然後我們看看 old_end 是如何計算的:
old_end = (char *) (chunk_at_offset (old_top, old_size));
依據 top chunk 頭地址和 top chunk 的 size 位來計算。
所以,正常情況下
brk == old_end
是相等的,但是 orange 技術在使用時通常都會把 top chunk size 改小,這樣會導致 old_end 變小,從而最終進入 else 分支呼叫 free 。
總結以下,需要注意以下幾點:
- top chunk 的 size 要小於所需分配的大小 + MINISIZE
- fast bin 合併後也不能有任何滿足所需分配大小的 bin
- 所需分配的大小不能超過 mmap 的閾值
- top chunk 的大小必須大於等於 MINSIZE ,prev_inuse 位必須置 1 ,top chunk 的結尾地址必須是頁對齊的
滿足這些條件後,就會呼叫 _int_free 函式將原來的 top chunk 扔進 bins 裡(會比原來小 0x20 ,因為有 0x20 被拿去用作 fencepost 了)
總結
這樣看來,orange 技術可以實現在程式無 free 的情況下,將 chunk 扔進 fast bin 與 unsorted bin 。 同時發現,在主分配區中,如果呼叫 sbrk 分配記憶體成功後, __after_morecore_hook 存在則呼叫 __after_morecore_hook 。
if (brk != (char *) (MORECORE_FAILURE)) // sbrk 分配成功
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
所以在 _malloc_hook 跟 _free_hook 都無法使用的時候,可以嘗試覆蓋 __after_morecore_hook ,以後有機會可以嘗試一下。(不過這種情況不太可能 2333 )
內容來源
《 glibc 記憶體管理 ptmalloc 原始碼分析》