由hello world 談程式運行機制

來源:互聯網
上載者:User
我們剛開始接觸編程的時候,最先完成的小項目就是“hello world”,在短時間內我們都能用這種語言寫出它的hello world。但是別看這隻是一個小小的幾個字母然而,對於hello world這個簡單程式的內部運行機制,大部分人還是說不清楚的,所以我們今天就為大家講講程式啟動並執行機制。

hello world這些資訊是如何通顯示器過顯示的?cpu執行的代碼和程式中我們寫的的代碼肯定不一樣,她是什麼樣子的?又是如何從我們寫的代碼變成cpu能執行的代碼的?程式運行時代碼是在什麼地方?她們是如何組織的?程式中的變數儲存在什麼地方?函數調用是怎樣是現的?這篇文章將簡單的討論程式的運行機制。

開發平台隱藏的過程

每一種語言都有自己的開發平台,我們的程式大多是也都是在這裡誕生的。從程式原始碼到可執行檔的轉化過程其實是分很多步而且是很複雜的,只是而現在的開發平台把所有的這些事情都自己承擔了,給我們帶來方便的同時她也影藏了大量的實現細節。所以大多程式員只負責編寫代碼,其它的複雜的轉換工作則由開發平台默默完成。

按照我的理解,簡單的說從原始碼到可執行檔的過程可分為以下幾個階段:

1、從原始碼到機器語言並將產生的機器語言按照一定的規律組織起來。我們暫且稱為檔案A。

2、把檔案A和運行A需要的檔案B(如庫函數)連結起來,形成檔案A+

3、把檔案A+裝載進入記憶體,運行檔案

(其實如果是看參考書或者其他資料的話可能不止這幾步,只是這裡為了簡化我把它歸納為3步)

這些事形成可執行檔的關鍵步驟,缺一不可。現在看到被開發平台“蒙蔽”了吧。下面的部分將撥開迷霧,還你開發平台的真面目。

目標檔案

在電腦領域有過一句經典的話:

“any problem in computer science can be sloved by another layer of indirecition”

“電腦科學領域的任何問題都可以通過增加一個中介層來解決”

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

那麼從原始碼到可執行檔的過程可以這樣理解。從原始碼到可執行檔也是一樣的,通過(不斷的)在他們之間增加中介層,來解決問題。

和上文說的,先把來源程式轉化為中間檔案A,再把中間檔案轉化為我們需要的目標檔案。

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

其實上面說的檔案A更專業的說法是:目標檔案。她不是可執行程式,需要和其它的目標檔案進行連結、裝載後才能執行。對於一個來源程式,開發平台首先要做的就是把來源程式翻譯成機器語言。其中很重要的一部就是編譯。相信很多人都知道,就是把原始碼翻譯成機器語言(其實就是一堆二進位代碼)。編譯知識很重要,卻不是本文的重點,有興趣的可自行google。

目標檔案格式:

現在來看一下上面說的目標檔案是如何組織的(也就是存放結構)。

起源:

想象一下如果是你來設計會如何組織這些二進位代碼?就像書桌上的物品要分類放置才整潔一樣,為了便於管理翻譯出來的二進位代碼也分類存放,把表示代碼的放在一起,表示資料的放在一起。這樣,二進位代碼就分為了不同的塊來存放。這樣的一個地區就是被稱為段(segment)的東西。

標準:

和電腦科學中的很多東西一樣,為了方便人們的交流、程式的相容等問題。也為這種二進位的存放方式制訂了標準,於是COFF(common object file format)就誕生了。現在的windows、Linux、等主流作業系統下的目標檔案格式和COFF大同小異,都可以認為是它的變種。

a.out:

a.out是目標檔案的預設名字。也就是說,當編譯一個檔案的時候,如果不對編譯後的目標檔案重新命名,編譯後就會產生一個名字為a.out的檔案。

具體的為什麼會用這個名字這裡就不在深究了。有興趣的可以自己google。

下面的圖可以讓你更直觀的瞭解目標檔案:


是目標檔案的典型結構,實際的情況可能會有所差別,但都是在這個基礎上衍生出來的。

ELF檔案頭:即中的第一個段。其中的header是目標檔案的頭部,裡麵包含了這個目標檔案的一些基本資料。如該檔案的版本、目標機器型號、程式入口地址等等。

文本段:裡面的資料主要是程式中的代碼部分。

資料區段:程式中的資料部分,比如說變數。

重定位段:

重定位段包括了文本重定位和資料重定位,裡麵包含了重定位資訊。一般來說,代碼中都會存在引用了外部的函數,或者變數的情況。既然是引用,那麼這些函數、變數並沒存在該目標檔案內。在使用他們的時候,就要給出他們的實際地址(這個過程發生在連結的時候)。正是這些重定位表,提供了尋找這些實際地址的資訊。理解了上面之後,文本重定位和資料重定位也就不難理解了。

