溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

從Hello World分析web程序運(yùn)行機(jī)制

發(fā)布時間:2021-11-17 15:00:43 來源:億速云 閱讀:143 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“從Hello World分析web程序運(yùn)行機(jī)制”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“從Hello World分析web程序運(yùn)行機(jī)制”吧!

開發(fā)平臺隱藏的過程

每一種語言都有自己的開發(fā)平臺,我們的程序大多是也都是在這里誕生的。從程序源代碼到可執(zhí)行文件的轉(zhuǎn)化過程其實(shí)是分很多步而且是很復(fù)雜的,只是而現(xiàn)在的開發(fā)平臺把所有的這些事情都自己承擔(dān)了,給我們帶來方便的同時她也影藏了大量的實(shí)現(xiàn)細(xì)節(jié)。所以大多程序員只負(fù)責(zé)編寫代碼,其它的復(fù)雜的轉(zhuǎn)換工作則由開發(fā)平臺默默完成。

按照我的理解,簡單 的說從源代碼到可執(zhí)行文件的過程可分為以下幾個階段:

1、從源代碼到機(jī)器語言并將產(chǎn)生的機(jī)器語言按照一定的規(guī)律組織起來。我們暫且稱為文件A。
2、把文件A和運(yùn)行A需要的文件B(如庫函數(shù))鏈接起來,形成文件A+
3、把文件A+裝載進(jìn)入內(nèi)存,運(yùn)行文件
(其實(shí)如果是看參考書或者其他資料的話可能不止這幾步,只是這里為了簡化我把它歸納為3步)

這些事形成可執(zhí)行文件的關(guān)鍵步驟,缺一不可?,F(xiàn)在看到被開發(fā)平臺“蒙蔽”了吧。下面的部分將撥開迷霧,還你開發(fā)平臺的真面目。

目標(biāo)文件

在計(jì)算機(jī)領(lǐng)域有過一句經(jīng)典的話:

“any problem in computer science can be sloved by another layer of indirecition”
“計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個中間層來解決”

比如說要實(shí)現(xiàn)從A到B的轉(zhuǎn)換,可以先把A轉(zhuǎn)換為文件A+,再把文件A+轉(zhuǎn)換為我們需要的文件B。(其實(shí)在波利亞的《how to slove it》里面對這種方法也有敘述。在解題的時候可以通過增加中間層來簡化問題)

那么從源代碼到可執(zhí)行文件的過程可以這樣理解。從源代碼到可執(zhí)行文件也是一樣的, 通過(不斷的)在他們之間增加中間層,來解決問題。和上文說的, 先把源程序轉(zhuǎn)化為中間文件A,再把中間文件轉(zhuǎn)化為我們需要的目標(biāo)文件。

在處理文件的時候就是按照這種思路來的。

其實(shí)上面說的文件A更專業(yè)的說法是:目標(biāo)文件。她不是可執(zhí)行程序,需要和其它的目標(biāo)文件進(jìn)行鏈接、裝載后才能執(zhí)行。對于一個源程序,  開發(fā)平臺首先要做的就是把源程序翻譯成機(jī)器語言。其中很重要的一部就是編譯。相信很多人都知道,就是把源代碼翻譯成機(jī)器語言(其實(shí)就是一堆二進(jìn)制代碼)。編譯知識很重要,卻不是本文的重點(diǎn),有興趣的可自行g(shù)oogle。

目標(biāo)文件格式:

現(xiàn)在來看一下上面說的目標(biāo)文件是如何組織的(也就是存放結(jié)構(gòu))。

起源:

想象一下如果是你來設(shè)計(jì)會如何組織這些二進(jìn)制代碼?就像書桌上的物品要分類放置才整潔一樣,為了便于管理翻譯出來的二進(jìn)制代碼也分類存放,把表示代碼的放在一起,表示數(shù)據(jù)的放在一起。這樣,二進(jìn)制代碼就分為了不同的塊來存放。這樣的一個區(qū)域就是被稱為段(segment)的東西。

標(biāo)準(zhǔn):

和計(jì)算機(jī)科學(xué)中的很多東西一樣,為了方便人們的交流、程序的兼容等問題。也為這種二進(jìn)制的存放方式制訂了標(biāo)準(zhǔn),于是COFF(common  object file  format)就誕生了?,F(xiàn)在的windows、Linux、等主流操作系統(tǒng)下的目標(biāo)文件格式和COFF大同小異,都可以認(rèn)為是它的變種。

