以下內容是個人揣測,不正確的機率很高,這裡只是寫下來備忘。
這裡以x86平台為例進行說明。
編譯:編譯器在編譯**的時候,是以乙個
cpp和乙個
h為基本編譯單元的,每個編譯單元我們這裡稱為乙個編譯模組,每個編譯模組都可能定義全域性變數和靜態變數以及區域性變數,同時可能引用其他
.編譯模組中的全域性變數。每個編譯單元編譯後生成的都是乙個
.obj
檔案,這個檔案中可以說是由很多段組成,這些段有很多,最基本的有
3種,**段,資料段和
bss段。
**段中存放的就是我們的函式了,資料段存放的就是我們已經初始化的全域性變數和靜態變數,由於他們已經都初始化了,所以他們的記憶體位址在編譯的時候就已經分配了(這裡的位址是邏輯位址,也就是基於某個段的偏移值。區域性變數執行時才能知道邏輯位址)。編譯時分配什麼意思哪?也就是說這個
obj檔案的大小會受這些變數型別的影響,等於已經填充了這個
obj檔案。但是對於那些未初始化的全域性變數和區域性變數,一般都不真正的為他分配記憶體空間,畢竟還沒有初始化嗎,所以分配記憶體空間也是填充
0,填充後我們的
obj檔案就要大很多,
obj檔案大了就要浪費磁碟空間,所以
bss段只是乙個佔位符號一樣。這裡其實還有很多其他段,都是為了輔助連線程式弄的。不過,編譯後,所有涉及和位址有關的**,基本都是轉換成了位置無關**,什麼意思哪,比如乙個跳轉指令,
jmp,他跳轉的位址是基於本條跳轉指令的偏移,而不是基於段的偏移,所以他就可以被作業系統載入到任意的記憶體中而不用對**位址進行調整。但是還有一部分指令是基於段內的偏移位址,比如基於**段的偏移位址,這種位址相對也好辦,但是還有一種位址,是可能跨段的,如下
jmp cs2::offset,
這種位址反映在我們的編譯單元中就是我們這個編譯單元使用了另外乙個編譯單元中的資料,這樣就會產生這樣乙個跨段的位址,並且這個位址暫時還是未知的。只能等到鏈結程式工作的時候才能確定。
由於每個編譯單元編譯出的
obj都含有很多性質類似的**段,資料段等,顯然,太多了沒有什麼好處,所以這裡聯結器就把性質相似的段合併在一起,同時,上次編譯時那些無法定位位址的指令也會涉及到位址重定位。最終,我們的**涉及到位址有關的部分,還是大致分成了
3類,一類是位址無關的,一類是基於段偏移的,一類是跨段的。但是這裡有一點要注意,聯結器鏈結出來的程式,可以指定基礎位址(一般叫基址),比如
0,或者某個確定的值(
exe和
dll都有預設值)。這樣,
link
後的exe
或者dll
中的**段和資料段又都是基於這個基址偏移的。所以,我們的那個長指令
jmp
段:偏移就會被編譯成相對於基址的偏移。
載入:這裡記住,我們的整個
exe或者
dll是基於某個基址編譯的,所有的涉及段位址的指令都是基於這個基位址的。所以,如果作業系統正好把我們的程式載入到記憶體的這個實體地址處,那麼一切都完美,程式可以正常執行,但是,程式載入到**是動態確定的,所以,如果程式載入到了其他位址處,那麼我們那些
jmp
段址:偏移的指令就會失效。為了避免失效,我們必須修改那些使用段址的指令,把其中的段址部分修正為作業系統實際載入到的實體地址。這樣貌似我們的程式就能正確的工作了,但是問題比我們想象的要複雜,因為
x86保護模式下段暫存器中存放的不是段址了,而是段的選擇子。由選擇子才去選擇真正的段址。選擇子指向乙個描述符,其中描述符中記錄了這個段的物理基址,以及這個段的長度,甚至段的可讀取許可權。很多個描述符組織在一起就相當於乙個表了,
x86中有兩種表,一種是
gdt,
一種是ldt
。gdt
大部分是存放的作業系統本身的**段和資料段等段的描述符,而
ldt表是存放的應用程式自身的各個段的描述符,並且每個應用程式都會有乙個
ldt表。
所以,為了適應
x86下
cpu的定址基址,作業系統必須為我們的程式建立乙個
ldt**,這個**中為我們的每個段(**段,資料段等)建立乙個描述符,描述符真正包含了我們的相應段的真實基址。由於作業系統幫我們載入進來的程式,所以他很清楚每個段的真實的位址。所以他完全可以建立這樣乙個區域性描述符表。建立這張表的時候,根據當前程式使用的段,一般有
cs,ds,es,fs,gs
,就可以建立各個段的選擇子並且填充,同時,在
gdt表中加入乙個我們
ldt表的描述符,用於描述我們這個應用程式
ldt表在記憶體中的位置,然後用
ldtr
指標指向
gdt表中的這個描述符。這樣,當我們的**在執行的時候,比如類似
執行:假設**段中有這樣一條語句
mov eaxds::offset
很顯然,
ldtr
暫存器可以從
gdt中找到我們的
ldt表的實體地址,而我們的
ds又知道了
es段在
ldt表中的選擇子,這樣我們就可以精確的知道
ds這個段的實體地址,然後又知道偏移,那麼顯然我們就可以定址了。
以上分析是基於找到的資料加個人理解整理出來的,不正確在所難免,只是起乙個拋磚引玉的作用,此外,分頁模式下可能稍微比這個複雜點。
關於源程式到可執行程式的過程
源程式,是指未經編譯的,按照一定的程式語言規範書寫的,人類可讀的文字檔案,我們通常理解為源程式就是我們所寫好的 可執行程式,我們常說的.exe程式,可以執行程式,完成計算機功能。在c語言中,c檔案就是所謂的原始檔,接下來,我們剖析一下,源程式到可執行程式的過程。在這個過程中,會發生如下的變化 c檔案...
關於源程式到可執行程式的過程
源程式,是指未經編譯的,依照一定的程式語言規範書寫的,人類可讀的文字檔案,我們通常理解為源程式就是我們所寫好的 可執行程式。我們常說的.exe程式,能夠執行程式。完畢計算機功能。在c語言中,c檔案就是所謂的原始檔,接下來,我們剖析一下,源程式到可執行程式的過程。在這個過程中。會發生例如以下的變化 c...
C C 源程式到可執行程式exe的全過程
本文參考了部落格,其位址如下 原始檔生成可執行檔案的過程總共是經歷了預處理 編譯 彙編 鏈結四個過程。源程式 source code 預處理器 preprocessor 編譯器 compiler 匯程式設計序 assembler 目標程式 object code 聯結器 鏈結器,linker 可執行...