符號表:符號表包含了原始碼中所有的符號資訊。包括每個變數名、函數名等等。裡面記錄了每個符號的資訊,比如說代碼中有“student”這個符號,對應的在符號表中就包括這個符號的資訊。包括這個符號所在的段、它的屬性(讀寫權限)等相關資訊。

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

字串表:和符號表差不多的功能,存放了一些字串資訊。

其中還有一點要說嗎的是:目標檔案都是以二進位來儲存的,它本身就是二進位檔案。

現實中的目標檔案會比這個模型要複雜些,但是它的思路都是一樣的,就是按照類型來儲存,再加上一些描述目標檔案資訊的段和連結中需要的資訊。

a.out剖分

Hello World

空口無憑,我們現在就來研究一下hello world編譯後形成的目標檔案,這裡用C來描述。

簡單的hellow world源碼:


為了在資料區段中也有資料可放,這裡增加了“int a=5”。

如果在VC上的話,點擊運行便能看到結果。

為了能看清楚內部到底是如何處理的,我們使用GCC來編譯。

運行

gcc hello.c

再看我們的目錄下,就多了目標檔案a.out。


現在我們想做的是看看a.out裡到底有什麼,可能有童鞋回想到用vim文本查看,當時我也是這麼天真的認為。但a.out是何等東西,怎能這麼簡單就暴露出來呢。是的,vim不行。“我們遇到的問題大多是前人就已經遇到並且已經解決的”,對,其中有一個很強悍的工具叫做objdump。有了它,我們就能徹底的去瞭解目標檔案的各種細節,當然還有一個叫做readelf也很有用,這個在後面介紹。

這兩個工具一般Linux裡面都會內建有有,可以自行google

註:這裡的代碼主要是在Linux下用GCC編譯,查看目標檔案用的是Objdump、readelf。但是我會把所有的運行結果都,所以之前沒有接觸過Linux的童鞋來看下面的內容也完全沒問題哦。我用的是ubuntu,感覺挺好~

下面是a.out的組織圖:(每段的起始地址、、大小等等)

查看目標檔案的命令是 objdump -h a.out


就和上文中描述的目標檔案的格式一樣,可以看出是分類儲存的。目標檔案被分為了6段。

從左至右,第一列(Idx Name)是段的名字,第二列(Size)是大小,VMA為虛擬位址,LMA為物理地址,File off是檔案內的位移。也就是這段相對於段中某一參考(一般是段起始)的距離。最後的Algn是對段屬性的說明,暫時不用理會

“text”段:程式碼片段。

“data”段:也就是上面說的資料區段,儲存了原始碼中的資料,一般是以初始化的資料。

“bss”段:也是資料區段,存放那些未初始化的資料,因為這些資料還未配置的空間,所以單獨存放。

“rodata”段:唯讀資料區段,裡面存放的資料是唯讀。

“cmment”存放的是編譯器版本資訊。

剩下的兩段對我們的討論沒有實際意義,就不再介紹。認為他們包含了一些連結、編譯、裝在的資訊就可。

註:

這裡的目標檔案格式只是列出實際情況中主要部分。實際情況還有一些表未列出。如果你也在用Linux,可以用objdump -X列出更詳細的段內容。

深入a.out

上面部分通過執行個體說了目標檔案中的典型的段,主要是段的資訊,如大小等相關的屬性。

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

objdump -s a.out 通過-s選項就可以查看目標檔案的十六進位格式。

查看結果如下:


如所示,列出了各段的十六進位表示形式。可以看出圖中共分為兩欄,左邊的一欄是十六進位的表示, 右邊則顯示相應的資訊。

比較明顯的如“rodata”唯讀資料區段中就有“hello world”。。汗,好像程式裡的“hello”打錯了,後面多加了一個“w”,麻煩,。原諒下哈。

你也可以查看“hellow world”的ASCII值,對應的十六進位就是裡面的內容了。

“comment”上文中說的這個段包含了一些編譯器的版本資訊,這個段後面的內容就是了:GCC編譯器,後面的是版本號碼。

a.out反組譯碼

編譯的過程總是先把源文先變為彙編形式,再翻譯為機器語言。(添加中介層嘛)看了這麼多的a.out,再研究一下他的彙編形式是恨必要的

objdump -d a.out可以列出檔案的彙編形式。不過這裡只列出了主要部分,即main函數部分,其實在main函數執行的開始和main函數執行以後都還有多工作要做。

即初始化函數執行環境以及釋放函數佔用的空間等。