a.out:

a.out是目標(biāo)文件的默認(rèn)名字。也就是說,當(dāng)編譯一個文件的時候,如果不對編譯后的目標(biāo)文件重命名,編譯后就會產(chǎn)生一個名字為a.out的文件。具體的為什么會用這個名字這里就不在深究了。有興趣的可以自己google。

下面的圖可以讓你更直觀的了解目標(biāo)文件:

從Hello World分析web程序運(yùn)行機(jī)制

上圖是目標(biāo)文件的典型結(jié)構(gòu),實(shí)際的情況可能會有所差別,但都是在這個基礎(chǔ)上衍生出來的。

ELF文件頭:即上圖中的***個段。其中的header是目標(biāo)文件的頭部,里面包含了這個目標(biāo)文件的一些基本信息。如該文件的版本、目標(biāo)機(jī)器型號、程序入口地址等等。

文本段:里面的數(shù)據(jù)主要是程序中的代碼部分。

數(shù)據(jù)段:程序中的數(shù)據(jù)部分,比如說變量。

重定位段

重定位段包括了文本重定位和數(shù)據(jù)重定位,里面包含了重定位信息。一般來說,代碼中都會存在引用了外部的函數(shù),或者變量的情況。既然是引用,那么這些函數(shù)、變量并沒存在該目標(biāo)文件內(nèi)。在使用他們的時候,   就要給出他們的實(shí)際地址(這個過程發(fā)生在鏈接的時候)。正是這些重定位表,提供了尋找這些實(shí)際地址的信息。理解了上面之后,文本重定位和數(shù)據(jù)重定位也就不難理解了。

符號表:符號表包含了源代碼中所有的符號信息 。 包括每個變量名、函數(shù)名等等。里面記錄了每個符號的信息,比如說代碼中有“student”這個符號,對應(yīng)的在符號表中就包括這個符號的信息。包括這個符號所在的段、它的屬性(讀寫權(quán)限)等相關(guān)信息。

其實(shí)符號表最初的來源可以說是在編譯的詞法分析階段。在做詞法分析的時候,就把代碼中的每個符號及其屬性都記錄在符號表中。

字符串表:和符號表差不多的功能,存放了一些字符串信息。
其中還有一點(diǎn)要說嗎的是:目標(biāo)文件都是以二進(jìn)制來存儲的,它本身就是二進(jìn)制文件。
現(xiàn)實(shí)中的目標(biāo)文件會比這個模型要復(fù)雜些,但是它的思路都是一樣的,就是按照類型來存儲,再加上一些描述目標(biāo)文件信息的段和鏈接中需要的信息。

a.out剖分

Hello World

空口無憑,我們現(xiàn)在就來研究一下hello world編譯后形成的目標(biāo)文件,這里用 C 來描述。
簡單的hellow world 源碼:

/*hello.c*/ #include<stdio.h> int main() { int a=5; printf("hellow world \n"); }

為了在數(shù)據(jù)段中也有數(shù)據(jù)可放,這里增加了“int a=5”。

如果在VC上的話,點(diǎn)擊運(yùn)行便能看到結(jié)果。為了能看清楚內(nèi)部到底是如何處理的,我們使用GCC來編譯。

運(yùn)行

gcc hello.c

再看我們的目錄下,就多了目標(biāo)文件a.out。

從Hello World分析web程序運(yùn)行機(jī)制

現(xiàn)在我們想做的是看看a.out里到底有什么,可能有童鞋回想到用vim文本查看,當(dāng)時我也是這么天真的認(rèn)為。但a.out是何等東西,怎能這么簡單就暴露出來呢   。是的,vim不行?!拔覀冇龅降膯栴}大多是前人就已經(jīng)遇到并且已經(jīng)解決的”,對,其中有一個很強(qiáng)悍的工具叫做objdump。有了它,我們就能徹底的去了解目標(biāo)文件的各種細(xì)節(jié),當(dāng)然還有一個叫做readelf也很有用,這個在后面介紹。這兩個工具一般Linux里面都會自帶有有,可以自行g(shù)oogle

注:這里的代碼主要是在Linux下用GCC編譯,查看目標(biāo)文件用的是Objdump、readelf。但是我會把所有的運(yùn)行結(jié)果都上圖,所以之前沒有接觸過Linux的童鞋來看下面的內(nèi)容也完全沒問題哦。我用的是ubuntu,感覺挺好~

