記憶體布局
linux下一個進程的典型記憶體分布:
____________________ 0xffffffff</p><p>kernel space </p><p>____________________ 0xc0000000</p><p>stack</p><p>(向下拓展)</p><p>____________________</p><p>unused</p><p>____________________</p><p>dynamic libraries</p><p>____________________ 0x40000000</p><p>unused</p><p>____________________</p><p>heap</p><p>(向上拓展)</p><p>____________________</p><p>read/write sections </p><p>(.data, .bss)</p><p>____________________</p><p>readonly sections</p><p>(.init, .rodata, .text)</p><p>____________________ 0x0804800</p><p>reserved</p><p>____________________ 0<br />
函數與棧
棧儲存一個函數調用所需要的維護資訊,這常常被稱為堆疊框架 或 活動記錄.
儲存如下內容:
1. 函數的放回地址和參數
2. 臨時變數: 包括函數的非靜態局部變數 以及 比一起自動產生的其他臨時變數.
3. 儲存的上下文: 包括在函數調用前後需要保持不變的寄存器.
一個常見的活動記錄如下:
_____________</p><p>參數</p><p>_____________</p><p>返回地址</p><p>_____________</p><p>old ebp <- ebp</p><p>_____________</p><p>儲存的寄存器</p><p>_____________</p><p>局部變數</p><p>_____________</p><p>其他資料</p><p>_____________ <- esp<br />
i386函數體的"標準"開頭是這樣的:
1. push ebp: 把ebp壓入棧中
2. mov ebp, esp: ebp = esp (這時ebp指向棧頂, 而此時棧頂就是old ebp)
3. [可選]sub esp, XXX: 在棧上分配XXX位元組的臨時空間
4. [可選]push XXX: 如有必要,儲存名為XXX的幾個寄存器
那麼"標準"結尾是這樣的:
1. [可選]pop XXX: 如有必要, 恢複寄存器
2. mov esp, ebp: 恢複esp同時回收局部變數空間
3. pop: 從棧中恢複儲存的ebp的值.
4. ret: 從棧中取得返回地址, 並跳轉到該位置
反組譯碼一個函數:
int foo()
{
return 123;
}
如下:
004113A0 push ebp step one,儲存ebp,讓ebp指向目前的棧頂<br />004113A1 mov ebp, esp</p><p>004113A3 sub esp, 0C0h step two, 在棧上開闢一塊空間</p><p>004113A9 push ebx step three,儲存ebx,esi,edi寄存器<br />004113AA push esi<br />004113AB push edi</p><p>004113AC lea edi, [ebp-0C0h] step four,加入調試資訊<br />004113B2 mov ecx, 30h<br />004113B7 mov eax, 0CCCCCCCh<br />004113BC rep stos dword ptr es[di]</p><p>004113BE mov eax, 7Bh step five,返回123(在這裡傳回值是通過eax寄存器傳遞的)<br />004113C3 pop edi step six,從棧上恢複寄存器<br />004113C4 pop esi<br />004113C5 pop ebx</p><p>004113C6 mov esp, ebp step seven,恢複進入函數前的esp,ebp<br />004113C8 pop ebp</p><p>004113C9 ret step eight,使用ret指令返回<br />
補充:
函數的調用:
call 0x0EA23h
將ECS或EIP壓棧;
轉移到0x0EA23h。
ret函數的返回:
ret
彈棧得到EIP或ECS;
返回EIP所指的指令繼續執行。
函數傳回值傳遞方式
#include <stdio.h><br />typedef struct big_thing<br />{<br />char buf[128];<br />}big_thing;</p><p>big_thing return_rest()<br />{<br />big_thing b;<br />b.buf[0] = 0;<br />return b;<br />}<br />int main()<br />{<br />big_thing n = return_rest();<br />return 0;<br />}
用虛擬碼表示如下:
void return_test(void *temp)<br />{<br />big_thing b;<br />b.buf[0] = 0;<br />memcpy(temp, &b, sizeof(big_thing));<br />eax = temp;<br />}</p><p>int main()<br />{<br />big_thing temp;<br />big_thing n;<br />return_test(&temp);<br />memcpy(&n, eax, sizeof(big_thing));<br />}
步驟如下:
1. 首先main函數在棧上額外開闢了一片空間, 並將這塊空間的一部分作為傳遞傳回值的臨時對象,temp.
2. 將temp對象的地址作為隱藏參數傳遞給return_test函數
3. return_test函數將資料拷貝給temp對象, 並將temp對象的地址用eax傳出.
4. return_test返回之後, main函數將eax指向的temp對象的內容拷貝給n.
當c++對象返回時,
#include <iostream><br />using namespace std;</p><p>struct cpp_obj<br />{<br />cpp_obj()<br />{<br />cout << "ctor/n";<br />}</p><p>cpp_obj(const cpp_obj& c)<br />{<br />cout << "copy ctor/n";<br />}</p><p>cpp_obj& operator=(const cpp_obj& rhs)<br />{<br />cout << "operator=/n";<br />return *this;<br />}</p><p>~cpp_obj()<br />{<br />cout << "dtor/n";<br />}<br />};</p><p>cpp_obj return_test()<br />{<br />cpp_obj b;<br />cout << "before return/n";<br />return b;<br />}</p><p>int main()<br />{<br />cpp_obj n;<br />n = return_test();<br />return 0;<br />}
結果:
ctor<br />ctor<br />before return<br />copy ctor<br />dtor<br />operator=<br />dtor<br />dtor
#include <iostream><br />using namespace std;</p><p>struct cpp_obj<br />{<br />cpp_obj()<br />{<br />cout << "ctor/n";<br />}</p><p>cpp_obj(const cpp_obj& c)<br />{<br />cout << "copy ctor/n";<br />}</p><p>cpp_obj& operator=(const cpp_obj& rhs)<br />{<br />cout << "operator=/n";<br />return *this;<br />}</p><p>~cpp_obj()<br />{<br />cout << "dtor/n";<br />}<br />};</p><p>cpp_obj return_test()<br />{<br />cout << "before return/n";<br />return cpp_obj();<br />}</p><p>int main()<br />{<br />cpp_obj n;<br />n = return_test();<br />return 0;<br />}
當將函數改成這樣時, C++進行了傳回值最佳化, 直接將物件建構在傳出時使用的臨時對象上, 結果如下:
ctor<br />before return<br />ctor<br />operator=<br />dtor<br />dtor
堆
在windows中,利用VirtualAlloc取得的虛擬空間,類似於向作業系統"批發"空間.
而用HeapAlloc或者是malloc分配的空間實際上是堆空間的"零售".
這是因為如果記憶體管理由作業系統核心來進行,系統調用時的效能開銷較大.而如果由程式自己管理時,有更高的效率.
堆分配演算法有:
1. 空閑鏈表
2. 頭/主體/空閑 位元影像
3. 對象池
在實際系統中, 是利用多種方法共同分配堆空間的.