上面的圖中,左邊是代碼的十六進位形式,左邊是彙編形式。對彙編熟悉的童鞋應該能看懂大部分,這裡就不在多述。

a.out標頭檔

在介紹目標檔案格式的時候,提到過標頭檔這個概念,裡麵包含了這個目標檔案的一些基本資料。如該檔案的版本、目標機器型號、程式入口地址等等。

是檔案頭的形式:

可以用readelf -h來查看。(中查看的是hello.o,它是源檔案hello.c編譯但未連結的檔案。 這個和查看a.out大部分是一樣的)


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

接下來的是一些和目標檔案相關的資訊。由於和我們要討論的問題關係不大,這裡就不展開討論了。

上面是內容用具體的執行個體說了目標檔案內部的組織形式,目標檔案只是產生可執行檔過程中的一個中間過程,對於程式是如何啟動並執行還沒做討論,目標檔案是如何轉變為可執行檔以及可執行檔是如何執行的將在下面的部分中討論

對連結的簡單認識

連結通俗的說就是把幾個可執行檔。

如果程式A中引用了檔案B中定義的函數,為了A中的函數能正常執行,就需要把B中的函數部分也放在A的原始碼中,那麼將A和B合并成一個檔案的過程就是連結了。

有專門的過程用來連結程式,稱為連結器。他將一些輸入的目標檔案加工後合成一個輸出檔案。這些目標檔案中往往有相互的資料、函數引用。

上文中我們看過了hello world的反組譯碼形式,是一個還沒有經過連結的檔案,也就是說當引用外部函數的時候是不知道其地址的:

如:


中,cal指令就是調用了printf()函數,因為這時候printf()函數並不在這個檔案中,所以無法確定它的地址,在十六進位中就用“ff ff ff”來表示它的地址。等經過連結以後,這個地址就會變為函數的實際地址,應為串連後這個函數已經被載入進入這個檔案中了。

連結的分類:按把A相關的資料或函數合并為一個檔案的先後可以把連結分為靜態連結和動態連結。

靜態連結:

在程式執行之前就完成連結工作。也就是等連結完成後檔案才能執行。但是這有一個明顯的缺點,比如說庫函數。如果檔案A和檔案B都需要用到某個庫函數,連結完成後他們串連後的檔案中都有這個庫函數。當A和B同時執行時,記憶體中就存在該庫函數的兩份拷貝,這無疑浪費了儲存空間。當規模擴大的時候,這種浪費尤為明顯。靜態連結還有不容易升級等缺點。為瞭解決這些問題,現在的很多程式都用動態連結。

動態連結:和靜態連結不一樣,動態連結是在程式執行的時候才進行連結。也就是當程式載入執行的時候。還是上面的例子,如果A和B都用到了庫函數Fun(),A和B執行的時候記憶體中就只需要有Fun()的一個拷貝。

關於連結還有很多知識,以後會用專門的文章來談。這裡就不展開講了。

對裝載的簡單解釋

我們知道,程式要運行是必然要把程式載入到記憶體中的。在過去的機器裡都是把整個程式都載入進入實體記憶體中,現在一般都採用了虛擬儲存機制,即每個進程都有完整的地址空間,給人的感覺好像每個進程都能使用完成的記憶體。然後由一個記憶體管理器把虛擬位址映射到實際的實體記憶體地址。

按照上文的敘述,程式的地址可以分為虛擬位址和實際地址。虛擬位址即她在她的虛擬記憶體空間中的地址,物理地址就是她被載入的實際地址。


在上文中查看段的時候或許你已經注意到了,由於檔案是未連結、未載入的,所以每個段的虛擬位址和物理地址都是0.

載入的過程可以這樣理解:先為程式中的各部分分配好虛擬位址,然後再建立虛擬位址到物理地址的映射。其實關鍵的部分就是虛擬位址到物理地址的映射過程。程式裝在完成之後,cpu的程式計數器pc就指向檔案中的代碼起始位置,然後程式就按順序執行。

寫這篇文章的目的在於梳理程式啟動並執行機制,在一個可執行檔執行的背後都隱藏了什麼。從原始碼到可執行檔通常要經曆許多中間步驟,每一個中間步驟都產生一個中間檔案。只是現在的整合式開發環境都吧這些步驟影藏了,習慣於整合式開發環境的我們也就逐漸的忽略了這些重要的技術內幕。這篇文章也只是介紹了一下這個過程的主線而已。其中的每一個細節展開來講都可足已用一篇文章來論述。

我想看完這篇文章之後,大家就不會覺得“hello world”只是很簡單的一個小實驗吧,也希望大家通過此篇文章瞭解到什麼是程式的運行機制以及是怎樣啟動並執行。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.