日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux内存管理内存映射以及通过反汇编定位内存错误问题

發(fā)布時(shí)間:2023/12/20 linux 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux内存管理内存映射以及通过反汇编定位内存错误问题 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

提到C語(yǔ)言,我們知道C語(yǔ)言和其他高級(jí)語(yǔ)言的最大的區(qū)別就是C語(yǔ)言是要操作內(nèi)存的!

?????我們需要知道——變量,其實(shí)是內(nèi)存地址的一個(gè)抽像名字罷了。在靜態(tài)編譯的程序中,所有的變量名都會(huì)在編譯時(shí)被轉(zhuǎn)成內(nèi)存地址。機(jī)器是不知道我們?nèi)〉拿值?#xff0c;只知道地址。

????內(nèi)存的使用時(shí)程序設(shè)計(jì)中需要考慮的重要因素之一,這不僅由于系統(tǒng)內(nèi)存是有限的(尤其在嵌入式系統(tǒng)中),而且內(nèi)存分配也會(huì)直接影響到程序的效率。因此,我們要對(duì)C語(yǔ)言中的內(nèi)存管理,有個(gè)系統(tǒng)的了解。

????在C語(yǔ)言中,定義了4個(gè)內(nèi)存區(qū)間:代碼區(qū);全局變量和靜態(tài)變量區(qū);局部變量區(qū)即棧區(qū);動(dòng)態(tài)存儲(chǔ)區(qū),即堆區(qū);具體如下:

????1、棧區(qū)(stack)— 由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類(lèi)似于數(shù)據(jù)結(jié)構(gòu)中的棧。

????2、堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類(lèi)似于鏈表,呵呵。
????3、全局區(qū)(靜態(tài)區(qū))(static)—全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的 另一塊區(qū)域。 - 程序結(jié)束后由系統(tǒng)釋放。
???4、常量區(qū) —常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放
???5、程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。

我們來(lái)看張圖:

首先我們要知道,源代碼編譯成程序,程序是放在硬盤(pán)上的,而非內(nèi)存里!只有執(zhí)行時(shí)才會(huì)被調(diào)用到內(nèi)存中!

我們來(lái)看看程序結(jié)構(gòu),ELF是是Linux的主要可執(zhí)行文件格式。ELF文件由4部分組成,分別是ELF頭(ELF header)、程序頭表(Program header table)、節(jié)(Section)和節(jié)頭表(Section header table)。具體如下:

1、Program header描述的是一個(gè)段在文件中的位置、大小以及它被放進(jìn)內(nèi)存后所在的位置和大小。即要加載的信息;
2、Sections保存著object 文件的信息,從連接角度看:包括指令,數(shù)據(jù),符號(hào)表,重定位信息等等。在圖中,我們可以看到Sections中包括:
??????(1)??.text???文本結(jié) 存放指令;
??????(2)??.rodata???數(shù)據(jù)結(jié)??readonly;
??????(3)??.data??數(shù)據(jù)結(jié)?可讀可寫(xiě);?
3、Section頭表(section header table)包含了描述文件sections的信息。每個(gè)section在這個(gè)表中有一個(gè)入口;每個(gè)入口給出了該section的名字,大小,等等信息。相當(dāng)于 索引!
?????而程序被加載到內(nèi)存里面,又是如何分布的呢?我們看看上圖中:
1、正文和初始化的數(shù)據(jù)和未初始化的數(shù)據(jù)就是我們所說(shuō)的數(shù)據(jù)段,正文即代碼段;
2、正文段上面是常量區(qū),常量區(qū)上面是全局變量和靜態(tài)變量區(qū),二者占據(jù)的就是初始化的數(shù)據(jù)和未初始化的數(shù)據(jù)那部分;
3、再上面就是堆,動(dòng)態(tài)存儲(chǔ)區(qū),這里是上增長(zhǎng);
4、堆上面是棧,存放的是局部變量,就是局部變量所在代碼塊執(zhí)行完畢后,這塊內(nèi)存會(huì)被釋放,這里棧區(qū)是下增長(zhǎng);
5、命令行參數(shù)就是$0 $1之類(lèi)的,環(huán)境變量什么的前面的文章已經(jīng)講過(guò),有興趣的可以去看看。
?
????我們知道,內(nèi)存分為動(dòng)態(tài)內(nèi)存和靜態(tài)內(nèi)存,我們先講靜態(tài)內(nèi)存。

一、靜態(tài)內(nèi)存

內(nèi)存管理---存儲(chǔ)模型
???????存儲(chǔ)模型決定了一個(gè)變量的內(nèi)存分配方式和訪問(wèn)特性,在C語(yǔ)言中主要有三個(gè)維度來(lái)決定:1、存儲(chǔ)時(shí)期 2、作用域 3、鏈接

1、存儲(chǔ)時(shí)期

? ? 存儲(chǔ)時(shí)期:變量在內(nèi)存中的保留時(shí)間(生命周期)

? ? 存儲(chǔ)時(shí)期分為兩種情況,關(guān)鍵是看變量在程序執(zhí)行過(guò)程中會(huì)不會(huì)被系統(tǒng)自動(dòng)回收掉。

?1)??靜態(tài)存儲(chǔ)時(shí)期 Static
????????在程序執(zhí)行過(guò)程中一旦分配就不會(huì)被自動(dòng)回收。
????????通常來(lái)說(shuō),任何不在函數(shù)級(jí)別代碼塊內(nèi)定義的變量。
????????無(wú)論是否在代碼塊內(nèi),只要采用static關(guān)鍵字修飾的變量。

?2) 自動(dòng)存儲(chǔ)時(shí)期??Automatic
????????除了靜態(tài)存儲(chǔ)以外的變量都是自動(dòng)存儲(chǔ)時(shí)期的,或者說(shuō)只要是在代碼塊內(nèi)定義的非static的變量,系統(tǒng)會(huì)肚臍自動(dòng)非配和釋放內(nèi)存;
?
2、作用域
??????作用域:一個(gè)變量在定義該變量的自身文件中的可見(jiàn)性(訪問(wèn)或者引用)
???????在C語(yǔ)言中,一共有3中作用域:
1)??代碼塊作用域
?????在代碼塊中定義的變量都具有該代碼的作用域。從這個(gè)變量定義地方開(kāi)始,到這個(gè)代碼塊結(jié)束,該變量是可見(jiàn)的;
2)??函數(shù)原型作用域
????出現(xiàn)在函數(shù)原型中的變量,都具有函數(shù)原型作用域,函數(shù)原型作用域從變量定義處一直到原型聲明的末尾。
3)??文件作用域
????一個(gè)在所有函數(shù)之外定義的變量具有文件作用域,具有文件作用域的變量從它的定義處到包含該定義的文件結(jié)尾處都是可見(jiàn)的;
?
3、鏈接
??????鏈接:一個(gè)變量在組成程序的所有文件中的可見(jiàn)性(訪問(wèn)或者引用);
??????C語(yǔ)言中一共有三種不同的鏈接:
1)??外部鏈接
??????如果一個(gè)變量在組成一個(gè)程序的所有文件中的任何位置都可以被訪問(wèn),則稱(chēng)該變量支持外部鏈接;
2)??內(nèi)部鏈接
????如果一個(gè)變量只可以在定義其自身的文件中的任何位置被訪問(wèn),則稱(chēng)該變量支持內(nèi)部鏈接。
3)??空鏈接???
??????如果一個(gè)變量只是被定義其自身的當(dāng)前代碼塊所私有,不能被程序的其他部分所訪問(wèn),則成該變量支持空鏈接
我們來(lái)看一個(gè)代碼示例:

#include <stdio.h>int a = 0;// 全局初始化區(qū) ? char *p1; //全局未初始化區(qū) ?int main() ? { ?int b; //b在棧區(qū)char s[] = "abc"; //棧 ?char *p2; //p2在棧區(qū)char *p3 = "123456"; //123456\0在常量區(qū),p3在棧上。 ?static int c =0; //全局(靜態(tài))初始化區(qū)p1 = (char *)malloc(10); ?p2 = (char *)malloc(20); ?//分配得來(lái)得10和20字節(jié)的區(qū)域就在堆區(qū)。 ?strcpy(p1, "123456"); //123456\0放在常量區(qū),編譯器可能會(huì)將它與p3所指向的"123456"優(yōu)化成一個(gè)地方。 ? } ?

二、動(dòng)態(tài)內(nèi)存

? ? 當(dāng)程序運(yùn)行到需要一個(gè)動(dòng)態(tài)分配的變量時(shí),必須向系統(tǒng)申請(qǐng)取得堆中的一塊所需大小的存儲(chǔ)空間,用于存儲(chǔ)該變量。當(dāng)不在使用該變量時(shí),也就是它的生命結(jié)束時(shí),要顯示釋放它所占用的存儲(chǔ)空間,這樣系統(tǒng)就能對(duì)該空間?進(jìn)行再次分配,做到重復(fù)使用有線(xiàn)的資源。下面介紹動(dòng)態(tài)內(nèi)存申請(qǐng)和釋放的函數(shù)。
1.1 malloc 函數(shù)
?malloc函數(shù)原型:

#include <stdio.h> void *malloc(size_t size);

size是需要?jiǎng)討B(tài)申請(qǐng)的內(nèi)存的字節(jié)數(shù)。若申請(qǐng)成功,函數(shù)返回申請(qǐng)到的內(nèi)存的起始地址,若申請(qǐng)失敗,返回NULL。我們看下面這個(gè)例子:

int *get_memory(int n) {int *p;p = (int *)malloc(sizeof(int));if(p == NULL){printf("malloc error\n");return p;}memset(p,0,n*sizeof(int)); }

使用該函數(shù)時(shí),有下面幾點(diǎn)要注意:
1)只關(guān)心申請(qǐng)內(nèi)存的大小;
2)申請(qǐng)的是一塊連續(xù)的內(nèi)存。記得一定要寫(xiě)出錯(cuò)判斷;
3)顯示初始化。即我們不知這塊內(nèi)存中有什么東西,要對(duì)其清零;
?
1.2 free函數(shù)
????在堆上分配的額內(nèi)存,需要用free函數(shù)顯示釋放,函數(shù)原型如下:

#include <stdlib.h> void free(void *ptr);

使用free(),也有下面幾點(diǎn)要注意:
1)必須提供內(nèi)存的起始地址;
????調(diào)用該函數(shù)時(shí),必須提供內(nèi)存的起始地址,不能夠提供部分地址,釋放內(nèi)存中的一部分是不允許的。
2)malloc和free配對(duì)使用;
? ? 編譯器不負(fù)責(zé)動(dòng)態(tài)內(nèi)存的釋放,需要程序員顯示釋放。因此,malloc與free是配對(duì)使用的,避免內(nèi)存泄漏。
free(p);
p = NULL;
p = NULL是必須的,因?yàn)殡m然這塊內(nèi)存被釋放了,但是p仍指向這塊內(nèi)存,避免下次對(duì)p的誤操作;
3)不允許重復(fù)釋放
因?yàn)檫@塊內(nèi)存被釋放后,可能已另分配,這塊區(qū)域被別人占用,如果再次釋放,會(huì)造成數(shù)據(jù)丟失;

