一、bss(可讀可寫)html
bss是英文Block Started by Symbol的簡稱,一般是指用來存放程序中未初始化的全局變量的一塊內存區域,在程序載入時由內核清0。BSS段屬於靜態內存分配。它的初始值也是由用戶本身定義的鏈接定位文件所肯定,用戶應該將它定義在可讀寫的RAM區內,linux
源程序中使用malloc分配的內存就是這一塊,它不是根據data大小肯定,主要由程序中同時分配內存最大值所肯定,不過若是超出了範圍,也就是分配失敗,能夠等空間釋放以後再分配。程序員
二、text(只讀)
text段是程序代碼段,在AT91庫中是表示程序段的大小,它是由編譯器在編譯鏈接時自動計算的,當你在連接定位文件中將該符號放置在代碼段後,那麼該符號表示的值就是代碼段大小,編譯鏈接時,該符號所表明的值會自動代入到源程序中。數組
三、data(可讀可寫)
data包含靜態初始化的數據,因此有初值的全局變量和static變量在data區。段的起始位置也是由鏈接定位文件所肯定,大小在編譯鏈接時自動分配,它和你的程序大小沒有關係,但和程序使用到的全局變量,常量數量相關。安全
From: http://www.javashuo.com/article/p-zgecszul-o.html數據結構
1.一個典型的ELF可重定位目標文件的格式P451。ELF頭(ELF header)以一個16字節的序列開始,這個序列描述了生成該文件的系統的字的大小和字節順序。ELF頭剩下的部分包含幫助連接器語法分析和解釋目標文件的信息。架構
其中包括ELF頭的大小、目標文件的類型(如可重定位、可執行或是共享的)、機器類型(如IA32)、節頭部表的文件偏移,以及節頭部表中的條目大小和數量。不一樣的節的位置和大小是由節頭部表描述的,其中目標文件中每一個節都有一個固定大小的條目。函數
2.夾在ELF頭和節頭部表之間的都是節。一個典型的ELF可重定位目標文件包含下面幾個節:工具
代碼段上面應該有個post
一個由C/C++編譯的程序佔用的內存分爲如下幾個部分
一、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
二、堆區(heap) — 通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表。
3. 全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,
初始化的全局變量和靜態變量在一塊區域(.data段),
未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域(.bss段)。 - 程序結束後有系統釋放。
注:在採用段式內存管理的架構中(好比intel的80x86系統),.bss段(Block Started by Symbol segment)一般是指用來存放程序中未初始化的全局變量的一塊內存區域,通常在初始化時bss段部分將會清零。
bss段屬於靜態內存分配,即程序一開始就將其清零了。在C語言之類的程序編譯完成以後,已初始化的全局變量保存在.data 段中。
四、文字常量區 — 常量字符串就是放在這裏的。 程序結束後由系統釋放
五、程序代碼區 (.text)— 存放函數體的二進制代碼。
int a = 0; 全局初始化區 char *p1; 全局未初始化區
main() { int b; 棧 char s[] = "abc"; 棧 char *p2; 棧 char *p3 = "123456"; 123456/0在常量區,p3在棧上 static int c =0; 全局(靜態)初始化區 p1 = (char *)malloc(10); p2 = (char *)malloc(20); 分配得來得10和20字節的區域就在堆區。 strcpy(p1, "123456"); 123456/0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。 }
2.1 申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間
heap:
須要程序員本身申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
可是注意p一、p2自己是在棧中的。
2.2 申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
2.4申請效率的比較:
棧:由系統自動分配,速度較快。但程序員是沒法控制的。
堆:是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。可是速度快,也最靈活
2.5堆和棧中的存儲內容
棧:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就肯定的;
可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
Unix系統裏的兩種重要的格式:a.out和elf(Executable and Linking Format)。
這兩種格式中都有符號表(symbol table),其中包括全部的符號(程序的入口點還有變量的地址等等)。在elf格式中符號表的內容會比a.out格式的豐富的多。可是這些符號表能夠用 strip工具去除,這樣的話這個文件就沒法讓debug程序跟蹤了,可是會生成比較小的可執行文件。a.out文件中的符號表能夠被徹底去除,可是 elf中的在加載運行是起着重要的做用,因此用strip永遠不可能徹底去除elf格式文件中的符號表。可是用strip命令不是徹底安全的,好比對未鏈接的目標文件來講若是用strip去掉符號表的話,會致使鏈接器沒法鏈接。
$:readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32 Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 256 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 11
Section header string table index: 8
Magic:字段是一個標識符,只要Magic字段是7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00的文件都是elf文件。
Class:字段是表示elf的版本,這是一個32位的elf。
Machine:字段是指出目標文件的平臺信息,這裏是 I386兼容平臺。
其餘的字段能夠從其字面上看出它的意義,這裏就不一一解釋了。
$:readelf -S hello.o
There are 11 section headers, starting at offset 0x100:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00002a 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000370 000010 08 9 1 4
[ 3] .data PROGBITS 00000000 000060 000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000060 000000 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000060 00000e 00 A 0 0 1
[ 6] .note.GNU-stack PROGBITS 00000000 00006e 000000 00 0 0 1
[ 7] .comment PROGBITS 00000000 00006e 00003e 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0000ac 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 0002b8 0000a0 10 10 8 4
[10] .strtab STRTAB 00000000 000358 000015 00 0 0 1
Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
Name字段顯示的是各個段的名字,
Type顯示段的屬性,
Addr是每一個段載入虛擬內存的位置,
Off是每一個段在目標文件中的偏移位置,
Size是每一個段的大小,後面的一些字段是表示段的可寫,可讀,或者可執行。
$:readelf -r hello.o
Relocation section '.rel.text' at offset 0x370 contains 2 entries: Offset Info Type Sym.Value Sym. Name 0000001f 00000501 R_386_32 00000000 .rodata 00000024 00000902 R_386_PC32 00000000 printf
在.text段中有兩個relocation,其中之一就是printf函數的relcation。
Offset指出當relocation時要把 printf函數的入口地址貼到離.text段開頭00000024處。
$:gcc hello.o
$:readelf -S a.out
There are 32 section headers, starting at offset 0xbc4:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .hash HASH 08048168 000168 00002c 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048194 000194 000060 10 A 5 1 4
[ 5] .dynstr STRTAB 080481f4 0001f4 000060 00 A 0 0 1
[ 6] .gnu.version VERSYM 08048254 000254 00000c 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 08048260 000260 000020 00 A 5 1 4
[ 8] .rel.dyn REL 08048280 000280 000008 08 A 4 0 4
[ 9] .rel.plt REL 08048288 000288 000010 08 A 4 11 4
[10] .init PROGBITS 08048298 000298 000017 00 AX 0 0 4
[11] .plt PROGBITS 080482b0 0002b0 000030 04 AX 0 0 4
[12] .text PROGBITS 080482e0 0002e0 0001b4 00 AX 0 0 16
[13] .fini PROGBITS 08048494 000494 00001a 00 AX 0 0 4
[14] .rodata PROGBITS 080484b0 0004b0 000016 00 A 0 0 4
[15] .eh_frame PROGBITS 080484c8 0004c8 000004 00 A 0 0 4
[16] .ctors PROGBITS 080494cc 0004cc 000008 00 WA 0 0 4
[17] .dtors PROGBITS 080494d4 0004d4 000008 00 WA 0 0 4
[18] .jcr PROGBITS 080494dc 0004dc 000004 00 WA 0 0 4
[19] .dynamic DYNAMIC 080494e0 0004e0 0000c8 08 WA 5 0 4
[20] .got PROGBITS 080495a8 0005a8 000004 04 WA 0 0 4
[21] .got.plt PROGBITS 080495ac 0005ac 000014 04 WA 0 0 4
[22] .data PROGBITS 080495c0 0005c0 00000c 00 WA 0 0 4
[23] .bss NOBITS 080495cc 0005cc 000004 00 WA 0 0 4
[24] .comment PROGBITS 00000000 0005cc 0001b2 00 0 0 1
[25] .debug_aranges PROGBITS 00000000 000780 000058 00 0 0 8
[26] .debug_info PROGBITS 00000000 0007d8 000164 00 0 0 1
[27] .debug_abbrev PROGBITS 00000000 00093c 000020 00 0 0 1
[28] .debug_line PROGBITS 00000000 00095c 00015a 00 0 0 1
[29] .shstrtab STRTAB 00000000 000ab6 00010c 00 0 0 1
[30] .symtab SYMTAB 00000000 0010c4 000510 10 31 56 4
[31] .strtab STRTAB 00000000 0015d4 000322 00 0 0 1
Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
這裏的段比目標文件hello.o的段要多的多,這是由於這個程序須要elf的一個動態鏈接庫libc.so.1。
在這裏須要簡單的介紹一下內核加載 elf可執行文件。內核先是把整個文件加載到用戶的虛擬內存空間,若是程序是與動態鏈接庫鏈接的,則程序中就會包含動態鏈接器的名稱,多是 /lib/elf/ld-linux.so.1。(動態鏈接器自己也是一個動態鏈接庫)
在文件的尾部的一些段的Addr值是00000000,由於這些都是符號表,動態鏈接器並不把這些段的內容加載到內存中。
. interp段中只是儲存這一個ASCII的字符串,它就是動態鏈接器的名字(路徑)。
.hash, .dynsym, .dynstr 這三個段是用於動態鏈接器執行relocation時的符號表。
.hash是一個哈希表,可讓咱們很快的從.dynsym中找到所需的符號。
.plt段中儲存着咱們調用動態鏈接庫中的函數入口地址,在默認狀態下,程序初始化時,.plt中的指針並非指向正確的函數入口地址的而是指向動態鏈接器自己,當你在程序中調用某個動態鏈接庫中的函數時,鏈接器會找到那個函數在動態鏈接庫中的位置,再把這個位置鏈接到.plt段中。這樣作的好處是若是在程序中調用了不少動態鏈接庫中的函數,會花費掉鏈接器很長時間把每一個函數的地址鏈接到.plt段中。因此就能夠採用鏈接器只是把要用的函數地址鏈接進去,之後要用的再鏈接。可是也能夠設置環境變量LD_BIND_NOW=1讓鏈接器在程序執行前把全部的函數地址都鏈接好,這主要是方便調試程序。
一個簡單的動態鏈接庫的例子
Dyn_hello.c
int main(void) { hi(); }
hi.c
#include hi() { printf("Hello world\n"); }
$:gcc -fPIC -c hi.c $:gcc -shared -o libhi.so hi.o
生成了 libhi.so 的文件。
主程序可執行文件
$:gcc -c Dyn_hello.c $:gcc -o Dyn_hello Dyn_hello.o -L. -lhi
生成了 Dyn_hello 文件
執行程序
$:LD_LIBRARY_PATH=. ./Dyn_hello
指出當前目錄是鏈接器的搜索目錄。
代碼段
#include int main (int argc, char *argv[]) { void (*hi) (); void *m;
if (argc > 2) exit (0);
m = dlopen (argv[1], RTLD_LAZY); if (!m) exit (0); hi = dlsym (m, "hi"); if (hi) { (*hi) (); } dlclose (m); }
主程序可執行文件
$:gcc -c Dl_hello.c $:gcc -o Dl_hello Dl_hello.o -ldl
生成了 Dl_hello 文件
執行程序
$:./Dl_hello ./libhi.so
End.