下面是a.out的組織結(jié)構(gòu):(每段的起始地址、、大小等等)
查看目標(biāo)文件的命令是    objdump -h a.out

從Hello World分析web程序運(yùn)行機(jī)制

就和上文中描述的目標(biāo)文件的格式一樣,可以看出是分類存儲的。目標(biāo)文件被分為了6段。

從左到右,***列(Idx Name)是段的名字,第二列(Size)是大小 ,VMA為虛擬地址,LMA為物理地址,F(xiàn)ile off是文件內(nèi)的偏移。也就是這段相對于段中某一參考(一般是段起始)的距離。***的Algn是對段屬性的說明,暫時不用理會

“text”段:代碼段。

“data”段:也就是上面說的數(shù)據(jù)段,保存了源代碼中的數(shù)據(jù),一般是以初始化的數(shù)據(jù)。

“bss”段:也是數(shù)據(jù)段,存放那些未初始化的數(shù)據(jù),因?yàn)檫@些數(shù)據(jù)還未分配空間,所以單獨(dú)存放。

“rodata”段:只讀數(shù)據(jù)段,里面存放的數(shù)據(jù)是只讀的。

“cmment”存放的是編譯器版本信息。

剩下的兩段對我們的討論沒有實(shí)際意義,就不再介紹。認(rèn)為他們包含了一些鏈接、編譯、裝在的信息就可。

注:

這里的目標(biāo)文件格式只是列出實(shí)際情況中主要部分。實(shí)際情況還有一些表未列出。如果你也在用Linux,可以用objdump -X 列出更詳細(xì)的段內(nèi)容。

深入a.out

上面部分通過實(shí)例說了目標(biāo)文件中的典型的段,主要是段的信息,如大小 等相關(guān)的屬性。

那么這些段里面究竟有些什么東西呢,“text”段里到底存了什么東西,還是用我們的objdump。

objdump -s a.out   通過-s選項(xiàng)就可以查看目標(biāo)文件的十六進(jìn)制格式。

查看結(jié)果如下:

從Hello World分析web程序運(yùn)行機(jī)制

如上圖所示,列出了各段的十六進(jìn)制表示形式??梢钥闯鰣D中共分為兩欄,左邊的一欄是十六進(jìn)制的表示, 右邊則顯示相應(yīng)的信息。比較明顯的如“rodata”只讀數(shù)據(jù)段中就有  “hello world”。。汗,好像程序里的“hello”打錯了,后面多加了一個“w”,截圖麻煩。原諒下哈。

你也可以查看“hellow world”的ASCII值,對應(yīng)的十六進(jìn)制就是里面的內(nèi)容了?!癱omment”上文中說的這個段包含了一些編譯器的版本信息,這個段后面的內(nèi)容就是了:GCC編譯器,后面的是版本號。

a.out反匯編

編譯的過程總是先把源文先變?yōu)閰R編形式,再翻譯為機(jī)器語言。(添加中間層嘛)看了這么多的a.out,再研究一下他的匯編形式是恨必要的

objdump -d a.out可以列出文件的匯編形式。不過這里只列出了主要部分,即main函數(shù)部分,其實(shí)在main函數(shù)執(zhí)行的開始和main函數(shù)執(zhí)行以后都還有多工作要做。即初始化函數(shù)執(zhí)行環(huán)境以及釋放函數(shù)占用的空間等。

從Hello World分析web程序運(yùn)行機(jī)制

上面的圖中,左邊是代碼的十六進(jìn)制形式,左邊是匯編形式。對匯編熟悉的童鞋應(yīng)該能看懂大部分,這里就不在多述。

a.out頭文件

在介紹目標(biāo)文件格式的時候,提到過頭文件這個概念,里面包含了這個目標(biāo)文件的一些基本信息。如該文件的版本、目標(biāo)機(jī)器型號、程序入口地址等等。

下圖是文件頭的形式:

可以用readelf -h 來查看。(下圖中查看的是 hello.o,它是源文件hello.c編譯但未鏈接的文件。 這個和查看a.out 大部分是一樣的)

從Hello World分析web程序運(yùn)行機(jī)制

圖中分為兩欄,左邊一欄表示的是屬性,右邊是屬性值。***行常被稱為魔數(shù)。后面是一連串的數(shù)字,其中的具體含義就不多說了,可以自己去google。