?
2、我們經(jīng)常將堆和棧相比較:
2.1申請(qǐng)方式??
stack:??由系統(tǒng)自動(dòng)分配。 例如,聲明在函數(shù)中一個(gè)局部變量 int b; 系統(tǒng)自動(dòng)在棧中為b開(kāi)辟空間??
heap:??需要程序員自己申請(qǐng),并指明大小,在c中malloc函數(shù) ,如p1 = (char *)malloc(10);??
??
2.2??申請(qǐng)后系統(tǒng)的響應(yīng)??
棧:只要棧的剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。??
堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語(yǔ)句才能正確的釋放本內(nèi)存空間。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中。?

2.3申請(qǐng)大小的限制??
棧:棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話(huà)的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù)),如果申請(qǐng)的空間超過(guò)棧的剩余空間時(shí),將提示overflow。因此,能從棧獲得的空間較小。?
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來(lái)存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見(jiàn),堆獲得的空間比較靈活,也比較大。?

2.4申請(qǐng)效率的比較:??
棧由系統(tǒng)自動(dòng)分配,速度較快。但程序員是無(wú)法控制的。??
堆是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過(guò)用起來(lái)最方便。?

2.5堆和棧中的存儲(chǔ)內(nèi)容??
棧: 在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語(yǔ)句的下一條可執(zhí)行語(yǔ)句)的地址,然后是函數(shù)的各個(gè)參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。??當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開(kāi)始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。?
堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容由程序員安排。?

2.6存取效率的比較?

char s1[] = "aaaaaaaaaaaaaaa";??
char *s2 = "bbbbbbbbbbbbbbbbb";??
aaaaaaaaaaa是在運(yùn)行時(shí)刻賦值的;??
而bbbbbbbbbbb是在編譯時(shí)就確定的;??
但是,在以后的存取中,在棧上的數(shù)組比指針?biāo)赶虻淖址?例如堆)快。??
比如:??

#include ? void main() ? { ?char a = 1; ?char c[] = "1234567890"; ?char *p ="1234567890"; ?a = c[1]; ?a = p[1]; ?return; ? } ?

對(duì)應(yīng)的匯編代碼?

0: a = c[1]; ? 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] ? 0040106A 88 4D FC mov byte ptr [ebp-4],cl ? 11: a = p[1]; ? 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] ? 00401070 8A 42 01 mov al,byte ptr [edx+1] ? 00401073 88 45 FC mov byte ptr [ebp-4],al ?

第一種在讀取時(shí)直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,再根據(jù)edx讀取字符,顯然慢了。?


2.7小結(jié):??
???堆和棧的區(qū)別可以用如下的比喻來(lái)看出:??
???棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))、付錢(qián)、和吃(使用),吃飽了就走,不必理會(huì)切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。??
???堆就象是自己動(dòng)手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。


補(bǔ)充知識(shí)點(diǎn)一:

線(xiàn)程和進(jìn)程各自有什么區(qū)別和優(yōu)劣呢?
? ? ?進(jìn)程是資源分配的最小單位,線(xiàn)程是程序執(zhí)行的最小單位。

? ? ?進(jìn)程有自己的獨(dú)立地址空間,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)就會(huì)為它分配地址空間,建立數(shù)據(jù)表來(lái)維護(hù)代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。而線(xiàn)程是共享進(jìn)程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個(gè)線(xiàn)程的花費(fèi)遠(yuǎn)比進(jìn)程要小很多,同時(shí)創(chuàng)建一個(gè)線(xiàn)程的開(kāi)銷(xiāo)也比進(jìn)程要小很多。

? ? ?線(xiàn)程之間的通信更方便,同一進(jìn)程下的線(xiàn)程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行。不過(guò)如何處理好同步與互斥是編寫(xiě)多線(xiàn)程程序的難點(diǎn)。

? ? ?但是多進(jìn)程程序更健壯,多線(xiàn)程程序只要有一個(gè)線(xiàn)程死掉,整個(gè)進(jìn)程也死掉了,而一個(gè)進(jìn)程死掉并不會(huì)對(duì)另外一個(gè)進(jìn)程造成影響,因?yàn)檫M(jìn)程有自己獨(dú)立的地址空間。


補(bǔ)充知識(shí)點(diǎn)二:

? ? BSS段通常是指用來(lái)存放程序中未初始化的全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。特點(diǎn)是可讀寫(xiě)的,在程序執(zhí)行之前BSS段會(huì)自動(dòng)清0。
? ? 可執(zhí)行程序包括BSS段、數(shù)據(jù)段、代碼段(也稱(chēng)文本段)。
? ? BSS(Block Started by Symbol)通常是指用來(lái)存放程序中未初始化的全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。特點(diǎn)是:可讀寫(xiě)的,在程序執(zhí)行之前BSS段會(huì)自動(dòng)清0。所以,未初始的全局變量在程序執(zhí)行之前已經(jīng)成0了。
? ? 數(shù)據(jù)段包括初始化的數(shù)據(jù)和未初始化的數(shù)據(jù)(BSS)兩部分 。BSS段存放的是未初始化的全局變量和靜態(tài)變量。
? ? 可使用size命令查看可執(zhí)行文件的段大小信息。如size a.out。

biao@ubuntu:~/test/photo$ size a.out text data bss dec hex filename2876 632 8 3516 dbc a.out biao@ubuntu:~/test/photo$




Linux驅(qū)動(dòng)mmap內(nèi)存映射

mmap在linux哪里?


什么是mmap?

上圖說(shuō)了,mmap是操作這些設(shè)備的一種方法,所謂操作設(shè)備,比如IO端口(點(diǎn)亮一個(gè)LED)、LCD控制器、磁盤(pán)控制器,實(shí)際上就是往設(shè)備的物理地址讀寫(xiě)數(shù)據(jù)。

但是,由于應(yīng)用程序不能直接操作設(shè)備硬件地址,所以操作系統(tǒng)提供了這樣的一種機(jī)制——內(nèi)存映射,把設(shè)備地址映射到進(jìn)程虛擬地址,mmap就是實(shí)現(xiàn)內(nèi)存映射的接口。

操作設(shè)備還有很多方法,如ioctl、ioremap

mmap的好處是,mmap把設(shè)備內(nèi)存映射到虛擬內(nèi)存,則用戶(hù)操作虛擬內(nèi)存相當(dāng)于直接操作設(shè)備了,省去了用戶(hù)空間到內(nèi)核空間的復(fù)制過(guò)程,相對(duì)IO操作來(lái)說(shuō),增加了數(shù)據(jù)的吞吐量。


什么是內(nèi)存映射?

既然mmap是實(shí)現(xiàn)內(nèi)存映射的接口,那么內(nèi)存映射是什么呢?看下圖

每個(gè)進(jìn)程都有獨(dú)立的進(jìn)程地址空間,通過(guò)頁(yè)表和MMU,可將虛擬地址轉(zhuǎn)換為物理地址,每個(gè)進(jìn)程都有獨(dú)立的頁(yè)表數(shù)據(jù),這可解釋為什么兩個(gè)不同進(jìn)程相同的虛擬地址,卻對(duì)應(yīng)不同的物理地址。


什么是虛擬地址空間?

每個(gè)進(jìn)程都有4G的虛擬地址空間,其中3G用戶(hù)空間,1G內(nèi)核空間(linux),每個(gè)進(jìn)程共享內(nèi)核空間,獨(dú)立的用戶(hù)空間,下圖形象地表達(dá)了這點(diǎn)

驅(qū)動(dòng)程序運(yùn)行在內(nèi)核空間,所以驅(qū)動(dòng)程序是面向所有進(jìn)程的。

用戶(hù)空間切換到內(nèi)核空間有兩種方法:

(1)系統(tǒng)調(diào)用,即軟中斷

(2)硬件中斷


虛擬地址空間里面是什么?

了解了什么是虛擬地址空間,那么虛擬地址空間里面裝的是什么?看下圖

虛擬空間裝的大概是上面那些數(shù)據(jù)了,內(nèi)存映射大概就是把設(shè)備地址映射到上圖的紅色段了,暫且稱(chēng)其為“內(nèi)存映射段”,至于映射到哪個(gè)地址,是由操作系統(tǒng)分配的,操作系統(tǒng)會(huì)把進(jìn)程空間劃分為三個(gè)部分:

(1)未分配的,即進(jìn)程還未使用的地址

(2)緩存的,緩存在ram中的頁(yè)

(3)未緩存的,沒(méi)有緩存在ram中

操作系統(tǒng)會(huì)在未分配的地址空間分配一段虛擬地址,用來(lái)和設(shè)備地址建立映射,至于怎么建立映射,后面再揭曉。

現(xiàn)在大概明白了“內(nèi)存映射”是什么了,那么內(nèi)核是怎么管理這些地址空間的呢?任何復(fù)雜的理論最終也是通過(guò)各種數(shù)據(jù)結(jié)構(gòu)體現(xiàn)出來(lái)的,而這里這個(gè)數(shù)據(jù)結(jié)構(gòu)就是進(jìn)程描述符。從內(nèi)核看,進(jìn)程是分配系統(tǒng)資源(CPU、內(nèi)存)的載體,為了管理進(jìn)程,內(nèi)核必須對(duì)每個(gè)進(jìn)程所做的事情進(jìn)行清楚的描述,這就是進(jìn)程描述符,內(nèi)核用task_struct結(jié)構(gòu)體來(lái)表示進(jìn)程,并且維護(hù)一個(gè)該結(jié)構(gòu)體鏈表來(lái)管理所有進(jìn)程。該結(jié)構(gòu)體包含一些進(jìn)程狀態(tài)、調(diào)度信息等上千個(gè)成員,我們這里主要關(guān)注進(jìn)程描述符里面的內(nèi)存描述符(struct mm_struct mm)


內(nèi)存描述符

具體的結(jié)構(gòu),請(qǐng)參考下圖

現(xiàn)在已經(jīng)知道了內(nèi)存映射是把設(shè)備地址映射到進(jìn)程空間地址(注意:并不是所有內(nèi)存映射都是映射到進(jìn)程地址空間的,ioremap是映射到內(nèi)核虛擬空間的,mmap是映射到進(jìn)程虛擬地址的),實(shí)質(zhì)上是分配了一個(gè)vm_area_struct結(jié)構(gòu)體加入到進(jìn)程的地址空間,也就是說(shuō),把設(shè)備地址映射到這個(gè)結(jié)構(gòu)體,映射過(guò)程就是驅(qū)動(dòng)程序要做的事了。


