對于一般人來說,一座樓就是一座樓;但是對于一個建筑觀察者來說,任何一座建筑都可以代表更多東西。它可以是后現(xiàn)代的偉大杰作,可以是英王愛德華時代的宏偉建筑,還可以是草原風格的宅邸。它可以游走于裝飾藝術(shù)領(lǐng)域,可以從繪畫文學汲取靈感,甚至可以自己生長、拓展輻射,最終在主根系中延生出茂盛而繁復(fù)的枝蔓。在《世界建筑風格漫游:從經(jīng)典廟宇到現(xiàn)代摩天樓》中,有心的讀者會發(fā)現(xiàn)大量有價值的信息,覆蓋了從古代建筑遺跡到鄉(xiāng)村原木小屋再到稀有的摩天建筑等方方面面。它可以幫你認識建筑,總結(jié)建筑風格的關(guān)鍵詞,感受設(shè)計者的思維,梳理建筑的發(fā)展脈絡(luò)?!妒澜缃ㄖL格漫游:從經(jīng)典廟宇到現(xiàn)代摩天樓》不僅概括介紹一些著名的建筑,而且從細節(jié)處精確討論同時伴有豐富而精美的照片,并用插圖形象展現(xiàn)一些專業(yè)建筑術(shù)語,如飛檐、飛扶壁、山墻等。讀者以建筑觀察者的客觀視角,濃繪不同的風格縮影。在這《世界建筑風格漫游:從經(jīng)典廟宇到現(xiàn)代摩天樓》中游走天下,狩獵建筑,你會有頗豐的收獲,有一天,你也會成為出色的建筑觀察者。
第一章古典建筑的遺產(chǎn)002
古典風格/003
古典柱式/005
多立克柱式/006
愛奧尼克柱式/007
科林斯柱式/008
希臘神廟/009
羅馬風格/011
羅馬拱/013
穹頂和筒形拱/014
神廟以及劇院/015
第二章拜占庭和羅馬風的建筑018
拜占庭建筑/019
羅馬風/021
諾曼風格/023
后羅馬風建筑/025
第三章伊斯蘭建筑028
倭馬亞和阿巴斯/029
非洲的伊斯蘭教/031
摩爾式建筑風格/033
奧斯曼風格/035
波斯建筑/037
莫臥兒建筑/039
第四章傳統(tǒng)的亞洲風格 042
中國傳統(tǒng)建筑/043
日本傳統(tǒng)建筑/045
廟宇建筑/047
亞洲地區(qū)建筑風格/049
第五章哥特式以及中世紀建筑052
中世紀的奠基/053
中世紀的防御工事/055
成長為哥特式/057
法式哥特/059
英式哥特/061
意大利和德國的哥特建筑/063
歐洲中部和北部的哥特建筑/065
第六章文藝復(fù)興及巴洛克建筑 068
文藝復(fù)興/069
文藝復(fù)興盛期/071
風格主義/073
都鐸王朝和詹姆斯一世時期/075
巴洛克/077
英式巴洛克/079
洛可可/081
阿爾卑斯和波西米亞/083
西班牙巴洛克風格/085
地中海風格/087
低地國家/089
俄羅斯巴洛克/091
第七章新大陸094
前哥倫布時期/095
印第安人村莊/土磚/097
西班牙人的構(gòu)想/099
英國殖民地建筑/101
鹽盒式建筑/103
美國德裔賓州人建筑/105
震教徒建筑/107
法國殖民地:阿卡迪亞教區(qū)/109
荷蘭殖民建筑/111
法國殖民地:克里奧爾及其
南部/113
非洲人的影響/115
西部本地建筑/117
西班牙殖民地/119
原木房屋/121
加勒比地區(qū)建筑風格/123
第八章古典復(fù)興建筑126
帕拉迪奧式建筑/127
法國新古典主義/129
德國新古典主義/131
喬治亞時代的藝術(shù)風格/133
聯(lián)邦制/亞當式/135
杰弗遜式/137
希臘復(fù)興式/139
英國攝政時期/141
第九章維多利亞風格144
哥特式建筑復(fù)興/145
維多利亞哥特式/147
古羅馬建筑復(fù)興/149
文藝復(fù)興時期的建筑復(fù)興/151
意大利風格建筑/153
木匠哥特式建筑/155
美洲木結(jié)構(gòu)建筑/157
安妮女王式建筑/159
木瓦風格建筑/161
理查森羅馬式/163
異國風情的復(fù)興/165
美術(shù)派風格/167
城堡風格/169
第二帝國風格/171
汽船哥特式/173
城市公寓/175
都鐸王朝風格的復(fù)興/177
“鐵器時代”唯物主義/179
英國工藝美術(shù)運動/181
第十章早期現(xiàn)代建筑184
美國的文藝復(fù)興/185
凱爾特復(fù)興/187
芝加哥學派/189
摩天大樓/191
草原風格/193
鄉(xiāng)村風格/195
涼臺平房/197
手工藝風格/西班牙傳教會式
建筑風格/199
新藝術(shù)/201
分離派/青年風格/203
加泰羅尼亞的現(xiàn)代主義/205
構(gòu)成主義/207
藝術(shù)裝飾派:爵士時代/209
藝術(shù)裝飾派:流線型/211
功能主義/213
美國風/215
包豪斯/217
國際風格/219
有機建筑(賴特)/221
第十一章戰(zhàn)后現(xiàn)代建筑224
戰(zhàn)后國際風格/225
賴特:成熟期/227
現(xiàn)代主義/229
粗野主義/野獸派/231
未來主義/233
表現(xiàn)主義/235
結(jié)構(gòu)表現(xiàn)主義/237
當代的現(xiàn)代主義/239
后現(xiàn)代主義/241
解構(gòu)主義/243
弗蘭克·蓋里/245
流體建筑/247
有機建筑/249
當代建筑趨勢/2512100433B
本書共分20講,敘述了中外建筑的起源與發(fā)展概況,對中國古建筑發(fā)展、古建筑特征、各建筑類型、近現(xiàn)代建筑和國外各歷史階段最具代表性的建筑風格、建筑流派、代表人物與代表作品進行了詳細的闡述和分析。《中外建筑...
本書是根據(jù)目前高職高專院校工程造價等專業(yè)的教學基本要求編寫而成。本書共13章,包括建筑概述,建筑制圖與識圖的基本知識,基礎(chǔ),墻體,樓板層與地面,樓梯,屋頂,門與窗,變形縫,工業(yè)建筑構(gòu)造,建筑施工圖的識...
古羅馬建筑是古羅馬人沿習亞平寧半島上伊特魯里亞人的建筑技術(shù),繼承古希臘建筑成就,在建筑形制、技術(shù)和藝術(shù)方面廣泛創(chuàng)新 的一種建筑風格。古羅馬建筑在公元一~三世紀為極盛時期,達到西方古代建筑的高峰。古羅馬...
格式:pdf
大?。?span id="kb7kb2e" class="single-tag-height">4.4MB
頁數(shù): 11頁
評分: 4.5
歐式建筑風格 VS 美式建筑風格 一、兩大風格之間的歷史背景對比 1、歐式風格的發(fā)展 歐洲建筑在大的概念上應(yīng)歸屬于西方建筑,從古希臘時代起到 20世紀三四 十年代,歐洲一直是西方建筑文明的中心, 第二次世界大戰(zhàn)結(jié)束后, 這個中心逐 漸偏移到了北美, 雖然有更為早期的歐洲原始建筑, 但是歐洲建筑真正的源頭是 古希臘文明,公元前 5世紀到公元前 4世紀,古希臘的建筑藝術(shù)達到鼎盛, 以雅 典衛(wèi)城及其神廟為代表的一個個建筑杰作橫空出世,它們簡單而純凈和諧而完 美,具有驚人的藝術(shù)創(chuàng)造力。 公元前 1世紀,古希臘人的 “光榮”被古羅馬人的 “偉大”所取代,后者興建的宮殿、 凱旋門、競技場、劇場和大浴場雄偉壯觀富麗堂皇, 他們和古希臘建筑一道被視 為垂范千古的經(jīng)典成為西方建筑文化最深刻的根源。 在接下來中世紀的 1000余年中,歐洲建筑具有兩大特點:一是基督教堂和 修道院始終是歐洲建筑的主體; 二是伴隨
來源:MRRiddler ,
blog.mrriddler.com/2017/02/10/計算機體系-編譯體系漫游/
要想讓代碼乖乖運行,自然代碼要先經(jīng)過編譯,這篇文章就來聊聊編譯體系。
代碼的編譯過程分為四個階段,預(yù)處理、編譯、匯編、鏈接。而編譯階段是整個過程中最復(fù)雜的階段,編譯階段還可以分為詞法分析、語法分析、語義分析。
在一頭扎進這四個階段之間,先聊一下語法、語義。人類之所以能在進化的歷史長河中,成為動物中的佼佼者,進化出的復(fù)雜的溝通機制—語言功不可沒。假如,我說出這句話:你個產(chǎn)品狗還在改需求!那么語法是啥呢?簡單說就是構(gòu)成這句話的順序,假如順序錯亂意思就不同了。那么語義是啥呢?就是語境,根據(jù)我說這句話的情景,才能解釋出你指的是誰。語法在編程語言中,表現(xiàn)出來的就是語法結(jié)構(gòu)和結(jié)合律。語義表現(xiàn)出來的就是上下文(context)。
預(yù)處理(Preprocess):處理預(yù)處理符(#),包括宏展開、頭文件引入。 詞法分析(Lexical Analysis、Tokenizer):寫出的代碼實際上就是字符串,此階段需要對字符串進行掃描(Scanner),將字符串掃描出分析的最基本單位(token),并在掃描過程中將它們分類,此階段是沒有任何語義的。也可以理解成將代碼掃描出基本表達式。 語法分析(Syntactic analysis、Parser):生成AST抽象語法樹,檢查語法結(jié)構(gòu),此階段是上下文無關(guān)的。也可以理解成將基本表達式按語法結(jié)構(gòu)組合成復(fù)合表達式。 語義分析(Semantic Analysis):語義檢查(比如檢查浮點數(shù)乘以指針,雖然語法結(jié)構(gòu)正確但是語義檢查不合格),將程序與上下文結(jié)合,進行靜態(tài)類型分析,確定AST每個節(jié)點的類型。也可以理解將復(fù)合表達式結(jié)合環(huán)境(Environment),并且確定基本表達式、復(fù)合表達式的類型。 中間碼(Intermediate Representation):與語言無關(guān)、平臺無關(guān)的中間碼。如果編譯器面向多語言,對于任意語言編譯階段后可以生成通用的中間碼,這樣編譯器就有多語言的高拓展性了。生成中間碼后再交給匯編階段,再生成與平臺相關(guān)的匯編,這樣使編譯器將平臺相關(guān)性盡量往后推移。中間碼除了做為“橋接“,對中間碼的優(yōu)化也是整個編譯過程中的關(guān)鍵優(yōu)化。 匯編(Assemble):對中間碼生成平臺具體的匯編,在這個階段添加對多個平臺的支持,編譯器就可以跨平臺了。最后生成機器碼。 鏈接(Link):將每個機器碼編譯單位中引用的其他編譯單位中的變量、函數(shù)符號修正(fix)成真實地址。編譯歷史
編譯的歷史基本就是計算機的進化史,是很有趣的一段故事。Long time ago… 程序員寫程序都是用紙帶,那時候還在寫0、1機器碼。紙帶上打孔就是0,不打孔就是1。然后計算機讀取紙帶就是讀取指令。但是就像互聯(lián)網(wǎng)本質(zhì)就是提高效率一樣,這樣寫程序的效率怎能接受?并且,寫程序如果犯了錯誤怎么辦?重新從頭到尾再用新紙帶搞一遍?程序員們機智的開始想辦法了,先是這樣搞:
將指帶有誤的地方,用黑黑的小貼紙?zhí)钌先ィ@樣將0改成1了。這也是Patch名字的由來。但是這樣寫指令效率還是太低,程序員們再機智的想辦法。后來將指令進行符號化(Symbol),抽象出指令集,這時就出現(xiàn)了匯編,程序員的效率上了一個檔次。但是新的問題又出現(xiàn)了。在寫過程調(diào)用的時候,要寫jmp 具體的函數(shù)地址。如果后來要在被調(diào)用的函數(shù)前面添加指令,那么函數(shù)地址也要跟著改。這樣牽一發(fā)動全身的感覺可不好,為了讓影響(impact)縮減到最小,不如將函數(shù)地址也符號化。凡是寫過程調(diào)用先寫成jmp func,等到程序生成機器碼的時候,再將func換成真正的函數(shù)地址,這一步也就是將程序員手動修正交由匯編器修正。
隨著生產(chǎn)力的提升,程序的規(guī)模越來越大,新的問題出現(xiàn)了,程序膨脹到難以維護和閱讀了。程序員們就將程序模塊化、層次化。這樣也使編譯的單位更小粒度化。編譯的時候,不同編譯單位之間的細節(jié)是互相隔離的。比如,對于C語言系,一個.h和一個.m就構(gòu)成了一個編譯單位。.m匯編時,是不知道其他.m的全局變量、函數(shù)地址的,而調(diào)用的時候就只能用符號進行調(diào)用,等到最后所有.m都生成機器碼后再進行統(tǒng)一的修正。而負責這一步的就是鏈接器(Linker),這一步也叫重定位(Relocation)。
目標文件
經(jīng)過匯編這一階段后,就會生成目標文件。目標文件和可執(zhí)行文件已經(jīng)非常相近了,只是有些符號還未修正,結(jié)構(gòu)上會進行調(diào)整。Windows平臺下為PE(Portable Executable),Linux平臺下為ELF(Executable Linkable Format),Mac平臺下為Mach-O。雖然不同平臺都有自己的格式,但是它們實際上都大同小異。下面大體聊一下文件的實際字段,這些知識會為后面我們搞一些符號重綁定做鋪墊。
section
首先,文件分段(section)。不同的Section放置不同的信息,文件還有一個section header table放置控制信息。實際上,就類似圖片格式和mutipart的HTTP報文。以下是一個ELF目標文件的常見section:
—— —— —— —— —— —— ——
|header | -----> 文件頭
|—— —— —— —— —— —— ——|
|.text | -----> 代碼段
|—— —— —— —— —— —— ——|
|.data | -----> 已初始化全局變量、靜態(tài)變量
|—— —— —— —— —— —— ——|
|.bss | -----> 未初始化全局變量、靜態(tài)變量
|—— —— —— —— —— —— ——|
|other sections... |
|—— —— —— —— —— —— ——|
|section header table| -----> section控制信息表
|—— —— —— —— —— —— ——|
|.strtab | -----> 字符串表
|—— —— —— —— —— —— ——|
|.symtab | -----> 符號表
|—— —— —— —— —— —— ——|
|..... |
—— —— —— —— —— —— —— —
.text放置代碼,.data放置已初始化的全局變量和靜態(tài)變量,.bss放置未初始化的全局變量和靜態(tài)變量。為什么代碼和全局變量、靜態(tài)變量要分開放?實際上,這就是個等同性問題。代碼段就是可讀的數(shù)據(jù),而全局變量、靜態(tài)變量是可讀可寫的數(shù)據(jù)。如果有多個進程進行同一份代碼,這些代碼都是等同的,不需要各自復(fù)制一份。而全局變量、靜態(tài)變量是不等同的,需要各自復(fù)制一份。而分什么又要分已初始化、未初始化呢?目標文件未初始化的全局、靜態(tài)變量只需要放置一個占位符,代表其在.bss。而.bss在鏈接階段,變量不占空間,在裝載時由操作系統(tǒng)再分配空間。可以看到,既然是文件格式,不管怎么設(shè)計,主要的目的就是占更少的空間。
header有很多文件控制信息,就不一一表述了,其中最重要的就是記錄了section header table的起始地址。而section header table記錄了所有section的名字、類型、長度、在文件中的偏移量(offset)等。如果想要尋址到任意section都要通過這個header table。section header table實際上是由struct構(gòu)成的數(shù)組。
.strtab放置section名、變量名,包括符號名的字符串。由于在整個結(jié)構(gòu)中,字符串的長度是不定的,一般將這些字符串統(tǒng)一放置在一個table中,然后存儲table中的offset,最后尋址到字符串。比如,在下表中,我想找到girlfriend一詞,我只要拿到.strtab的base地址,加上girlfriend的offset 9就可以找到這個字符串。
0 1 2 3 4 5 6 7 8
i 0 w a n t 0 a 0
g i r l f r i e n
d
.symtab就是大名鼎鼎的符號表。每個目標文件都有自己的符號表,符號表記錄符號的映射,符號可以這樣分:文件外符號和文件內(nèi)符號,文件外符號就是使用在其他文件定義的符號,文件內(nèi)符號除了在文件內(nèi)定義給其他文件使用的符號,還包括每個section符號,在文件內(nèi)定義光是文件內(nèi)使用的符號。光文件內(nèi)使用的符號,對鏈接沒有幫助,主要為了崩潰后分析而存在。符號表也是一個由struct構(gòu)成的數(shù)組。ELF的32位符號sturct:
typedef struct {
int32_t st_name;
uint32_t st_value;
int32_t st_size;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
} ELF32_Sym;
st_name字段就是符號的名字,表示為在.strtab中的字符串offset。st_info表示是局部符號、全局符號還是弱符號。符號也可以分為強符號(Strong Symbol)、弱符號(Weak Symbol),顧名思義,強符號有唯一性,弱符號沒有唯一性,一個強符號可以和多個弱符號共存,多個重復(fù)的強符號不可以共存,鏈接器會報出duplicate dymbol,可以用attribute((weak))指明弱符號。相對的,符號也有強引用(Strong Reference)、弱引用(Weak Reference),在鏈接進行符號修正的時候,強引用必須修正,而弱引用可以不修正,可以用attribute((weakref))指明符號弱引用。
st_shndx字段指明了符號是文件外符號,還是文件內(nèi)符號。如果是文件外符號就為SHN_UNDEF。如果是文件內(nèi)符號包括給其他文件使用的、光自己使用的、section符號,就為所在section的索引號,而st_value表示所在section的offset。等到鏈接過后,不管是文件外符號還是文件內(nèi)符號,st_value指明實際地址。
符號修飾(Symbol-Decoration)與函數(shù)簽名(Function-Signature)
機智的同學已經(jīng)發(fā)現(xiàn)了,如果光按上面聊的方式進行符號鏈接是有問題的,假如目標文件有個func符號又引用了其他文件同名的func符號,那符號不就出現(xiàn)沖突了?這里就需要引入函數(shù)簽名,函數(shù)簽名是一個函數(shù)的名字、參數(shù)類型、所在類名組成的字符串,不同語言、不同編譯器對同一個函數(shù)生成的函數(shù)簽名是不一樣的,比如OC中函數(shù)簽名還要加上返回變量類型,C++中還要加上NameSpace。在鏈接的時候,過程調(diào)用符號不光是函數(shù)名,是對函數(shù)簽名處理后的結(jié)果,全局變量符號也是經(jīng)過類似用函數(shù)簽名處理后的結(jié)果。這一處理過程就是符號修飾。
fishhook
fishhook是facebook開源的重綁定Mach-O符號的庫,最常用來hook C語言函數(shù),而且實際上只能重新綁定C符號,因為符號修飾這一步只去掉了”_”,相當于只針對C語言做了符號修飾。在了解了目標文件后,重綁定就不是那么困難了。最基本的思路就是先拿到header,然后通過header拿到section header table,再找到.hash,.hash是一個用于加快訪問.symtab的哈希結(jié)構(gòu),再索引到.symtab,詳見這里,通過name去.strtab比對符號名,如果匹配就置換value。
https://docs.oracle.com/cd/E2382401/html/819-0690/chapter6-48031.html
fishhook大體實現(xiàn)原理就是這樣,只不過對Mach-O平臺特性改進一下方案就行。在Mach-O中類似于section header table的段叫做load commands。并且Mach-O中使用二級命名空間,先分segment,就相當于上文中的section,然后再在同一segment中區(qū)分section。
先拿到header,通過header中的ncmds(segment的個數(shù))和cmdsize(segment的大小)字段就可以找到所有的segment。然后找到.strtab、.symtab、indirect symbol table。這個indirect symbol table是一個uint32_t的數(shù)組。它就是nl_symbol_ptr(non-lazy)和la_symbol_ptr(lazy )對應(yīng)的.symtab struct數(shù)組的索引。nl_symbol_ptr和la_symbol_ptr section section中的reserved1字段指明對應(yīng)的indirect symbol table起始offset。只要從這兩個section對應(yīng)的indirect symbol table起始表項再跳到.symtab去匹配、置換就可以了。
下面是32位下.symtab的struct,可以看到和上段文章講的幾乎一致:
struct nlist {
union {
char *n_name; /* for use when in-core */
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
int16_t n_desc; /* see <mach-o/stab.h> */
uint32_t n_value; /* value of this symbol (or stab offset) */
};
上文提到的Mach-O格式如下:
—— —— —— —— —— —— ——
|header |
|—— —— —— —— —— —— ——|
|load commands |
|—— —— —— —— —— —— ——|
|__Text |
|—— —— —— —— —— —— ——| —— __nl_symbol_ptr
|__Data | -----> |
|—— —— —— —— —— —— ——| —— __la_symbol_ptr
|other sections... |
|—— —— —— —— —— —— ——|
|.strtab |
|—— —— —— —— —— —— ——|
|.dynsym | -----> indirect symbol table
|—— —— —— —— —— —— ——|
|..... |
—— —— —— —— —— —— —— —
更加詳細的格式,推薦這篇文章。
http://turingh.github.io/2016/03/07/mach-o文件格式分析/
鏈接
上面聊了這么多 ,那靜態(tài)鏈接到底是如何將多個目標文件鏈接成一個可執(zhí)行文件的呢?
靜態(tài)鏈接分為兩階段(Two-pass Linking),第一階段先掃描所有目標文件,調(diào)整結(jié)構(gòu)。將所有目標文件相同section合并,包括.symtab合并成全局.symtab,然后為每個section分配虛擬地址,再將全局.symtab中的符號進行置換成虛擬地址。
這里如何將全局.symtab中的符號置換成虛擬地址呢?實際上,在分配section虛擬地址后,符號的虛擬地址按所在section虛擬地址加offset就可以計算出了。
第二階段將所有符號進行修正。通過重定位表找到所有section中需要被修正的符號位置,然后從全局.symtab查詢出虛擬地址置換。
每個section都會對應(yīng)一個重定位段,這些重定位段組成一個重定位表。每個重定位表項叫做重定位入口(Relocation Entry),它記錄了所需重定向符號所在段的offset。
靜態(tài)鏈接庫
靜態(tài)鏈接庫就是一組目標文件,經(jīng)過壓縮、索引而成的一個文件形式。當我們平時在使用靜態(tài)鏈接庫的時候,實際上鏈接器會根據(jù)所需的符號,在庫中搜索到相應(yīng)的目標文件,并將其鏈接入最終可執(zhí)行文件。
動態(tài)鏈接
隨著靜態(tài)鏈接慢慢發(fā)展起來,靜態(tài)鏈接也暴露出了問題。靜態(tài)鏈接將鏈接與被鏈接的目標文件結(jié)合的太緊密了,導(dǎo)致如果多個目標文件要鏈接同一個目標文件,那這個被鏈接的目標文件相當于要被復(fù)制多份,每個可執(zhí)行文件都要包含這個被鏈接的目標文件的內(nèi)容,這樣會占太多冗余空間。那怎么辦?將鏈接與被鏈接的目標文件先隔離開,將鏈接的時機往后推移,等到裝載的時候再進行鏈接。這樣,讓被鏈接的目標文件只占一份空間就好。
既然動態(tài)鏈接隔離開了鏈接、被鏈接目標文件,鏈接目標文件需動態(tài)鏈接的符號,就需要先做個動態(tài)鏈接占位符。這也就是說,在目標文件鏈接成可執(zhí)行文件時,即使是用作動態(tài)鏈接的目標文件也要作為動態(tài)鏈接庫輸入到鏈接階段,以供目標文件識別哪些符號是動態(tài)符號。
動態(tài)鏈接重定位
上面已經(jīng)聊完了在鏈接階段對靜態(tài)鏈接進行重定位,根據(jù)符號所在section的虛擬地址和所在section的offset。而動態(tài)鏈接可以這樣重定位嗎?不行,這時動態(tài)鏈接庫的地址還沒有確定,必須等到裝載以后操作系統(tǒng)分配。那可以等到裝載以后,確定地址后再直接進行重定位嗎?不行,假如動態(tài)鏈接庫被多個進程引用,裝載時動態(tài)鏈接庫進行重定位,動態(tài)鏈接庫映射到每個進程中的虛擬地址都不一樣,動態(tài)鏈接庫只能對一個進程重定位,那么動態(tài)鏈接庫就不是共享的了。那怎么搞?
通過.got(global offset table),.got就是一個指針數(shù)組,.got存儲引用符號的實際地址。而代碼段引用符號直接更改為引用.got項的位移,這在鏈接階段以后就不會再改變了。然后將.got分配在.data section,裝載時每個進程復(fù)制一份并修正。實際上,動態(tài)鏈接重定位指的就是在裝載的時候,根據(jù)全局符號表修正.got表項。動態(tài)鏈接庫使用全局變量、靜態(tài)變量、引用文件外過程調(diào)用都要經(jīng)過.got。.got就像indirection table一樣,解決多進程共享動態(tài)鏈接庫。
這樣,動態(tài)鏈接庫雖然在不同進程中有不同的映射虛擬空間,但物理空間上共享。.got在不同進程中,虛擬空間和物理空間都不共享。如下圖所示:
virtual address -> physical address
—— —— —— —— —— —— ——
|processA |
|—— —— —— —— —— —— ——|
|..... |
|—— —— —— —— —— —— ——|
|dynamic libiraries |----------
|—— —— —— —— —— —— ——| |
|..... | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|.got |---------|---------->|.got |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|..... | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
----------->|dynamic libiraries |
—— —— —— —— —— —— —— | |—— —— —— —— —— —— ——|
|processB | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|..... | | ------>|.got |
|—— —— —— —— —— —— ——| | | |—— —— —— —— —— —— ——|
|dynamic libiraries |---------- | |..... |
|—— —— —— —— —— —— ——| |
|..... | |
|—— —— —— —— —— —— ——| |
|.got |---------------
|—— —— —— —— —— —— ——|
|..... |
|—— —— —— —— —— —— ——|
動態(tài)鏈接庫文件外符號重定位用.got就搞定了,那動態(tài)鏈接庫文件內(nèi)符號呢?靜態(tài)鏈接同樣是在鏈接階段重定位就搞定了。動態(tài)鏈接將絕對尋址指令更換成相對尋址指令,只要指令的offset不變,相對尋址指令就可根據(jù)當前地址和offset得到正確的地址,這樣文件內(nèi)符號根本不需要重定位了?;谝陨蟽牲c的處理,代碼段在鏈接后就不需要更改了,這樣的代碼段也叫做地址無關(guān)碼(PIC),也就是說代碼段和裝載后的地址無關(guān)。
延遲綁定(PLT)
由于要跳過.got引用動態(tài)鏈接庫的符號,動態(tài)鏈接庫比靜態(tài)鏈接庫慢5%左右,但相比于節(jié)省的大量空間還是很劃算的。除此之外,動態(tài)鏈接還會有其他的問題,裝載時需要進行重定位,會導(dǎo)致性能下降。不如,直接延遲綁定,等到過程調(diào)用符號運行時被用到再進行重定位。
整個過程強烈推薦這篇文章,要想理解動態(tài)鏈接重定位,沒有比追匯編更好的方法了。動態(tài)鏈接和延遲綁定整個過程都是由動態(tài)鏈接器幫我們完成的。當引用符號(callq)時,先jmpq去plt結(jié)構(gòu),使用了PLT,引用符號就要先jmpq去plt結(jié)構(gòu)。如果沒找到相應(yīng)的地址,然后再jmpq去.got.plt或.got中。再把符號相應(yīng).rela.plt表中的索引和.got.plt相應(yīng)的表項,pushq入棧,rela.plt中有符號的類型和名字。再jmp到動態(tài)鏈接庫中(_dl_fixup),去全局符號表中找到符號相應(yīng)的地址。再將地址reloc到.got.plt或.got相應(yīng)表項。然后就完成了延遲綁定,下次引用同樣的符號就可以jmpq去plt結(jié)構(gòu)找到地址。
http://sysfork.com/post/linux-dynamic-lib-lazy-load/
引用
程序員的自我修養(yǎng)—鏈接、裝載與庫關(guān)注「ImportNew」,看技術(shù)干貨
《計算機建筑漫游實例(附光盤)》共九章。內(nèi)容有:建筑漫游動畫基礎(chǔ)知識、Premiere軟件學習、室內(nèi)一樓建模、室內(nèi)二樓建模、住宅小區(qū)室外建模、住宅小區(qū)游覽等。
當您出差在外地時,您可以在任何一臺有互聯(lián)網(wǎng)連接的電腦上,登陸到自己的傳真服務(wù)器上,實現(xiàn)您漫游查看傳真的功能。2100433B