接下來的是一些和目標(biāo)文件相關(guān)的信息。由于和我們要討論的問題關(guān)系不大,這里就不展開討論了。

上面是內(nèi)容用具體的實(shí)例說了目標(biāo)文件內(nèi)部的組織形式,目標(biāo)文件只是產(chǎn)生可執(zhí)行文件過程中的一個中間過程,對于程序是如何運(yùn)行的還沒做討論,目標(biāo)文件是如何轉(zhuǎn)變?yōu)榭蓤?zhí)行文件以及可執(zhí)行文件是如何執(zhí)行的將在下面的部分中討論

對鏈接的簡單認(rèn)識

鏈接通俗的說就是把幾個可執(zhí)行文件。如果程序A中引用了文件B中定義的函數(shù),為了A中的函數(shù)能正常執(zhí)行,就需要把B中的函數(shù)部分也放在A的源代碼中,那么將A和B合并成一個文件的過程就是鏈接了。有專門的過程用來鏈接程序,稱為鏈接器。他將一些輸入的目標(biāo)文件加工后合成一個輸出文件。這些目標(biāo)文件中往往有相互的數(shù)據(jù)、函數(shù)引用。

上文中我們看過了hello world的反匯編形式,是一個還沒有經(jīng)過鏈接的文件,也就是說當(dāng)引用外部函數(shù)的時候是不知道其地址的,如下圖:

從Hello World分析web程序運(yùn)行機(jī)制

上圖中,cal指令就是調(diào)用了printf()函數(shù),因?yàn)檫@時候printf()函數(shù)并不在這個文件中,所以無法確定它的地址,在十六進(jìn)制中就用“ff  ff ff ”來表示它的地址。等經(jīng)過鏈接以后,這個地址就會變?yōu)楹瘮?shù)的實(shí)際地址,應(yīng)為連接后這個函數(shù)已經(jīng)被加載進(jìn)入這個文件中了。

鏈接的分類:按把A相關(guān)的數(shù)據(jù)或函數(shù)合并為一個文件的先后可以把鏈接分為靜態(tài)鏈接和動態(tài)鏈接。

靜態(tài)鏈接:

在程序執(zhí)行之前就完成鏈接工作。也就是等鏈接完成后文件才能執(zhí)行。但是這有一個明顯的缺點(diǎn),比如說庫函數(shù)。如果文件A 和文件B  都需要用到某個庫函數(shù),鏈接完成后他們連接后的文件中都有這個庫函數(shù)。當(dāng)A和B同時執(zhí)行時,內(nèi)存中就存在該庫函數(shù)的兩份拷貝,這無疑浪費(fèi)了存儲空間。當(dāng)規(guī)模擴(kuò)大的時候,這種浪費(fèi)尤為明顯。靜態(tài)鏈接還有不容易升級等缺點(diǎn)。為了解決這些問題,現(xiàn)在的很多程序都用動態(tài)鏈接。

動態(tài)鏈接:和靜態(tài)鏈接不一樣,動態(tài)鏈接是在程序執(zhí)行的時候才進(jìn)行鏈接。也就是當(dāng)程序加載執(zhí)行的時候。還是上面的例子 ,如果A和B都用到了庫函數(shù)Fun(),A和B執(zhí)行的時候內(nèi)存中就只需要有Fun()的一個拷貝。
關(guān)于鏈接還有很多知識,以后會用專門的文章來談。這里就不展開講了。

對裝載的簡單解釋

我們知道,程序要運(yùn)行是必然要把程序加載到內(nèi)存中的。在過去的機(jī)器里都是把整個程序都加載進(jìn)入物理內(nèi)存中,現(xiàn)在一般都采用了虛擬存儲機(jī)制,即每個進(jìn)程都有完整的地址空間,給人的感覺好像每個進(jìn)程都能使用完成的內(nèi)存。然后由一個內(nèi)存管理器把虛擬地址映射到實(shí)際的物理內(nèi)存地址。

按照上文的敘述, 程序的地址可以分為虛擬地址和實(shí)際地址。虛擬地址即她在她的虛擬內(nèi)存空間中的地址,物理地址就是她被加載的實(shí)際地址。

從Hello World分析web程序運(yùn)行機(jī)制

感謝各位的閱讀,以上就是“從Hello World分析web程序運(yùn)行機(jī)制”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對從Hello World分析web程序運(yùn)行機(jī)制這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

web
AI