內(nèi)存映射的實(shí)現(xiàn)

以字符設(shè)備驅(qū)動(dòng)為例,一般對(duì)字符設(shè)備的操作都如下框圖

而內(nèi)存映射的主要任務(wù)就是實(shí)現(xiàn)內(nèi)核空間中的mmap()函數(shù),先來(lái)了解一下字符設(shè)備驅(qū)動(dòng)程序的框架

以下是mmap_driver.c的源代碼

//所有的模塊代碼都包含下面兩個(gè)頭文件?? #include?<linux/module.h>?? #include?<linux/init.h>??#include?<linux/types.h>?//定義dev_t類(lèi)型?? #include?<linux/cdev.h>?//定義struct?cdev結(jié)構(gòu)體及相關(guān)操作?? #include?<linux/slab.h>?//定義kmalloc接口?? #include?<asm/io.h>//定義virt_to_phys接口?? #include?<linux/mm.h>//remap_pfn_range?? #include?<linux/fs.h>??#define?MAJOR_NUM?990?? #define?MM_SIZE?4096??static?char?driver_name[]?=?"mmap_driver1";//驅(qū)動(dòng)模塊名字?? static?int?dev_major?=?MAJOR_NUM;?? static?int?dev_minor?=?0;?? char?*buf?=?NULL;?? struct?cdev?*cdev?=?NULL;??static?int?device_open(struct?inode?*inode,?struct?file?*file)?? {??printk(KERN_ALERT"device?open\n");??buf?=?(char?*)kmalloc(MM_SIZE,?GFP_KERNEL);//內(nèi)核申請(qǐng)內(nèi)存只能按頁(yè)申請(qǐng),申請(qǐng)?jiān)搩?nèi)存以便后面把它當(dāng)作虛擬設(shè)備??return?0;?? }??static?int?device_close(struct?inode?*indoe,?struct?file?*file)?? {??printk("device?close\n");??if(buf)??{??kfree(buf);??}??return?0;?? }??static?int?device_mmap(struct?file?*file,?struct?vm_area_struct?*vma)?? {??vma->vm_flags?|=?VM_IO;//表示對(duì)設(shè)備IO空間的映射??vma->vm_flags?|=?VM_RESERVED;//標(biāo)志該內(nèi)存區(qū)不能被換出,在設(shè)備驅(qū)動(dòng)中虛擬頁(yè)和物理頁(yè)的關(guān)系應(yīng)該是長(zhǎng)期的,應(yīng)該保留起來(lái),不能隨便被別的虛擬頁(yè)換出??if(remap_pfn_range(vma,//虛擬內(nèi)存區(qū)域,即設(shè)備地址將要映射到這里??vma->vm_start,//虛擬空間的起始地址??virt_to_phys(buf)>>PAGE_SHIFT,//與物理內(nèi)存對(duì)應(yīng)的頁(yè)幀號(hào),物理地址右移12位??vma->vm_end?-?vma->vm_start,//映射區(qū)域大小,一般是頁(yè)大小的整數(shù)倍??vma->vm_page_prot))//保護(hù)屬性,??{??return?-EAGAIN;??}??return?0;?? }??static?struct?file_operations?device_fops?=?? {??.owner?=?THIS_MODULE,??.open??=?device_open,??.release?=?device_close,??.mmap?=?device_mmap,?? };??static?int?__init?char_device_init(?void?)?? {??int?result;??dev_t?dev;//高12位表示主設(shè)備號(hào),低20位表示次設(shè)備號(hào)??printk(KERN_ALERT"module?init2323\n");??printk("dev=%d",?dev);??dev?=?MKDEV(dev_major,?dev_minor);??cdev?=?cdev_alloc();//為字符設(shè)備cdev分配空間??printk(KERN_ALERT"module?init\n");??if(dev_major)??{??result?=?register_chrdev_region(dev,?1,?driver_name);//靜態(tài)分配設(shè)備號(hào)??printk("result?=?%d\n",?result);??}??else??{??result?=?alloc_chrdev_region(&dev,?0,?1,?driver_name);//動(dòng)態(tài)分配設(shè)備號(hào)??dev_major?=?MAJOR(dev);??}??if(result?<?0)??{??printk(KERN_WARNING"Cant't?get?major?%d\n",?dev_major);??return?result;??}??cdev_init(cdev,?&device_fops);//初始化字符設(shè)備cdev??cdev->ops?=?&device_fops;??cdev->owner?=?THIS_MODULE;??result?=?cdev_add(cdev,?dev,?1);//向內(nèi)核注冊(cè)字符設(shè)備??printk("dffd?=?%d\n",?result);??return?0;?? }??static?void?__exit?char_device_exit(?void?)?? {??printk(KERN_ALERT"module?exit\n");??cdev_del(cdev);??unregister_chrdev_region(MKDEV(dev_major,?dev_minor),?1);?? }??module_init(char_device_init);//模塊加載?? module_exit(char_device_exit);//模塊退出??MODULE_LICENSE("GPL");?? MODULE_AUTHOR("ChenShengfa");?

下面是測(cè)試代碼test_mmap.c

#include?<stdio.h>?? #include?<fcntl.h>?? #include?<sys/mman.h>?? #include?<stdlib.h>?? #include?<string.h>??int?main(?void?)?? {??int?fd;??char?*buffer;??char?*mapBuf;??fd?=?open("/dev/mmap_driver",?O_RDWR);//打開(kāi)設(shè)備文件,內(nèi)核就能獲取設(shè)備文件的索引節(jié)點(diǎn),填充inode結(jié)構(gòu)??if(fd<0)??{??printf("open?device?is?error,fd?=?%d\n",fd);??return?-1;??}??/*測(cè)試一:查看內(nèi)存映射段*/??printf("before?mmap\n");??sleep(15);//睡眠15秒,查看映射前的內(nèi)存圖cat?/proc/pid/maps??buffer?=?(char?*)malloc(1024);??memset(buffer,?0,?1024);??mapBuf?=?mmap(NULL,?1024,?PROT_READ|PROT_WRITE,?MAP_SHARED,?fd,?0);//內(nèi)存映射,會(huì)調(diào)用驅(qū)動(dòng)的mmap函數(shù)??printf("after?mmap\n");??sleep(15);//睡眠15秒,在命令行查看映射后的內(nèi)存圖,如果多出了映射段,說(shuō)明映射成功??/*測(cè)試二:往映射段讀寫(xiě)數(shù)據(jù),看是否成功*/??strcpy(mapBuf,?"Driver?Test");//向映射段寫(xiě)數(shù)據(jù)??memset(buffer,?0,?1024);??strcpy(buffer,?mapBuf);//從映射段讀取數(shù)據(jù)??printf("buf?=?%s\n",?buffer);//如果讀取出來(lái)的數(shù)據(jù)和寫(xiě)入的數(shù)據(jù)一致,說(shuō)明映射段的確成功了??munmap(mapBuf,?1024);//去除映射??free(buffer);??close(fd);//關(guān)閉文件,最終調(diào)用驅(qū)動(dòng)的close??return?0;?? }??

下面是makefile文件

ifneq?($(KERNELRELEASE),)??obj-m?:=?mmap_driver.o??else?? KDIR?:=?/lib/modules/3.2.0-52-generic/build??all:??make?-C?$(KDIR)?M=$(PWD)?modules?? clean:??rm?-f?*.ko?*.o?*.mod.o?*.mod.c?*~?*.symvers?*.order??endif??

下面命令演示一下驅(qū)動(dòng)程序的編譯、安裝、測(cè)試過(guò)程(注:其他用戶(hù)在mknod之后還需要chmod改變權(quán)限)

# make??? //編譯驅(qū)動(dòng)

# insmod mmap_driver.ko??? //安裝驅(qū)動(dòng)

# mknod?/dev/mmap_driver c 999 0??? //創(chuàng)建設(shè)備文件

# gcc test_mmap.c -o test.o??? //編譯應(yīng)用程序

# ./test.o??? //運(yùn)行應(yīng)用程序來(lái)測(cè)試驅(qū)動(dòng)程序


拓展:

關(guān)于這個(gè)過(guò)程,涉及一些術(shù)語(yǔ)

(1)設(shè)備文件:linux中對(duì)硬件虛擬成設(shè)備文件,對(duì)普通文件的各種操作均適用于設(shè)備文件

(2)索引節(jié)點(diǎn):linux使用索引節(jié)點(diǎn)來(lái)記錄文件信息(如文件長(zhǎng)度、創(chuàng)建修改時(shí)間),它存儲(chǔ)在磁盤(pán)中,讀入內(nèi)存后就是一個(gè)inode結(jié)構(gòu)體,文件系統(tǒng)維護(hù)了一個(gè)索引節(jié)點(diǎn)的數(shù)組,每個(gè)元素都和文件或者目錄一一對(duì)應(yīng)。

(3)主設(shè)備號(hào):如上面的999,表示設(shè)備的類(lèi)型,比如該設(shè)備是lcd還是usb等

(4)次設(shè)備號(hào):如上面的0,表示該類(lèi)設(shè)備上的不同設(shè)備

(5)文件(普通文件或設(shè)備文件)的三個(gè)結(jié)構(gòu)

??????? ①文件操作:struct file_operations

??????? ②文件對(duì)象:struct file

??????? ③文件索引節(jié)點(diǎn):struct inode

關(guān)于驅(qū)動(dòng)程序中內(nèi)存映射的實(shí)現(xiàn),先了解一下open和close的流程

(1)設(shè)備驅(qū)動(dòng)open流程

①應(yīng)用程序調(diào)用open("/dev/mmap_driver", O_RDWR);

②Open就會(huì)通過(guò)VFS找到該設(shè)備的索引節(jié)點(diǎn)(inode),mknod的時(shí)候會(huì)根據(jù)設(shè)備號(hào)把驅(qū)動(dòng)程序的file_operations結(jié)構(gòu)填充到索引節(jié)點(diǎn)中(關(guān)于mknod?/dev/mmap_driver c 999 0,這條指令創(chuàng)建了設(shè)備文件,在安裝驅(qū)動(dòng)(insmod)的時(shí)候,會(huì)運(yùn)行驅(qū)動(dòng)程序的初始化程序(module_init),在初始化程序中,會(huì)注冊(cè)它的主設(shè)備號(hào)到系統(tǒng)中(cdev_add),如果mknod時(shí)的主設(shè)備號(hào)999在系統(tǒng)中不存在,即和注冊(cè)的主設(shè)備號(hào)不同,則上面的指令會(huì)執(zhí)行失敗,就創(chuàng)建不了設(shè)備文件)

③然后根據(jù)設(shè)備文件的索引節(jié)點(diǎn)中的file_operations中的open指針,就調(diào)用驅(qū)動(dòng)的open方法了。

④生成一個(gè)文件對(duì)象files_struct結(jié)構(gòu),系統(tǒng)維護(hù)一個(gè)files_struct的鏈表,表示系統(tǒng)中所有打開(kāi)的文件

⑤返回文件描述符fd,把fd加入到進(jìn)程的文件描述符表中

(2)設(shè)備驅(qū)動(dòng)close流程

應(yīng)用程序調(diào)用close(fd),最終可調(diào)用驅(qū)動(dòng)的close,為什么根據(jù)一個(gè)簡(jiǎn)單的int型fd就可以找到驅(qū)動(dòng)的close函數(shù)?這就和上面說(shuō)的三個(gè)結(jié)構(gòu)(struct file_operations、struct file、struct inode)息息相關(guān)了,假如fd = 3

(3)設(shè)備驅(qū)動(dòng)mmap流程

由open和close得知,同理,應(yīng)用程序調(diào)用mmap最終也會(huì)調(diào)用到驅(qū)動(dòng)程序中mmap方法

①應(yīng)用程序test.mmap.c中mmap函數(shù)

void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:映射后虛擬地址的起始地址,通常為NULL,內(nèi)核自動(dòng)分配

length:映射區(qū)的大小

prot:頁(yè)面訪問(wèn)權(quán)限(PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE)

flags:參考網(wǎng)絡(luò)資料

fd:文件描述符

offset:文件映射開(kāi)始偏移量

②驅(qū)動(dòng)程序的mmap_driver.c中mmap函數(shù)

上面說(shuō)了,mmap的主要工作是把設(shè)備地址映射到進(jìn)程虛擬地址,也即是一個(gè)vm_area_struct的結(jié)構(gòu)體,這里說(shuō)的映射,是一個(gè)很懸的東西,那它在程序中的表現(xiàn)是什么呢?——頁(yè)表,沒(méi)錯(cuò),就是頁(yè)表,映射就是要建立頁(yè)表。進(jìn)程地址空間就可以通過(guò)頁(yè)表(軟件)和MMU(硬件)映射到設(shè)備地址上了

virt_to_phys(buf),buf是在open時(shí)申請(qǐng)的地址,這里使用virt_to_phys把buf轉(zhuǎn)換成物理地址,是模擬了一個(gè)硬件設(shè)備,即把虛擬設(shè)備映射到虛擬地址,在實(shí)際中可以直接使用物理地址。

總結(jié)

①?gòu)囊陨峡吹?#xff0c;內(nèi)核各個(gè)模塊錯(cuò)綜復(fù)雜、相互交叉

②單純一個(gè)小小驅(qū)動(dòng)模塊,就涉及了進(jìn)程管理(進(jìn)程地址空間)、內(nèi)存管理(頁(yè)表與頁(yè)幀映射)、虛擬文件系統(tǒng)(structfile、structinode)

③并不是所有設(shè)備驅(qū)動(dòng)都可以使用mmap來(lái)映射,比如像串口和其他面向流的設(shè)備,并且必須按照頁(yè)大小進(jìn)行映射。



?ELF文件格式分析

一、概述

1.ELF全稱(chēng)Executable and Linkable Format,可執(zhí)行連接格式,ELF格式的文件用于存儲(chǔ)Linux程序。ELF文件(目標(biāo)文件)格式主要三種:

  • 可重定向文件:文件保存著代碼和適當(dāng)?shù)臄?shù)據(jù),用來(lái)和其他的目標(biāo)文件一起來(lái)創(chuàng)建一個(gè)可執(zhí)行文件或者是一個(gè)共享目標(biāo)文件。(目標(biāo)文件或者靜態(tài)庫(kù)文件,即linux通常后綴為.a和.o的文件)
  • 可執(zhí)行文件:文件保存著一個(gè)用來(lái)執(zhí)行的程序。(例如bash,gcc等)
  • 共享目標(biāo)文件:共享庫(kù)。文件保存著代碼和合適的數(shù)據(jù),用來(lái)被下連接編輯器和動(dòng)態(tài)鏈接器鏈接。(linux下后綴為.so的文件。)
    目標(biāo)文件既要參與程序鏈接又要參與程序執(zhí)行:

一般的 ELF 文件包括三個(gè)索引表:ELF header,Program header table,Section header table。

  • ELF header:在文件的開(kāi)始,保存了路線(xiàn)圖,描述了該文件的組織情況。
  • Program header table:告訴系統(tǒng)如何創(chuàng)建進(jìn)程映像。用來(lái)構(gòu)造進(jìn)程映像的目標(biāo)文件必須具有程序頭部表,可重定位文件不需要這個(gè)表。
  • Section header table:包含了描述文件節(jié)區(qū)的信息,每個(gè)節(jié)區(qū)在表中都有一項(xiàng),每一項(xiàng)給出諸如節(jié)區(qū)名稱(chēng)、節(jié)區(qū)大小這類(lèi)信息。用于鏈接的目標(biāo)文件必須包含節(jié)區(qū)頭部表,其他目標(biāo)文件可以有,也可以沒(méi)有這個(gè)表。

二、分析ELF文件頭(ELF header)

  • 進(jìn)入終端輸入:cd /usr/include 進(jìn)入include文件夾后查看elf.h文件,查看ELF的文件頭包含整個(gè)文件的控制結(jié)構(gòu)

  • 寫(xiě)一個(gè)小程序(hello 20135328)進(jìn)行編譯,生成hello可執(zhí)行文件。
    使用‘readelf –a hello’命令,都得到下面的ELF Header頭文件的信息,如下圖:

  • 通過(guò)上圖信息,可以得出Elf Header的Size為52bytes,所以可以使用hexdump工具將頭文件的16進(jìn)制表打開(kāi)。
    如下圖使用:‘hexdump -x hello -n 52 ’命令來(lái)查看hello文件頭的16進(jìn)制表(前52bytes)對(duì)格式進(jìn)行分析。

  • 第一行
    • 對(duì)應(yīng)e_ident[EI_NIDENT]。實(shí)際表示內(nèi)容為7f454c46010100010000000000000000,前四個(gè)字節(jié)7f454c46(0x45,0x4c,0x46是'e','l','f'對(duì)應(yīng)的ascii編碼)是一個(gè)魔數(shù),表示這是一個(gè)ELF對(duì)象。
    • 接下來(lái)的一個(gè)字節(jié)01表示是一個(gè)32位對(duì)象,接下來(lái)的一個(gè)字節(jié)01表示是小端法表示,再接下來(lái)的一個(gè)字節(jié)01表示文件頭版本。剩下的默認(rèn)都設(shè)置為0.
  • 第二行
    • e_type值為0x0002,表示是一個(gè)可執(zhí)行文件
    • 。e_machine值為0x003e,表示是Advanced Micro Devices X86-64處理器體系結(jié)構(gòu)。
    • e_version值為0x00000001,表示是當(dāng)前版本。
    • e_entry值為0x 08048320,表示入口點(diǎn)。
  • 第三行
    • e_phof f值為0x1178,表示程序頭表。
    • e_shoff值為0x0034,表示段表的偏移地址。
  • 第四行
    • e_flags值為0x001e,表示未知處理器特定標(biāo)志。
    • e_ehsize值為0x0034,表示elf文件頭大小(正好是52bytes)。
    • e_phentsize表示一個(gè)program header表中的入口的長(zhǎng)度,值為0x0020。
    • e_phnum的值為0x0009,給出program header表中的入口數(shù)目。
    • e_shentsize值為0x0028表示段頭大小為40個(gè)字節(jié)。e_shnum值為0x001e,表示段表入口有30個(gè)。
    • e_shstrndx值為0x001b,表示段名串表的在段表中的索引號(hào)。

      三、通過(guò)文件頭找到section header table,理解其內(nèi)容

  • file elf顯示生成的目標(biāo)文件hello的類(lèi)型
  • elf是一個(gè)可執(zhí)行文件。輸入:ls –l hello查看hello的大小:
  • 如圖可知,hello大小為7336字節(jié)。
    輸入:hexdump –x hello來(lái)用16進(jìn)制的數(shù)字來(lái)顯示hello的內(nèi)容
    (其中,第二列是16進(jìn)制表示的偏移地址)
  • 輸入:objdump –x hello來(lái)顯示hello中各個(gè)段以及符號(hào)表的相關(guān)信息:
  • 輸入:readelf –a hello來(lái)查看各個(gè)段信息:
  • ELF文件頭信息:
  • 段表Section header table:
  • 符號(hào)表 Symbol table:


    四、通過(guò)section header table找到各section

    在一個(gè)ELF文件中有一個(gè)section header table,通過(guò)它我們可以定位到所有的 section,而 ELF header 中的e_shoff 變量就是保存 section header table 入口對(duì)文件頭的偏移量。而每個(gè) section 都會(huì)對(duì)應(yīng)一個(gè) section header ,所以只要在 section header table 中找到每個(gè) section header,就可以通過(guò) section header 找到你想要的 section。

下面以可執(zhí)行文件hello為例,以保存代碼段的 section 為例來(lái)講解讀取某個(gè)section 的過(guò)程。
使用‘vi /usr/include/elf.h ’命令查看Sections Header的結(jié)構(gòu)體:

由上面分析可知,section headers table中的每一個(gè)section header所占的size均為64字節(jié),ELF header得到了e_shoff變量的值為0X0034,也就是table入口的偏移量,通過(guò)看e_shnum值為0x001e,表示段表入口有30個(gè)。
所以從0x00000034開(kāi)始有30個(gè)段,每個(gè)段占40個(gè)字節(jié)大小,輸入 hexdump hello查看:

  • 第一個(gè)段,其中內(nèi)容全部為0,所以不表示任何段。
  • 第二個(gè)段,為.interp段

  • 第三個(gè)段,為.note.ABI-tag段

  • 第四個(gè)段,為.note.gnu.build-i段

  • 第五個(gè)段,為.gnu.hash段

.......

  • 第十四個(gè)段,為.text段

  • 第十六個(gè)段,為.rodata段

  • 第二十五個(gè)段,為.data段

  • 第二十六個(gè)段,為.bss段

  • 第二十九個(gè)段, 為.symtab段

  • 第三十個(gè)段, 為.strtab段

我們用readelf 命令去查看.text這個(gè) section 中的內(nèi)容,
輸入readelf –x 13 hello,(.text前面的標(biāo)號(hào)為13)對(duì)13索引號(hào)的.text的section的內(nèi)容進(jìn)行查看:

下面用 hexdump 的方法去讀取.text這個(gè) section 中的內(nèi)容,通過(guò)看section header中.text中offset和size分別是0x320和0x192
輸入 hexdump –C hello
找到320后的192個(gè)


得到了和上面的readelf得到的相同。
使用下面命令對(duì)hello的文本段(.text)進(jìn)行反匯編:
objdump –d hello 得到如下圖:


可以看出,使用反匯編的16進(jìn)制數(shù)據(jù)和前面查找到的是相同的。

五、理解常見(jiàn).text .strtab .symtab .rodata等section

  • text section是可執(zhí)行指令的集合,.data和.text都是屬于PROGBITS類(lèi)型的section,這是將來(lái)要運(yùn)行的程序與代碼。查詢(xún)段表可知.text section的位偏移為0x0000320,size為0x0000192。
  • strtab section是屬于STRTAB類(lèi)型的section,可以在文件中看到,它存著字符串,儲(chǔ)存著符號(hào)的名字。位偏移為0x000106f,size為0x0000106
  • symtab section存放所有section中定義的符號(hào)名字,比如“data_items”,“start_loop”。 .symtab section是屬于SYMTAB類(lèi)型的section,它描述了.strtab中的符號(hào)在“內(nèi)存”中對(duì)應(yīng)的“內(nèi)存地址”。 位偏移為0x0001628,size為0x0000430。
  • rodata section,ro代表read only。位偏移為0x000050c,size為0x00000b0。


  • readelf命令和ELF文件詳解?

    ELF(Executable and Linking Format)是一個(gè)定義了目標(biāo)文件內(nèi)部信息如何組成和組織的文件格式。內(nèi)核會(huì)根據(jù)這些信息加載可執(zhí)行文件,內(nèi)核根據(jù)這些信息可以知道從文件哪里獲取代碼,從哪里獲取初始化數(shù)據(jù),在哪里應(yīng)該加載共享庫(kù),等信息。?
    ELF文件有下面三種類(lèi)型:?
    1.目標(biāo)文件

    $ gcc -c test.c?
    得到的test.o就是目標(biāo)文件,目標(biāo)文件通過(guò)鏈接可生成可執(zhí)行文件。?
    靜態(tài)庫(kù)其實(shí)也算目標(biāo)文件,靜態(tài)庫(kù)是通過(guò)ar命令將目標(biāo)打包為.a文件。?
    如:ar crv libtest.a test.o

    2.可執(zhí)行文件?
    $gcc -o test test.c?
    得到的test文件就是可執(zhí)行的二進(jìn)制文件。

    3.共享庫(kù)?
    $ gcc test.c -fPIC -shared -o libtest.so?
    得到的文件listtest.so就是共享庫(kù)。

    可以通過(guò)readelf來(lái)區(qū)分上面三種類(lèi)型的ELF文件,每種類(lèi)型文件的頭部信息是不一樣的。?
    $readelf -h test.o?
    目標(biāo)文件

    ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: REL (Relocatable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x0Start of program headers: 0 (bytes into file)Start of section headers: 456 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 0 (bytes)Number of program headers: 0Size of section headers: 64 (bytes)Number of section headers: 13Section header string table index: 10

    $readelf -h test?

    可執(zhí)行文件

    ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x400420Start of program headers: 64 (bytes into file)Start of section headers: 2696 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 8Size of section headers: 64 (bytes)Number of section headers: 30Section header string table index: 27

    $readelf -h libtest.so?
    共享庫(kù)

    ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Shared object file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x570Start of program headers: 64 (bytes into file)Start of section headers: 2768 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 6Size of section headers: 64 (bytes)Number of section headers: 29Section header string table index: 26

    下面是test.c文件內(nèi)容:

    #include<stdio.h>int global_data = 4; int global_data_2; int main(int argc, char **argv) { int local_data = 3; printf("Hello World\n"); printf("global_data = %d\n", global_data); printf("global_data_2 = %d\n", global_data_2); printf("local_data = %d\n", local_data); return (0); }

    $gcc -o test test.c?
    生成可執(zhí)行文件test,然后使用readelf對(duì)其進(jìn)行分析。

    $readelf -h test?
    下面是輸出結(jié)果:

    ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x400420Start of program headers: 64 (bytes into file)Start of section headers: 2696 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 8Size of section headers: 64 (bytes)Number of section headers: 30Section header string table index: 27

    上面的信息可以告訴我們什么信息??
    1.根據(jù)Class、Type和Machine,可以知道該文件在X86-64位機(jī)器上生成的64位可執(zhí)行文件。?
    2.根據(jù)Entry point address,可以知道當(dāng)該程序啟動(dòng)時(shí)從虛擬地址0x400420處開(kāi)始運(yùn)行。這個(gè)地址并不是main函數(shù)的地址,而是_start函數(shù)的地址,_start由鏈接器創(chuàng)建,_start是為了初始化程序。通過(guò)這個(gè)命令可以看到_start函數(shù),objdump -d -j .text test?
    3.根據(jù)Number of program headers,可以知道該程序有8個(gè)段。?
    4.根據(jù)Number of section headers,可以知道該程序有30個(gè)區(qū)。?
    區(qū)中存儲(chǔ)的信息是用來(lái)鏈接使用的,主要包括:程序代碼、程序數(shù)據(jù)(變量)、重定向信息等。比如:Code section保存的是代碼,data section保存的是初始化或未初始化的數(shù)據(jù),等等。

    Linux內(nèi)核無(wú)法以區(qū)的概念來(lái)識(shí)別可執(zhí)行文件。內(nèi)核使用包括連續(xù)頁(yè)的VMA(virtual memory area)來(lái)識(shí)別進(jìn)程。在每個(gè)VMA中可能映射了一個(gè)或多個(gè)區(qū)。每個(gè)VMA代表一個(gè)ELF文件的段。?
    那么,內(nèi)核如何知道哪個(gè)區(qū)屬于某個(gè)VMA(段)?映射關(guān)系保存在Program Header Table(PHT)中。

    下面查看區(qū)的內(nèi)容:?

    $readelf -S test

    There are 30 section headers, starting at offset 0xa88:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .interp PROGBITS 0000000000400200 00000200000000000000001c 0000000000000000 A 0 0 1[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c0000000000000020 0000000000000000 A 0 0 4[ 3] .note.gnu.build-i NOTE 000000000040023c 0000023c0000000000000024 0000000000000000 A 0 0 4[ 4] .gnu.hash GNU_HASH 0000000000400260 00000260000000000000001c 0000000000000000 A 5 0 8[ 5] .dynsym DYNSYM 0000000000400280 000002800000000000000078 0000000000000018 A 6 1 8[ 6] .dynstr STRTAB 00000000004002f8 000002f80000000000000044 0000000000000000 A 0 0 1[ 7] .gnu.version VERSYM 000000000040033c 0000033c000000000000000a 0000000000000002 A 5 0 2[ 8] .gnu.version_r VERNEED 0000000000400348 000003480000000000000020 0000000000000000 A 6 1 8[ 9] .rela.dyn RELA 0000000000400368 000003680000000000000018 0000000000000018 A 5 0 8[10] .rela.plt RELA 0000000000400380 000003800000000000000048 0000000000000018 A 5 12 8[11] .init PROGBITS 00000000004003c8 000003c80000000000000018 0000000000000000 AX 0 0 4[12] .plt PROGBITS 00000000004003e0 000003e00000000000000040 0000000000000010 AX 0 0 4[13] .text PROGBITS 0000000000400420 000004200000000000000238 0000000000000000 AX 0 0 16[14] .fini PROGBITS 0000000000400658 00000658000000000000000e 0000000000000000 AX 0 0 4[15] .rodata PROGBITS 0000000000400668 000006680000000000000053 0000000000000000 A 0 0 8[16] .eh_frame_hdr PROGBITS 00000000004006bc 000006bc0000000000000024 0000000000000000 A 0 0 4[17] .eh_frame PROGBITS 00000000004006e0 000006e0000000000000007c 0000000000000000 A 0 0 8[18] .ctors PROGBITS 0000000000600760 000007600000000000000010 0000000000000000 WA 0 0 8[19] .dtors PROGBITS 0000000000600770 000007700000000000000010 0000000000000000 WA 0 0 8[20] .jcr PROGBITS 0000000000600780 000007800000000000000008 0000000000000000 WA 0 0 8[21] .dynamic DYNAMIC 0000000000600788 000007880000000000000190 0000000000000010 WA 6 0 8[22] .got PROGBITS 0000000000600918 000009180000000000000008 0000000000000008 WA 0 0 8[23] .got.plt PROGBITS 0000000000600920 000009200000000000000030 0000000000000008 WA 0 0 8[24] .data PROGBITS 0000000000600950 000009500000000000000008 0000000000000000 WA 0 0 4[25] .bss NOBITS 0000000000600958 000009580000000000000018 0000000000000000 WA 0 0 8[26] .comment PROGBITS 0000000000000000 00000958000000000000002c 0000000000000001 MS 0 0 1[27] .shstrtab STRTAB 0000000000000000 0000098400000000000000fe 0000000000000000 0 0 1[28] .symtab SYMTAB 0000000000000000 000012080000000000000648 0000000000000018 29 46 8[29] .strtab STRTAB 0000000000000000 00001850000000000000021e 0000000000000000 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)

    .text區(qū)存儲(chǔ)的是程序的代碼(二進(jìn)制指令),該區(qū)的標(biāo)志為X表示可執(zhí)行。

    下面使用objdump反匯編查看.text的內(nèi)容:?
    $objdump -d -j .text test?
    -d選項(xiàng)告訴objdump反匯編機(jī)器碼,-j選項(xiàng)告訴objdump只關(guān)心.text區(qū)。

    test: file format elf64-x86-64Disassembly of section .text:0000000000400420 <_start>:400420: 31 ed xor %ebp,%ebp400422: 49 89 d1 mov %rdx,%r9400425: 5e pop %rsi400426: 48 89 e2 mov %rsp,%rdx400429: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp40042d: 50 push %rax40042e: 54 push %rsp40042f: 49 c7 c0 80 05 40 00 mov $0x400580,%r8400436: 48 c7 c1 90 05 40 00 mov $0x400590,%rcx40043d: 48 c7 c7 04 05 40 00 mov $0x400504,%rdi400444: e8 c7 ff ff ff callq 400410 <__libc_start_main@plt>400449: f4 hlt 40044a: 90 nop40044b: 90 nop000000000040044c <call_gmon_start>:40044c: 48 83 ec 08 sub $0x8,%rsp400450: 48 8b 05 c1 04 20 00 mov 0x2004c1(%rip),%rax # 600918 <_DYNAMIC+0x190>400457: 48 85 c0 test %rax,%rax40045a: 74 02 je 40045e <call_gmon_start+0x12>40045c: ff d0 callq *%rax40045e: 48 83 c4 08 add $0x8,%rsp400462: c3 retq 400463: 90 nop400464: 90 nop400465: 90 nop400466: 90 nop400467: 90 nop400468: 90 nop400469: 90 nop40046a: 90 nop40046b: 90 nop40046c: 90 nop40046d: 90 nop40046e: 90 nop40046f: 90 nop0000000000400470 <__do_global_dtors_aux>:400470: 55 push %rbp400471: 48 89 e5 mov %rsp,%rbp400474: 53 push %rbx400475: 48 83 ec 08 sub $0x8,%rsp400479: 80 3d d8 04 20 00 00 cmpb $0x0,0x2004d8(%rip) # 600958 <__bss_start>400480: 75 4b jne 4004cd <__do_global_dtors_aux+0x5d>400482: bb 78 07 60 00 mov $0x600778,%ebx400487: 48 8b 05 d2 04 20 00 mov 0x2004d2(%rip),%rax # 600960 <dtor_idx.6349>40048e: 48 81 eb 70 07 60 00 sub $0x600770,%rbx400495: 48 c1 fb 03 sar $0x3,%rbx400499: 48 83 eb 01 sub $0x1,%rbx40049d: 48 39 d8 cmp %rbx,%rax4004a0: 73 24 jae 4004c6 <__do_global_dtors_aux+0x56>4004a2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)4004a8: 48 83 c0 01 add $0x1,%rax4004ac: 48 89 05 ad 04 20 00 mov %rax,0x2004ad(%rip) # 600960 <dtor_idx.6349>4004b3: ff 14 c5 70 07 60 00 callq *0x600770(,%rax,8)4004ba: 48 8b 05 9f 04 20 00 mov 0x20049f(%rip),%rax # 600960 <dtor_idx.6349>4004c1: 48 39 d8 cmp %rbx,%rax4004c4: 72 e2 jb 4004a8 <__do_global_dtors_aux+0x38>4004c6: c6 05 8b 04 20 00 01 movb $0x1,0x20048b(%rip) # 600958 <__bss_start>4004cd: 48 83 c4 08 add $0x8,%rsp4004d1: 5b pop %rbx4004d2: c9 leaveq 4004d3: c3 retq 4004d4: 66 66 66 2e 0f 1f 84 data32 data32 nopw %cs:0x0(%rax,%rax,1)4004db: 00 00 00 00 00 00000000004004e0 <frame_dummy>:4004e0: 48 83 3d 98 02 20 00 cmpq $0x0,0x200298(%rip) # 600780 <__JCR_END__>4004e7: 00 4004e8: 55 push %rbp4004e9: 48 89 e5 mov %rsp,%rbp4004ec: 74 12 je 400500 <frame_dummy+0x20>4004ee: b8 00 00 00 00 mov $0x0,%eax4004f3: 48 85 c0 test %rax,%rax4004f6: 74 08 je 400500 <frame_dummy+0x20>4004f8: bf 80 07 60 00 mov $0x600780,%edi4004fd: c9 leaveq 4004fe: ff e0 jmpq *%rax400500: c9 leaveq 400501: c3 retq 400502: 90 nop400503: 90 nop0000000000400504 <main>:400504: 55 push %rbp400505: 48 89 e5 mov %rsp,%rbp400508: 48 83 ec 20 sub $0x20,%rsp40050c: 89 7d ec mov %edi,-0x14(%rbp)40050f: 48 89 75 e0 mov %rsi,-0x20(%rbp)400513: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%rbp)40051a: bf 78 06 40 00 mov $0x400678,%edi40051f: e8 dc fe ff ff callq 400400 <puts@plt>400524: 8b 15 2a 04 20 00 mov 0x20042a(%rip),%edx # 600954 <global_data>40052a: b8 84 06 40 00 mov $0x400684,%eax40052f: 89 d6 mov %edx,%esi400531: 48 89 c7 mov %rax,%rdi400534: b8 00 00 00 00 mov $0x0,%eax400539: e8 b2 fe ff ff callq 4003f0 <printf@plt>40053e: 8b 15 24 04 20 00 mov 0x200424(%rip),%edx # 600968 <global_data_2>400544: b8 96 06 40 00 mov $0x400696,%eax400549: 89 d6 mov %edx,%esi40054b: 48 89 c7 mov %rax,%rdi40054e: b8 00 00 00 00 mov $0x0,%eax400553: e8 98 fe ff ff callq 4003f0 <printf@plt>400558: b8 aa 06 40 00 mov $0x4006aa,%eax40055d: 8b 55 fc mov -0x4(%rbp),%edx400560: 89 d6 mov %edx,%esi400562: 48 89 c7 mov %rax,%rdi400565: b8 00 00 00 00 mov $0x0,%eax40056a: e8 81 fe ff ff callq 4003f0 <printf@plt>40056f: b8 00 00 00 00 mov $0x0,%eax400574: c9 leaveq 400575: c3 retq 400576: 90 nop400577: 90 nop400578: 90 nop400579: 90 nop40057a: 90 nop40057b: 90 nop40057c: 90 nop40057d: 90 nop40057e: 90 nop40057f: 90 nop0000000000400580 <__libc_csu_fini>:400580: f3 c3 repz retq 400582: 66 66 66 66 66 2e 0f data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)400589: 1f 84 00 00 00 00 00 0000000000400590 <__libc_csu_init>:400590: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp)400595: 4c 89 64 24 e0 mov %r12,-0x20(%rsp)40059a: 48 8d 2d bb 01 20 00 lea 0x2001bb(%rip),%rbp # 60075c <__init_array_end>4005a1: 4c 8d 25 b4 01 20 00 lea 0x2001b4(%rip),%r12 # 60075c <__init_array_end>4005a8: 4c 89 6c 24 e8 mov %r13,-0x18(%rsp)4005ad: 4c 89 74 24 f0 mov %r14,-0x10(%rsp)4005b2: 4c 89 7c 24 f8 mov %r15,-0x8(%rsp)4005b7: 48 89 5c 24 d0 mov %rbx,-0x30(%rsp)4005bc: 48 83 ec 38 sub $0x38,%rsp4005c0: 4c 29 e5 sub %r12,%rbp4005c3: 41 89 fd mov %edi,%r13d4005c6: 49 89 f6 mov %rsi,%r144005c9: 48 c1 fd 03 sar $0x3,%rbp4005cd: 49 89 d7 mov %rdx,%r154005d0: e8 f3 fd ff ff callq 4003c8 <_init>4005d5: 48 85 ed test %rbp,%rbp4005d8: 74 1c je 4005f6 <__libc_csu_init+0x66>4005da: 31 db xor %ebx,%ebx4005dc: 0f 1f 40 00 nopl 0x0(%rax)4005e0: 4c 89 fa mov %r15,%rdx4005e3: 4c 89 f6 mov %r14,%rsi4005e6: 44 89 ef mov %r13d,%edi4005e9: 41 ff 14 dc callq *(%r12,%rbx,8)4005ed: 48 83 c3 01 add $0x1,%rbx4005f1: 48 39 eb cmp %rbp,%rbx4005f4: 72 ea jb 4005e0 <__libc_csu_init+0x50>4005f6: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx4005fb: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp400600: 4c 8b 64 24 18 mov 0x18(%rsp),%r12400605: 4c 8b 6c 24 20 mov 0x20(%rsp),%r1340060a: 4c 8b 74 24 28 mov 0x28(%rsp),%r1440060f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15400614: 48 83 c4 38 add $0x38,%rsp400618: c3 retq 400619: 90 nop40061a: 90 nop40061b: 90 nop40061c: 90 nop40061d: 90 nop40061e: 90 nop40061f: 90 nop0000000000400620 <__do_global_ctors_aux>:400620: 55 push %rbp400621: 48 89 e5 mov %rsp,%rbp400624: 53 push %rbx400625: 48 83 ec 08 sub $0x8,%rsp400629: 48 8b 05 30 01 20 00 mov 0x200130(%rip),%rax # 600760 <__CTOR_LIST__>400630: 48 83 f8 ff cmp $0xffffffffffffffff,%rax400634: 74 19 je 40064f <__do_global_ctors_aux+0x2f>400636: bb 60 07 60 00 mov $0x600760,%ebx40063b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)400640: 48 83 eb 08 sub $0x8,%rbx400644: ff d0 callq *%rax400646: 48 8b 03 mov (%rbx),%rax400649: 48 83 f8 ff cmp $0xffffffffffffffff,%rax40064d: 75 f1 jne 400640 <__do_global_ctors_aux+0x20>40064f: 48 83 c4 08 add $0x8,%rsp400653: 5b pop %rbx400654: c9 leaveq 400655: c3 retq 400656: 90 nop400657: 90 nop

    下面使用objdump反匯編查看.data的內(nèi)容:?
    $objdump -d -j .data test?
    .data區(qū)保存的是初始化的全局變量。

    test: file format elf64-x86-64Disassembly of section .data:0000000000600950 <__data_start>:600950: 00 00 add %al,(%rax)...0000000000600954 <global_data>:600954: 04 00 00 00

    下面使用objdump反匯編查看.bss的內(nèi)容:?
    $objdump -d -j .bss test?
    .bss區(qū)保存的是未初始化的全局變量,linux會(huì)默認(rèn)將未初始化的變量置為0。

    test: file format elf64-x86-64Disassembly of section .bss:0000000000600958 <completed.6347>:...0000000000600960 <dtor_idx.6349>:...0000000000600968 <global_data_2>:

    下面命令可以看到test文件中所有的符號(hào):?
    $readelf -s test?
    Value的值是符號(hào)的地址。

    Symbol table '.dynsym' contains 5 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)Symbol table '.symtab' contains 67 entries:Num: Value Size Type Bind Vis Ndx Name0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000400200 0 SECTION LOCAL DEFAULT 1 2: 000000000040021c 0 SECTION LOCAL DEFAULT 2 3: 000000000040023c 0 SECTION LOCAL DEFAULT 3 4: 0000000000400260 0 SECTION LOCAL DEFAULT 4 5: 0000000000400280 0 SECTION LOCAL DEFAULT 5 6: 00000000004002f8 0 SECTION LOCAL DEFAULT 6 7: 000000000040033c 0 SECTION LOCAL DEFAULT 7 8: 0000000000400348 0 SECTION LOCAL DEFAULT 8 9: 0000000000400368 0 SECTION LOCAL DEFAULT 9 10: 0000000000400380 0 SECTION LOCAL DEFAULT 10 11: 00000000004003c8 0 SECTION LOCAL DEFAULT 11 12: 00000000004003e0 0 SECTION LOCAL DEFAULT 12 13: 0000000000400420 0 SECTION LOCAL DEFAULT 13 14: 0000000000400658 0 SECTION LOCAL DEFAULT 14 15: 0000000000400668 0 SECTION LOCAL DEFAULT 15 16: 00000000004006bc 0 SECTION LOCAL DEFAULT 16 17: 00000000004006e0 0 SECTION LOCAL DEFAULT 17 18: 0000000000600760 0 SECTION LOCAL DEFAULT 18 19: 0000000000600770 0 SECTION LOCAL DEFAULT 19 20: 0000000000600780 0 SECTION LOCAL DEFAULT 20 21: 0000000000600788 0 SECTION LOCAL DEFAULT 21 22: 0000000000600918 0 SECTION LOCAL DEFAULT 22 23: 0000000000600920 0 SECTION LOCAL DEFAULT 23 24: 0000000000600950 0 SECTION LOCAL DEFAULT 24 25: 0000000000600958 0 SECTION LOCAL DEFAULT 25 26: 0000000000000000 0 SECTION LOCAL DEFAULT 26 27: 000000000040044c 0 FUNC LOCAL DEFAULT 13 call_gmon_start28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c29: 0000000000600760 0 OBJECT LOCAL DEFAULT 18 __CTOR_LIST__30: 0000000000600770 0 OBJECT LOCAL DEFAULT 19 __DTOR_LIST__31: 0000000000600780 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__32: 0000000000400470 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux33: 0000000000600958 1 OBJECT LOCAL DEFAULT 25 completed.634734: 0000000000600960 8 OBJECT LOCAL DEFAULT 25 dtor_idx.634935: 00000000004004e0 0 FUNC LOCAL DEFAULT 13 frame_dummy36: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c37: 0000000000600768 0 OBJECT LOCAL DEFAULT 18 __CTOR_END__38: 0000000000400758 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__39: 0000000000600780 0 OBJECT LOCAL DEFAULT 20 __JCR_END__40: 0000000000400620 0 FUNC LOCAL DEFAULT 13 __do_global_ctors_aux41: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c42: 0000000000600920 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_43: 000000000060075c 0 NOTYPE LOCAL DEFAULT 18 __init_array_end44: 000000000060075c 0 NOTYPE LOCAL DEFAULT 18 __init_array_start45: 0000000000600788 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC46: 0000000000600950 0 NOTYPE WEAK DEFAULT 24 data_start47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.548: 0000000000400580 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini49: 0000000000400420 0 FUNC GLOBAL DEFAULT 13 _start50: 0000000000600968 4 OBJECT GLOBAL DEFAULT 25 global_data_251: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__52: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.554: 0000000000400658 0 FUNC GLOBAL DEFAULT 14 _fini55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_56: 0000000000400668 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used57: 0000000000600950 0 NOTYPE GLOBAL DEFAULT 24 __data_start58: 0000000000400670 0 OBJECT GLOBAL HIDDEN 15 __dso_handle59: 0000000000600778 0 OBJECT GLOBAL HIDDEN 19 __DTOR_END__60: 0000000000400590 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init61: 0000000000600958 0 NOTYPE GLOBAL DEFAULT ABS __bss_start62: 0000000000600970 0 NOTYPE GLOBAL DEFAULT ABS _end63: 0000000000600958 0 NOTYPE GLOBAL DEFAULT ABS _edata64: 0000000000600954 4 OBJECT GLOBAL DEFAULT 24 global_data65: 0000000000400504 114 FUNC GLOBAL DEFAULT 13 main66: 00000000004003c8 0 FUNC GLOBAL DEFAULT 11 _init

    下面命令來(lái)查看文件的段信息:?
    $readelf -l test?
    區(qū)到段的映射,基本上是按照區(qū)的順序進(jìn)行映射。?
    如果Flags為R和E,表示該段可讀和可執(zhí)行。?
    如果Flags為W,表示該段可寫(xiě)。?
    VirtAddr是每個(gè)段的虛擬起始地址。這個(gè)地址并不是位于真正內(nèi)存上的地址(物理地址)。

    Elf file type is EXEC (Executable file) Entry point 0x400420 There are 8 program headers, starting at offset 64Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignPHDR 0x0000000000000040 0x0000000000400040 0x00000000004000400x00000000000001c0 0x00000000000001c0 R E 8INTERP 0x0000000000000200 0x0000000000400200 0x00000000004002000x000000000000001c 0x000000000000001c R 1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD 0x0000000000000000 0x0000000000400000 0x00000000004000000x000000000000075c 0x000000000000075c R E 200000LOAD 0x0000000000000760 0x0000000000600760 0x00000000006007600x00000000000001f8 0x0000000000000210 RW 200000DYNAMIC 0x0000000000000788 0x0000000000600788 0x00000000006007880x0000000000000190 0x0000000000000190 RW 8NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c0x0000000000000044 0x0000000000000044 R 4GNU_EH_FRAME 0x00000000000006bc 0x00000000004006bc 0x00000000004006bc0x0000000000000024 0x0000000000000024 R 4GNU_STACK 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000000 0x0000000000000000 RW 8Section to Segment mapping:Segment Sections...00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07

    如上所示,段有多種類(lèi)型,下面介紹LOAD類(lèi)型?
    LOAD:該段的內(nèi)容從可執(zhí)行文件中獲取。Offset標(biāo)識(shí)內(nèi)核從文件讀取的位置。FileSiz標(biāo)識(shí)讀取多少字節(jié)。

    那么,執(zhí)行test之后的進(jìn)程的段布局是如何呢??
    可以通過(guò)cat /proc/pid/maps來(lái)查看。pid是進(jìn)程的pid。?
    但是該test運(yùn)行時(shí)間很短,可以使用gdb加斷點(diǎn)來(lái)運(yùn)行,或者在return語(yǔ)句之前加上sleep()。

    下面使用gdb加斷點(diǎn)的形式:

    GNU gdb (GDB) Red Hat Enterprise Linux (7.2-50.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /data/readyao/qqlive_zb_prj/server/cgi_push_post_replay/lib/test...(no debugging symbols found)...done. (gdb) b main Breakpoint 1 at 0x400508 (gdb) r Starting program: /data/readyao/qqlive_zb_prj/server/cgi_push_post_replay/lib/test [Thread debugging using libthread_db enabled]Breakpoint 1, 0x0000000000400508 in main () Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.49.tl1.x86_64

    $cat /proc/6929/maps?

    00400000-00401000 r-xp 00000000 ca:11 8626925 /test 00600000-00601000 rw-p 00000000 ca:11 8626925 /test 7ffff762d000-7ffff7644000 r-xp 00000000 ca:01 332328 /lib64/libpthread-2.12.so 7ffff7644000-7ffff7843000 ---p 00017000 ca:01 332328 /lib64/libpthread-2.12.so 7ffff7843000-7ffff7844000 r--p 00016000 ca:01 332328 /lib64/libpthread-2.12.so 7ffff7844000-7ffff7845000 rw-p 00017000 ca:01 332328 /lib64/libpthread-2.12.so 7ffff7845000-7ffff7849000 rw-p 00000000 00:00 0 7ffff7849000-7ffff784b000 r-xp 00000000 ca:01 332237 /lib64/libdl-2.12.so 7ffff784b000-7ffff7a4b000 ---p 00002000 ca:01 332237 /lib64/libdl-2.12.so 7ffff7a4b000-7ffff7a4c000 r--p 00002000 ca:01 332237 /lib64/libdl-2.12.so 7ffff7a4c000-7ffff7a4d000 rw-p 00003000 ca:01 332237 /lib64/libdl-2.12.so 7ffff7a4d000-7ffff7bd3000 r-xp 00000000 ca:01 332102 /lib64/libc-2.12.so 7ffff7bd3000-7ffff7dd3000 ---p 00186000 ca:01 332102 /lib64/libc-2.12.so 7ffff7dd3000-7ffff7dd7000 r--p 00186000 ca:01 332102 /lib64/libc-2.12.so 7ffff7dd7000-7ffff7dd8000 rw-p 0018a000 ca:01 332102 /lib64/libc-2.12.so 7ffff7dd8000-7ffff7ddd000 rw-p 00000000 00:00 0 7ffff7ddd000-7ffff7dfd000 r-xp 00000000 ca:01 332126 /lib64/ld-2.12.so 7ffff7ed9000-7ffff7edc000 rw-p 00000000 00:00 0 7ffff7eeb000-7ffff7eee000 r-xp 00000000 ca:01 336319 /lib64/libonion_security.so.1.0.13 7ffff7eee000-7ffff7fee000 ---p 00003000 ca:01 336319 /lib64/libonion_security.so.1.0.13 7ffff7fee000-7ffff7fef000 rw-p 00003000 ca:01 336319 /lib64/libonion_security.so.1.0.13 7ffff7fef000-7ffff7ffb000 rw-p 00000000 00:00 0 7ffff7ffb000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso] 7ffff7ffc000-7ffff7ffd000 r--p 0001f000 ca:01 332126 /lib64/ld-2.12.so 7ffff7ffd000-7ffff7ffe000 rw-p 00020000 ca:01 332126 /lib64/ld-2.12.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffea000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

    前面一部分是VMA的起始地址和結(jié)束地址。?
    最后一部分是該區(qū)域內(nèi)容所屬文件。?
    在32位系統(tǒng)中,進(jìn)程地址空間為4G,分為用戶(hù)空間和內(nèi)核空間。?


    從下面可以看到棧的地址是向下生長(zhǎng),堆的地址是向上生長(zhǎng)。?

    通過(guò)反匯編定位段錯(cuò)誤:

    段錯(cuò)誤是程序員最討厭的問(wèn)題之一,其發(fā)生往往很突然,且破壞巨大。典型的段錯(cuò)誤是由于操作內(nèi)存不當(dāng)引起的(如使用野指針或訪問(wèn)受保護(hù)的地址等),發(fā)生段錯(cuò)誤時(shí),內(nèi)核以一個(gè)信號(hào)SIGSEGV強(qiáng)行終止進(jìn)程,留下的出錯(cuò)信息極少,從而導(dǎo)致難以定位。但利用gdb和反匯編工具,可以較準(zhǔn)確地定位段錯(cuò)誤產(chǎn)生的原因。但想用這種方法調(diào)試,一些準(zhǔn)備工作和工具是必需的。

    準(zhǔn)備工作:

    (1)coredump:進(jìn)程異常中止時(shí),內(nèi)核生成的記錄文件,其中保存了進(jìn)程異常時(shí)所占用的內(nèi)存和CPU資源,如pc計(jì)數(shù)器、各個(gè)寄存器的值等。這個(gè)文件是調(diào)試段錯(cuò)誤最重要的依據(jù)。要使內(nèi)核生成coredump,需要在內(nèi)核配置中打開(kāi)CONFIG_ELF_CORE選項(xiàng),如果沒(méi)有打開(kāi),將其選上后重新編譯內(nèi)核即可。

    此外,利用命令ulimit -c unlimited,可以設(shè)置coredump大小為不受限制,可以保存更完整的信息。文件/proc/sys/kernel/core_pattern可以配置生成coredump的命名格式,如果不設(shè)置格式,則coredump默認(rèn)生成的位置在出錯(cuò)進(jìn)程的目錄下,且生成的core同名,也就意味著舊的coredump可能被新的coredump所覆蓋。如果我想在/tmp目錄下生成以core.pid格式命名的coredump文件,只需執(zhí)行命令:

    echo "/tmp/core.%p" > /proc/sys/kernel/core_pattern

    (2)編譯:為了利用gdb進(jìn)行調(diào)試,在編譯程序時(shí),需要在編譯選項(xiàng)中加入-g 選項(xiàng),以將調(diào)試信息編譯到目標(biāo)文件中。

    (3)反匯編:顧名思義,反匯編就是將編譯好的二進(jìn)制可執(zhí)行文件翻譯成匯編文件。一般來(lái)說(shuō),編譯器會(huì)自帶一套反匯編工具,只有選擇正確的工具才能正確地進(jìn)行反匯編,這不難理解。比如我是用gcc4.6.3編譯的用于mips的應(yīng)用程序,那么,在編譯器的目錄下可以找到gcc463/usr/bin/mipsel-buildroot-linux-uclibc-objdump,這就是我要使用的反匯編工具。將二進(jìn)制文件反匯編成匯編文件只需執(zhí)行命令:XXXX-objdump -S XXXX(程序名),即可生成可以閱讀的、關(guān)聯(lián)到C代碼的匯編代碼,如下所示:

    ?? ?status = httpRpmPost(reqId);?? ?42f208:?? ?0320f809 ?? ?jalr?? ?t942f20c:?? ?00808021 ?? ?move?? ?s0,a042f210:?? ?8fbc0018 ?? ?lw?? ?gp,24(sp)if (RPM_OK != status && RPM_DONE != status)42f214:?? ?10400009 ?? ?beqz?? ?v0,42f23c <postDataApStatusJson.part.4+0x68>42f218:?? ?3c120061 ?? ?lui?? ?s2,0x6142f21c:?? ?24030002 ?? ?li?? ?v1,242f220:?? ?10430006 ?? ?beq?? ?v0,v1,42f23c <postDataApStatusJson.part.4+0x68>42f224:?? ?3c040061 ?? ?lui?? ?a0,0x61{RPM_AP_ERROR("httpRpmPost error!");42f228:?? ?2484c928 ?? ?addiu?? ?a0,a0,-1404042f22c:?? ?2645cc98 ?? ?addiu?? ?a1,s2,-1316042f230:?? ?8f999e50 ?? ?lw?? ?t9,-25008(gp)42f234:?? ?0810bca3 ?? ?j?? ?42f28c <postDataApStatusJson.part.4+0xb8>42f238:?? ?24060327 ?? ?li?? ?a2,807return RPM_ERROR;}?? ? ?httpStatusSet(reqId, HTTP_OK);42f23c:?? ?8f998088 ?? ?lw?? ?t9,-32632(gp)42f240:?? ?02002021 ?? ?move?? ?a0,s042f244:?? ?0320f809 ?? ?jalr?? ?t942f248:?? ?00002821 ?? ?move?? ?a1,zero42f24c:?? ?8fbc0018 ?? ?lw?? ?gp,24(sp)

    可以看到,C代碼下面跟著一串匯編代碼,而匯編代碼前面有一段地址,這個(gè)地址是什么呢?如果熟悉Linux進(jìn)程空間的概念,很容易就可以聯(lián)想到,這個(gè)地址其實(shí)就是相應(yīng)的匯編指令在.text段(即代碼段)中的地址。也就是說(shuō),這個(gè)地址就是我們用于定位具體出錯(cuò)地點(diǎn)的依據(jù)。(4)gdb:可以說(shuō)是Linux下調(diào)試程序最常用的工具,功能強(qiáng)大,操作也很簡(jiǎn)單。對(duì)于mips程序調(diào)試,只需安裝相應(yīng)的gdb:mips-linux-gdb即可。


    開(kāi)始調(diào)試:

    上面的準(zhǔn)備工作都完成后,就可以開(kāi)始調(diào)試了。當(dāng)進(jìn)程再次異常終止時(shí),就可以在/tmp目錄下找到coredump文件:比如core.126(進(jìn)程id為126的進(jìn)程生成的coredump)。
    用gdb的-c選項(xiàng)打開(kāi)coredump:mips-linux-gdb -c /tmp/core.126,可以看到如下信息:

    <span style="font-size:14px;">GNU gdb (GDB) 7.4.1 Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. ?Type "show copying" and "show warranty" for details. This GDB was configured as "--host=i686-pc-linux-gnu --target=mips-linux". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Core was generated by `/usr/bin/httpd'. Program terminated with signal 11, Segmentation fault. #0 ?0x2b17ff50 in ?? () (gdb) </span>

    前面是gdb的版本信息,不必理會(huì)。我們主要關(guān)注下面的內(nèi)容:
    <span style="font-size:14px;">Core was generated by `/usr/bin/httpd'.
    Program terminated with signal 11, Segmentation fault.
    #0 ?0x2b17ff50 in ?? ()</span>
    表示這個(gè)coredump是為進(jìn)程httpd生成的,而進(jìn)程退出的原因是signal 11,即SIGSEGV,這正是我們想要的。最后一行,0x2b17ff50是一個(gè)地址,這里??的地方本來(lái)應(yīng)該顯示一個(gè)函數(shù)名,之所以這里沒(méi)有顯示,我猜想這應(yīng)該是一個(gè)庫(kù)函數(shù),而編譯這個(gè)庫(kù)時(shí),并沒(méi)有帶入-g信息。
    不要緊,接下來(lái)只需要輸入where,即可顯示信號(hào)產(chǎn)生時(shí)程序中止的位置:

    <span style="font-size:14px;">Core was generated by `/usr/bin/httpd'. Program terminated with signal 11, Segmentation fault. #0 ?0x2b17ff50 in ?? () (gdb) where #0 ?0x2b17ff50 in ?? () #1 ?0x0045c034 in ?? () Backtrace stopped: previous frame identical to this frame (corrupt stack?) (gdb) </span>

    ? ? 至此,我們已經(jīng)拿到最重要的信息:0x0045c034,就是進(jìn)程中止時(shí)停留的位置。對(duì)照上面生成的反匯編文件,搜索45c034,即可找到:

    ? ? out = cJSON_Print(root);45c00c:?? ?8f9981ec ?? ?lw?? ?t9,-32276(gp)45c010:?? ?00000000 ?? ?nop45c014:?? ?0320f809 ?? ?jalr?? ?t945c018:?? ?02402021 ?? ?move?? ?a0,s245c01c:?? ?8fbc0010 ?? ?lw?? ?gp,16(sp)httpnPrintf(reqId, strlen(out) + 1, "%s\n", out);45c020:?? ?00402021 ?? ?move?? ?a0,v045c024:?? ?8f999fe4 ?? ?lw?? ?t9,-24604(gp)45c028:?? ?00000000 ?? ?nop45c02c:?? ?0320f809 ?? ?jalr?? ?t945c030:?? ?00408021 ?? ?move?? ?s0,v045c034:?? ?8fbc0010 ?? ?lw?? ?gp,16(sp)45c038:?? ?8fa47e48 ?? ?lw?? ?a0,32328(sp)45c03c:?? ?3c060062 ?? ?lui?? ?a2,0x6245c040:?? ?8f9981f0 ?? ?lw?? ?t9,-32272(gp)45c044:?? ?24450001 ?? ?addiu?? ?a1,v0,145c048:?? ?24c6cdb8 ?? ?addiu?? ?a2,a2,-1287245c04c:?? ?0320f809 ?? ?jalr?? ?t945c050:?? ?02003821 ?? ?move?? ?a3,s045c054:?? ?8fbc0010 ?? ?lw?? ?gp,16(sp)45c058:?? ?00000000 ?? ?nopRPM_MONITORAP_TRACE("\r\n%s\r\n\r\n", out);

    可以看到,出錯(cuò)時(shí),對(duì)應(yīng)的C函數(shù)是httpnPrintf,對(duì)應(yīng)的匯編代碼為:????lw????gp, 16(sp)。

    在反匯編文件中再稍微對(duì)照上下文,即可知道具體是哪個(gè)模塊、哪個(gè)文件中的調(diào)用。如果看得懂匯編代碼,基本可以定位到函數(shù)中的具體語(yǔ)句,即使看不懂匯編,利用打印調(diào)試或者靜態(tài)代碼分析等常規(guī)調(diào)試手段也基本可以定位到具體的出錯(cuò)原因了。在本例中,最終確定這個(gè)函數(shù)出錯(cuò)的原因是操作了調(diào)用malloc(0)而獲取的一個(gè)空指針(malloc(0)返回什么),著實(shí)令人始料未及。
    ?

    不能使用GDB的地方,也可以使用反匯編來(lái)定位段錯(cuò)誤的位置。一般在發(fā)生段錯(cuò)誤的時(shí)候,都會(huì)有些信息打印出來(lái),如下:

    Oops: Data Abort caused by READ instruction! Fault: Alignment fault pc: 0021034c r0: 20000053 r1: 00000001 r2: 00000000 r3: 20000053 r4: aaaaaaaa r5: 00256984 r6: aaaaaaaa r7: dddddddd r8: aaaaaaaa r9: dddddddd r10: 00060000 fp: 00000000 ip: 00000000 sp: 00240bc0 SPSR: 600000d3

    ? ? 從上面看出,PC就是程序計(jì)數(shù)器,即程序運(yùn)行到此位置發(fā)生錯(cuò)誤,然后我們利用objdump將運(yùn)行的程序反匯編,跟蹤查詢(xún)到問(wèn)題出現(xiàn)的位置。
    版權(quán)說(shuō)明:文章內(nèi)容轉(zhuǎn)載自下面文章

    ? ?《ELF文件格式分析》轉(zhuǎn)載自:https://www.cnblogs.com/cdcode/p/5551649.html
    ? ?《Linux驅(qū)動(dòng)mmap內(nèi)存映射》轉(zhuǎn)載自https://www.cnblogs.com/wanghuaijun/p/7624564.html
    ? ?《通過(guò)反匯編定位段錯(cuò)誤》轉(zhuǎn)載自https://blog.csdn.net/ringrang/article/details/60596846
    ? ?《Linux C 內(nèi)存管理》轉(zhuǎn)載自:?https://blog.csdn.net/zqixiao_09/article/details/50381476?
    ? ?《readelf命令和ELF文件詳解》轉(zhuǎn)載自:https://blog.csdn.net/linux_ever/article/details/78210089?

    《Understanding_ELF.pdf》 是官方elf 文件格式的翻譯,對(duì)elf格式有做詳細(xì)的介紹。下載地址:https://download.csdn.net/download/li_wen01/12446168

    ?

    ----------------------------------------------------------------2022.08.28----------------------------------------------------------------

    ?新的文章內(nèi)容和附件工程文件

    已更新在博客首頁(yè)和:

    gong 眾 hao :?liwen01

    總結(jié)

    以上是生活随笔為你收集整理的Linux内存管理内存映射以及通过反汇编定位内存错误问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。