本書(shū)介紹程序設(shè)計(jì)語(yǔ)言編譯程序構(gòu)造的一般原理、基本設(shè)計(jì)方法、主要實(shí)現(xiàn)技術(shù)和一些自動(dòng)構(gòu)造工具。主要內(nèi)容包括:編譯程序概論、文法和語(yǔ)言、詞法分析與有限自動(dòng)機(jī)、自上而下語(yǔ)法分析方法、自下而上語(yǔ)法分析方法等。
書(shū)名 | 編譯原理/計(jì)算機(jī)系列 | 出版社 | 安徽大學(xué)出版社 |
---|---|---|---|
頁(yè)數(shù) | 238頁(yè) | 開(kāi)本 | 16 |
作者 | 王一賓 陳義仁 | 出版日期 | 2014年1月1日 |
語(yǔ)種 | 簡(jiǎn)體中文 | ISBN | 7566406167 |
第1章 編譯程序概論
1.1 編程語(yǔ)言與翻譯系統(tǒng)
1.1.1程序設(shè)計(jì)語(yǔ)言
1.1.2 常用的高級(jí)語(yǔ)言
1.1.3 編譯程序的概念
1.2 編譯程序的工作過(guò)程
1.2.1 詞法分析
1.2.2 語(yǔ)法分析
1.2.3 語(yǔ)義分析和中間代碼產(chǎn)生
1.2.4 代碼優(yōu)化
1.2.5 目標(biāo)代碼生成
1. 3編譯程序的邏輯結(jié)構(gòu)
1.3.1 編譯程序的總體框架
1.3.2 編譯程序的表格管理
1.3.3 編譯程序中的錯(cuò)誤及出錯(cuò)處理
1.3.4 編譯程序的分遍處理
1.3.5 編譯前端與后端
1.4 編譯技術(shù)應(yīng)用
1.4.1 高級(jí)語(yǔ)言的實(shí)現(xiàn)
1.4.2 針對(duì)計(jì)算機(jī)體系結(jié)構(gòu)的優(yōu)化
1.4.3 新計(jì)算機(jī)體系結(jié)構(gòu)的設(shè)計(jì)
1.4.4 程序翻譯
1.4.5 提高軟件開(kāi)發(fā)效率的工具
1.5 本章小結(jié)
習(xí)題1
第2章 文法和語(yǔ)言
2.1 符號(hào)和符號(hào)串
2.2 文法和語(yǔ)言的形式定義
2.2.1 文法和上下文無(wú)關(guān)文法
2.2.2 推導(dǎo)和語(yǔ)法分析樹(shù)
2.2.3 句型、句子和語(yǔ)言
2.3 Chomsky文法分類
2.4 文法和語(yǔ)言的二義性
2.5 文法的等價(jià)及其變換
2.6 本章小結(jié)-.
習(xí)題2
第3章 詞法分析與有限自動(dòng)機(jī)
3.1 詞法分析器的設(shè)計(jì)思想
3.1.1 詞法分析器的任務(wù)和輸出形式
3.1.2 將詞法分析工作分離的考慮
3.2 詞法分析器的設(shè)計(jì)
3.2.1 輸入緩沖區(qū)和預(yù)處理程序
3.2.2 掃描器的工作原理
3.2.3 狀態(tài)轉(zhuǎn)換圖與單詞的識(shí)別
3.2.4 狀態(tài)轉(zhuǎn)換圖的代碼實(shí)現(xiàn)
實(shí)驗(yàn)一 詞法分析器的設(shè)計(jì)
3.3 單詞的描述工具
3.3.1 正規(guī)文法
3.3.2 正規(guī)式與正規(guī)集
3.4 有限自動(dòng)機(jī)
3.4.1 確定有限自動(dòng)機(jī)(DFA)
3.4.2 非確定有限自動(dòng)機(jī)(NFA)
3.4.3 將NFA轉(zhuǎn)換為DFA
3.4.4 確定有限自動(dòng)機(jī)的化簡(jiǎn)
3.5正規(guī)文法、正規(guī)式和有限自動(dòng)機(jī)的等價(jià)特性
3.5.1 正規(guī)文法與正規(guī)式的等價(jià)性
3.5.2 正規(guī)文法與有限自動(dòng)機(jī)的等價(jià)性
3.5.3 正規(guī)式與有限自動(dòng)機(jī)的等價(jià)性
3.6 詞法分析器的自動(dòng)構(gòu)造工具--LEX
3.7 本章小結(jié)
習(xí)題3
第4章 自上而下語(yǔ)法分析方法
4.1 語(yǔ)法分析的任務(wù)和分析方法
4.2 自上而下分析的基本思想和面臨的問(wèn)題
4.2.1 自上而下分析的基本思想一
4.2.2 自上而下分析存在的困難和缺陷
4.3 左遞歸和回溯的消除
4.3.1 消除直接左遞歸
4.3.2 消除間接左遞歸
4.3.3 提取左公因子消除回溯
4.4 LL(1)分析法
4.4.1 FIRST集及其計(jì)算方法
4.4.2 FOLLOW集及其計(jì)算方法
4.4.3 LL(1)文法及LL(1)判定條件
4.4.4 LL(1)分析方法
4.5 不帶回溯的自上而下分析方法
4.5.1 遞歸下降分析程序
4.5.2 預(yù)測(cè)分析程序
4.6 LL(1)分析中的錯(cuò)誤處理
實(shí)驗(yàn)二 語(yǔ)法分析器設(shè)計(jì)之一--預(yù)測(cè)分析程序
4.7本章小結(jié)
習(xí)題4
第5章 自下而上語(yǔ)法分析方法
5.1 自下而上分析的一般思想和面臨的問(wèn)題
5.1.1 歸約和"移進(jìn)一歸約"分析法
5.1.2 短語(yǔ)、句柄和最左素短語(yǔ)
5.1.3 規(guī)范歸約與規(guī)范推導(dǎo)
5.1. 4 自下而上分析的核心問(wèn)題和分析方法
5.1.5 語(yǔ)法分析棧的使用與語(yǔ)法樹(shù)的表示
5.2 算符優(yōu)先分析法
5.2.1 算符文法和算符優(yōu)先文法
5.2.2 FIRSTVT集和LASTVT集
5.2.3 算符優(yōu)先關(guān)系表及優(yōu)先函數(shù)
5.2.4 算符優(yōu)先分析算法及其特點(diǎn)
5.2.5 算符優(yōu)先分析中的出錯(cuò)處理
實(shí)驗(yàn)三 語(yǔ)法分析器設(shè)計(jì)之二--算符優(yōu)先分析程序
5.3 LR分析法
5.3.1 LR分析器的工作原理
5.3.2 LR(0)分析器
5.3.3 SLR(1)分析器
5.3.4.LR(1)分析器
5.3.5 LALR(1)分析器
5.3.6 二義文法在LR分析中的應(yīng)用
5.3.7 LR分析中的出錯(cuò)處理
實(shí)驗(yàn)四 語(yǔ)法分析器設(shè)計(jì)之三--LR分析程序
5.4 語(yǔ)法分析器的自動(dòng)產(chǎn)生工具--YACC
5.5本章小結(jié)
習(xí)題5
第6章 語(yǔ)法制導(dǎo)翻譯和語(yǔ)義分析
6.1 屬性文法與語(yǔ)法制導(dǎo)翻譯
6.1.1 屬性及屬性文法
6.1.2 綜合屬性與繼承屬性
6.1.3 S-屬性文法與L一屬性文法
6.1.4 基于屬性文法的語(yǔ)法制導(dǎo)翻譯
6.2 語(yǔ)義分析和中間代碼的產(chǎn)生
6.2.1 語(yǔ)義分析的任務(wù)
6.2.2 常見(jiàn)的中間代碼形式
6.3 簡(jiǎn)單算術(shù)表達(dá)式及賦值語(yǔ)句的翻譯
6.4 布爾表達(dá)式的翻譯
6.4.1 布爾表達(dá)式的翻譯方法
6.4.2 控制語(yǔ)句中布爾表達(dá)式的翻譯
6.5 控制結(jié)構(gòu)的翻譯
6.5.1 if語(yǔ)句的翻譯
6.5.2 while語(yǔ)句的翻譯
6.5.3 for語(yǔ)句的翻譯
6.5.4 goto語(yǔ)句的翻譯
6.6 說(shuō)明語(yǔ)句的翻譯
6.6.1 簡(jiǎn)單說(shuō)明語(yǔ)句的翻譯
6.6.2 過(guò)程中的說(shuō)明
6.7 數(shù)組的翻譯
6.7.1 數(shù)組元素的地址計(jì)算
6.7.2 賦值語(yǔ)句中數(shù)組元素的翻譯
6.8 過(guò)程調(diào)用語(yǔ)句的翻譯
6.8.1 參數(shù)傳遞的方式
6.8.2 過(guò)程調(diào)用的處理
6.9 本章小結(jié)
習(xí)題6
第7章 符號(hào)表
7.1 符號(hào)表的作用與內(nèi)容
7.1.1 符號(hào)表的作用
7.1. 2 符號(hào)表的內(nèi)容與操作
7.2 符號(hào)表的組織與管理
7.2.1 符號(hào)表的組織結(jié)構(gòu)
7.2.2 符號(hào)表的構(gòu)造與查找
7.3 名字的作用范圍
7.4 本章小結(jié)
習(xí)題7
第8章代碼優(yōu)化
8.1 優(yōu)化概述
8.2 局部?jī)?yōu)化
8.2.1 基本塊及流圖
8.2.2 基本塊的DAG表示及其應(yīng)用
8.3 循環(huán)優(yōu)化
8.3.1 代碼外提
8.3.2 強(qiáng)度削弱
8.3.3 刪除歸納變量
8.4 本章小結(jié)
習(xí)題8
第9章 目標(biāo)代碼生成
9.1 代碼生成概述
9.2 目標(biāo)機(jī)器模型
9.3 一種簡(jiǎn)單的代碼生成算法
9.3.1 活躍信息與待用信息
9.3.2 寄存器和變量地址描述
9.3.3 簡(jiǎn)單代碼生成算法
9.3.4 寄存器分配
9.4 本章小結(jié)
習(xí)題9
參考文獻(xiàn)
這個(gè)系統(tǒng)調(diào)試一般什么時(shí)候用?信息點(diǎn)怎么計(jì)算了? --:你好,這個(gè)點(diǎn)位就是設(shè)備終端的個(gè)數(shù)。 比如,打印機(jī),網(wǎng)絡(luò)插座,微機(jī),掃描儀這些能夠構(gòu)成網(wǎng)絡(luò)系統(tǒng)的設(shè)備 這個(gè)就是當(dāng)設(shè)備比較多的時(shí)候記取的一個(gè)調(diào)試。
軟件需要計(jì)算機(jī)系統(tǒng)配置問(wèn)題
CPU呢,小區(qū)的數(shù)據(jù)比較大,處理量比較費(fèi)勁
打開(kāi)控制面板-管理工具-服務(wù) 禁用Application Management服務(wù),就能解決了。具體原因不明。
格式:pdf
大?。?span id="jhwq7mh" class="single-tag-height">9KB
頁(yè)數(shù): 1頁(yè)
評(píng)分: 4.8
南航研究生計(jì)算機(jī)復(fù)試—計(jì)算機(jī)原理與編譯原理 考試大綱: 計(jì)算機(jī)原理部分 第一章 計(jì)算機(jī)各部件的作用和層次結(jié)構(gòu) 第二章 數(shù)據(jù) 一、數(shù)值、非數(shù)值數(shù)據(jù)的表示二、校驗(yàn)碼 第三章 運(yùn)算器 一、算術(shù)和邏輯運(yùn)算的實(shí)現(xiàn) 二、標(biāo)志位 第四章 存儲(chǔ)系統(tǒng)一、存儲(chǔ)器分類、性能指標(biāo)二、存儲(chǔ)器擴(kuò)展方法三、高速 緩存工作原理四、磁表面和光存儲(chǔ)器 第五章 指令系統(tǒng)一、指令格式和尋址方式二、掌握 8086基本指令系統(tǒng)及簡(jiǎn) 單匯編語(yǔ)言編程方法 * 第六章 CPU組織 一、CPU的結(jié)構(gòu)與功能二、 CPU控制流程和時(shí)序三、組 合邏輯和微程序控制器設(shè)計(jì)四、掌握 INTEL 微處理器基本結(jié)構(gòu)特征 * 第七章 I/O 組織一、I/O 接口及工作原理 * 二、程序控制傳送和中斷機(jī)制三、 DMA、通道和 I/O 處理機(jī)注:帶 *的部分可以參考《微機(jī)原理與接口技術(shù)》教材 編譯原理部分 第一章:了解有關(guān)編譯程序的基本概念、結(jié)構(gòu) 第二章:掌
來(lái)源:MRRiddler ,
blog.mrriddler.com/2017/02/10/計(jì)算機(jī)體系-編譯體系漫游/
要想讓代碼乖乖運(yùn)行,自然代碼要先經(jīng)過(guò)編譯,這篇文章就來(lái)聊聊編譯體系。
代碼的編譯過(guò)程分為四個(gè)階段,預(yù)處理、編譯、匯編、鏈接。而編譯階段是整個(gè)過(guò)程中最復(fù)雜的階段,編譯階段還可以分為詞法分析、語(yǔ)法分析、語(yǔ)義分析。
在一頭扎進(jìn)這四個(gè)階段之間,先聊一下語(yǔ)法、語(yǔ)義。人類之所以能在進(jìn)化的歷史長(zhǎng)河中,成為動(dòng)物中的佼佼者,進(jìn)化出的復(fù)雜的溝通機(jī)制—語(yǔ)言功不可沒(méi)。假如,我說(shuō)出這句話:你個(gè)產(chǎn)品狗還在改需求!那么語(yǔ)法是啥呢?簡(jiǎn)單說(shuō)就是構(gòu)成這句話的順序,假如順序錯(cuò)亂意思就不同了。那么語(yǔ)義是啥呢?就是語(yǔ)境,根據(jù)我說(shuō)這句話的情景,才能解釋出你指的是誰(shuí)。語(yǔ)法在編程語(yǔ)言中,表現(xiàn)出來(lái)的就是語(yǔ)法結(jié)構(gòu)和結(jié)合律。語(yǔ)義表現(xiàn)出來(lái)的就是上下文(context)。
預(yù)處理(Preprocess):處理預(yù)處理符(#),包括宏展開(kāi)、頭文件引入。 詞法分析(Lexical Analysis、Tokenizer):寫(xiě)出的代碼實(shí)際上就是字符串,此階段需要對(duì)字符串進(jìn)行掃描(Scanner),將字符串掃描出分析的最基本單位(token),并在掃描過(guò)程中將它們分類,此階段是沒(méi)有任何語(yǔ)義的。也可以理解成將代碼掃描出基本表達(dá)式。 語(yǔ)法分析(Syntactic analysis、Parser):生成AST抽象語(yǔ)法樹(shù),檢查語(yǔ)法結(jié)構(gòu),此階段是上下文無(wú)關(guān)的。也可以理解成將基本表達(dá)式按語(yǔ)法結(jié)構(gòu)組合成復(fù)合表達(dá)式。 語(yǔ)義分析(Semantic Analysis):語(yǔ)義檢查(比如檢查浮點(diǎn)數(shù)乘以指針,雖然語(yǔ)法結(jié)構(gòu)正確但是語(yǔ)義檢查不合格),將程序與上下文結(jié)合,進(jìn)行靜態(tài)類型分析,確定AST每個(gè)節(jié)點(diǎn)的類型。也可以理解將復(fù)合表達(dá)式結(jié)合環(huán)境(Environment),并且確定基本表達(dá)式、復(fù)合表達(dá)式的類型。 中間碼(Intermediate Representation):與語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)的中間碼。如果編譯器面向多語(yǔ)言,對(duì)于任意語(yǔ)言編譯階段后可以生成通用的中間碼,這樣編譯器就有多語(yǔ)言的高拓展性了。生成中間碼后再交給匯編階段,再生成與平臺(tái)相關(guān)的匯編,這樣使編譯器將平臺(tái)相關(guān)性盡量往后推移。中間碼除了做為“橋接“,對(duì)中間碼的優(yōu)化也是整個(gè)編譯過(guò)程中的關(guān)鍵優(yōu)化。 匯編(Assemble):對(duì)中間碼生成平臺(tái)具體的匯編,在這個(gè)階段添加對(duì)多個(gè)平臺(tái)的支持,編譯器就可以跨平臺(tái)了。最后生成機(jī)器碼。 鏈接(Link):將每個(gè)機(jī)器碼編譯單位中引用的其他編譯單位中的變量、函數(shù)符號(hào)修正(fix)成真實(shí)地址。編譯歷史
編譯的歷史基本就是計(jì)算機(jī)的進(jìn)化史,是很有趣的一段故事。Long time ago… 程序員寫(xiě)程序都是用紙帶,那時(shí)候還在寫(xiě)0、1機(jī)器碼。紙帶上打孔就是0,不打孔就是1。然后計(jì)算機(jī)讀取紙帶就是讀取指令。但是就像互聯(lián)網(wǎng)本質(zhì)就是提高效率一樣,這樣寫(xiě)程序的效率怎能接受?并且,寫(xiě)程序如果犯了錯(cuò)誤怎么辦?重新從頭到尾再用新紙帶搞一遍?程序員們機(jī)智的開(kāi)始想辦法了,先是這樣搞:
將指帶有誤的地方,用黑黑的小貼紙?zhí)钌先?,這樣將0改成1了。這也是Patch名字的由來(lái)。但是這樣寫(xiě)指令效率還是太低,程序員們?cè)贆C(jī)智的想辦法。后來(lái)將指令進(jìn)行符號(hào)化(Symbol),抽象出指令集,這時(shí)就出現(xiàn)了匯編,程序員的效率上了一個(gè)檔次。但是新的問(wèn)題又出現(xiàn)了。在寫(xiě)過(guò)程調(diào)用的時(shí)候,要寫(xiě)jmp 具體的函數(shù)地址。如果后來(lái)要在被調(diào)用的函數(shù)前面添加指令,那么函數(shù)地址也要跟著改。這樣牽一發(fā)動(dòng)全身的感覺(jué)可不好,為了讓影響(impact)縮減到最小,不如將函數(shù)地址也符號(hào)化。凡是寫(xiě)過(guò)程調(diào)用先寫(xiě)成jmp func,等到程序生成機(jī)器碼的時(shí)候,再將func換成真正的函數(shù)地址,這一步也就是將程序員手動(dòng)修正交由匯編器修正。
隨著生產(chǎn)力的提升,程序的規(guī)模越來(lái)越大,新的問(wèn)題出現(xiàn)了,程序膨脹到難以維護(hù)和閱讀了。程序員們就將程序模塊化、層次化。這樣也使編譯的單位更小粒度化。編譯的時(shí)候,不同編譯單位之間的細(xì)節(jié)是互相隔離的。比如,對(duì)于C語(yǔ)言系,一個(gè).h和一個(gè).m就構(gòu)成了一個(gè)編譯單位。.m匯編時(shí),是不知道其他.m的全局變量、函數(shù)地址的,而調(diào)用的時(shí)候就只能用符號(hào)進(jìn)行調(diào)用,等到最后所有.m都生成機(jī)器碼后再進(jìn)行統(tǒng)一的修正。而負(fù)責(zé)這一步的就是鏈接器(Linker),這一步也叫重定位(Relocation)。
目標(biāo)文件
經(jīng)過(guò)匯編這一階段后,就會(huì)生成目標(biāo)文件。目標(biāo)文件和可執(zhí)行文件已經(jīng)非常相近了,只是有些符號(hào)還未修正,結(jié)構(gòu)上會(huì)進(jìn)行調(diào)整。Windows平臺(tái)下為PE(Portable Executable),Linux平臺(tái)下為ELF(Executable Linkable Format),Mac平臺(tái)下為Mach-O。雖然不同平臺(tái)都有自己的格式,但是它們實(shí)際上都大同小異。下面大體聊一下文件的實(shí)際字段,這些知識(shí)會(huì)為后面我們搞一些符號(hào)重綁定做鋪墊。
section
首先,文件分段(section)。不同的Section放置不同的信息,文件還有一個(gè)section header table放置控制信息。實(shí)際上,就類似圖片格式和mutipart的HTTP報(bào)文。以下是一個(gè)ELF目標(biāo)文件的常見(jiàn)section:
—— —— —— —— —— —— ——
|header | -----> 文件頭
|—— —— —— —— —— —— ——|
|.text | -----> 代碼段
|—— —— —— —— —— —— ——|
|.data | -----> 已初始化全局變量、靜態(tài)變量
|—— —— —— —— —— —— ——|
|.bss | -----> 未初始化全局變量、靜態(tài)變量
|—— —— —— —— —— —— ——|
|other sections... |
|—— —— —— —— —— —— ——|
|section header table| -----> section控制信息表
|—— —— —— —— —— —— ——|
|.strtab | -----> 字符串表
|—— —— —— —— —— —— ——|
|.symtab | -----> 符號(hào)表
|—— —— —— —— —— —— ——|
|..... |
—— —— —— —— —— —— —— —
.text放置代碼,.data放置已初始化的全局變量和靜態(tài)變量,.bss放置未初始化的全局變量和靜態(tài)變量。為什么代碼和全局變量、靜態(tài)變量要分開(kāi)放?實(shí)際上,這就是個(gè)等同性問(wèn)題。代碼段就是可讀的數(shù)據(jù),而全局變量、靜態(tài)變量是可讀可寫(xiě)的數(shù)據(jù)。如果有多個(gè)進(jìn)程進(jìn)行同一份代碼,這些代碼都是等同的,不需要各自復(fù)制一份。而全局變量、靜態(tài)變量是不等同的,需要各自復(fù)制一份。而分什么又要分已初始化、未初始化呢?目標(biāo)文件未初始化的全局、靜態(tài)變量只需要放置一個(gè)占位符,代表其在.bss。而.bss在鏈接階段,變量不占空間,在裝載時(shí)由操作系統(tǒng)再分配空間??梢钥吹剑热皇俏募袷?,不管怎么設(shè)計(jì),主要的目的就是占更少的空間。
header有很多文件控制信息,就不一一表述了,其中最重要的就是記錄了section header table的起始地址。而section header table記錄了所有section的名字、類型、長(zhǎng)度、在文件中的偏移量(offset)等。如果想要尋址到任意section都要通過(guò)這個(gè)header table。section header table實(shí)際上是由struct構(gòu)成的數(shù)組。
.strtab放置section名、變量名,包括符號(hào)名的字符串。由于在整個(gè)結(jié)構(gòu)中,字符串的長(zhǎng)度是不定的,一般將這些字符串統(tǒng)一放置在一個(gè)table中,然后存儲(chǔ)table中的offset,最后尋址到字符串。比如,在下表中,我想找到girlfriend一詞,我只要拿到.strtab的base地址,加上girlfriend的offset 9就可以找到這個(gè)字符串。
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就是大名鼎鼎的符號(hào)表。每個(gè)目標(biāo)文件都有自己的符號(hào)表,符號(hào)表記錄符號(hào)的映射,符號(hào)可以這樣分:文件外符號(hào)和文件內(nèi)符號(hào),文件外符號(hào)就是使用在其他文件定義的符號(hào),文件內(nèi)符號(hào)除了在文件內(nèi)定義給其他文件使用的符號(hào),還包括每個(gè)section符號(hào),在文件內(nèi)定義光是文件內(nèi)使用的符號(hào)。光文件內(nèi)使用的符號(hào),對(duì)鏈接沒(méi)有幫助,主要為了崩潰后分析而存在。符號(hào)表也是一個(gè)由struct構(gòu)成的數(shù)組。ELF的32位符號(hào)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字段就是符號(hào)的名字,表示為在.strtab中的字符串offset。st_info表示是局部符號(hào)、全局符號(hào)還是弱符號(hào)。符號(hào)也可以分為強(qiáng)符號(hào)(Strong Symbol)、弱符號(hào)(Weak Symbol),顧名思義,強(qiáng)符號(hào)有唯一性,弱符號(hào)沒(méi)有唯一性,一個(gè)強(qiáng)符號(hào)可以和多個(gè)弱符號(hào)共存,多個(gè)重復(fù)的強(qiáng)符號(hào)不可以共存,鏈接器會(huì)報(bào)出duplicate dymbol,可以用attribute((weak))指明弱符號(hào)。相對(duì)的,符號(hào)也有強(qiáng)引用(Strong Reference)、弱引用(Weak Reference),在鏈接進(jìn)行符號(hào)修正的時(shí)候,強(qiáng)引用必須修正,而弱引用可以不修正,可以用attribute((weakref))指明符號(hào)弱引用。
st_shndx字段指明了符號(hào)是文件外符號(hào),還是文件內(nèi)符號(hào)。如果是文件外符號(hào)就為SHN_UNDEF。如果是文件內(nèi)符號(hào)包括給其他文件使用的、光自己使用的、section符號(hào),就為所在section的索引號(hào),而st_value表示所在section的offset。等到鏈接過(guò)后,不管是文件外符號(hào)還是文件內(nèi)符號(hào),st_value指明實(shí)際地址。
符號(hào)修飾(Symbol-Decoration)與函數(shù)簽名(Function-Signature)
機(jī)智的同學(xué)已經(jīng)發(fā)現(xiàn)了,如果光按上面聊的方式進(jìn)行符號(hào)鏈接是有問(wèn)題的,假如目標(biāo)文件有個(gè)func符號(hào)又引用了其他文件同名的func符號(hào),那符號(hào)不就出現(xiàn)沖突了?這里就需要引入函數(shù)簽名,函數(shù)簽名是一個(gè)函數(shù)的名字、參數(shù)類型、所在類名組成的字符串,不同語(yǔ)言、不同編譯器對(duì)同一個(gè)函數(shù)生成的函數(shù)簽名是不一樣的,比如OC中函數(shù)簽名還要加上返回變量類型,C++中還要加上NameSpace。在鏈接的時(shí)候,過(guò)程調(diào)用符號(hào)不光是函數(shù)名,是對(duì)函數(shù)簽名處理后的結(jié)果,全局變量符號(hào)也是經(jīng)過(guò)類似用函數(shù)簽名處理后的結(jié)果。這一處理過(guò)程就是符號(hào)修飾。
fishhook
fishhook是facebook開(kāi)源的重綁定Mach-O符號(hào)的庫(kù),最常用來(lái)hook C語(yǔ)言函數(shù),而且實(shí)際上只能重新綁定C符號(hào),因?yàn)榉?hào)修飾這一步只去掉了”_”,相當(dāng)于只針對(duì)C語(yǔ)言做了符號(hào)修飾。在了解了目標(biāo)文件后,重綁定就不是那么困難了。最基本的思路就是先拿到header,然后通過(guò)header拿到section header table,再找到.hash,.hash是一個(gè)用于加快訪問(wèn).symtab的哈希結(jié)構(gòu),再索引到.symtab,詳見(jiàn)這里,通過(guò)name去.strtab比對(duì)符號(hào)名,如果匹配就置換value。
https://docs.oracle.com/cd/E2382401/html/819-0690/chapter6-48031.html
fishhook大體實(shí)現(xiàn)原理就是這樣,只不過(guò)對(duì)Mach-O平臺(tái)特性改進(jìn)一下方案就行。在Mach-O中類似于section header table的段叫做load commands。并且Mach-O中使用二級(jí)命名空間,先分segment,就相當(dāng)于上文中的section,然后再在同一segment中區(qū)分section。
先拿到header,通過(guò)header中的ncmds(segment的個(gè)數(shù))和cmdsize(segment的大小)字段就可以找到所有的segment。然后找到.strtab、.symtab、indirect symbol table。這個(gè)indirect symbol table是一個(gè)uint32_t的數(shù)組。它就是nl_symbol_ptr(non-lazy)和la_symbol_ptr(lazy )對(duì)應(yīng)的.symtab struct數(shù)組的索引。nl_symbol_ptr和la_symbol_ptr section section中的reserved1字段指明對(duì)應(yīng)的indirect symbol table起始o(jì)ffset。只要從這兩個(gè)section對(duì)應(yīng)的indirect symbol table起始表項(xiàng)再跳到.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
|—— —— —— —— —— —— ——|
|..... |
—— —— —— —— —— —— —— —
更加詳細(xì)的格式,推薦這篇文章。
http://turingh.github.io/2016/03/07/mach-o文件格式分析/
鏈接
上面聊了這么多 ,那靜態(tài)鏈接到底是如何將多個(gè)目標(biāo)文件鏈接成一個(gè)可執(zhí)行文件的呢?
靜態(tài)鏈接分為兩階段(Two-pass Linking),第一階段先掃描所有目標(biāo)文件,調(diào)整結(jié)構(gòu)。將所有目標(biāo)文件相同section合并,包括.symtab合并成全局.symtab,然后為每個(gè)section分配虛擬地址,再將全局.symtab中的符號(hào)進(jìn)行置換成虛擬地址。
這里如何將全局.symtab中的符號(hào)置換成虛擬地址呢?實(shí)際上,在分配section虛擬地址后,符號(hào)的虛擬地址按所在section虛擬地址加offset就可以計(jì)算出了。
第二階段將所有符號(hào)進(jìn)行修正。通過(guò)重定位表找到所有section中需要被修正的符號(hào)位置,然后從全局.symtab查詢出虛擬地址置換。
每個(gè)section都會(huì)對(duì)應(yīng)一個(gè)重定位段,這些重定位段組成一個(gè)重定位表。每個(gè)重定位表項(xiàng)叫做重定位入口(Relocation Entry),它記錄了所需重定向符號(hào)所在段的offset。
靜態(tài)鏈接庫(kù)
靜態(tài)鏈接庫(kù)就是一組目標(biāo)文件,經(jīng)過(guò)壓縮、索引而成的一個(gè)文件形式。當(dāng)我們平時(shí)在使用靜態(tài)鏈接庫(kù)的時(shí)候,實(shí)際上鏈接器會(huì)根據(jù)所需的符號(hào),在庫(kù)中搜索到相應(yīng)的目標(biāo)文件,并將其鏈接入最終可執(zhí)行文件。
動(dòng)態(tài)鏈接
隨著靜態(tài)鏈接慢慢發(fā)展起來(lái),靜態(tài)鏈接也暴露出了問(wèn)題。靜態(tài)鏈接將鏈接與被鏈接的目標(biāo)文件結(jié)合的太緊密了,導(dǎo)致如果多個(gè)目標(biāo)文件要鏈接同一個(gè)目標(biāo)文件,那這個(gè)被鏈接的目標(biāo)文件相當(dāng)于要被復(fù)制多份,每個(gè)可執(zhí)行文件都要包含這個(gè)被鏈接的目標(biāo)文件的內(nèi)容,這樣會(huì)占太多冗余空間。那怎么辦?將鏈接與被鏈接的目標(biāo)文件先隔離開(kāi),將鏈接的時(shí)機(jī)往后推移,等到裝載的時(shí)候再進(jìn)行鏈接。這樣,讓被鏈接的目標(biāo)文件只占一份空間就好。
既然動(dòng)態(tài)鏈接隔離開(kāi)了鏈接、被鏈接目標(biāo)文件,鏈接目標(biāo)文件需動(dòng)態(tài)鏈接的符號(hào),就需要先做個(gè)動(dòng)態(tài)鏈接占位符。這也就是說(shuō),在目標(biāo)文件鏈接成可執(zhí)行文件時(shí),即使是用作動(dòng)態(tài)鏈接的目標(biāo)文件也要作為動(dòng)態(tài)鏈接庫(kù)輸入到鏈接階段,以供目標(biāo)文件識(shí)別哪些符號(hào)是動(dòng)態(tài)符號(hào)。
動(dòng)態(tài)鏈接重定位
上面已經(jīng)聊完了在鏈接階段對(duì)靜態(tài)鏈接進(jìn)行重定位,根據(jù)符號(hào)所在section的虛擬地址和所在section的offset。而動(dòng)態(tài)鏈接可以這樣重定位嗎?不行,這時(shí)動(dòng)態(tài)鏈接庫(kù)的地址還沒(méi)有確定,必須等到裝載以后操作系統(tǒng)分配。那可以等到裝載以后,確定地址后再直接進(jìn)行重定位嗎?不行,假如動(dòng)態(tài)鏈接庫(kù)被多個(gè)進(jìn)程引用,裝載時(shí)動(dòng)態(tài)鏈接庫(kù)進(jìn)行重定位,動(dòng)態(tài)鏈接庫(kù)映射到每個(gè)進(jìn)程中的虛擬地址都不一樣,動(dòng)態(tài)鏈接庫(kù)只能對(duì)一個(gè)進(jìn)程重定位,那么動(dòng)態(tài)鏈接庫(kù)就不是共享的了。那怎么搞?
通過(guò).got(global offset table),.got就是一個(gè)指針數(shù)組,.got存儲(chǔ)引用符號(hào)的實(shí)際地址。而代碼段引用符號(hào)直接更改為引用.got項(xiàng)的位移,這在鏈接階段以后就不會(huì)再改變了。然后將.got分配在.data section,裝載時(shí)每個(gè)進(jìn)程復(fù)制一份并修正。實(shí)際上,動(dòng)態(tài)鏈接重定位指的就是在裝載的時(shí)候,根據(jù)全局符號(hào)表修正.got表項(xiàng)。動(dòng)態(tài)鏈接庫(kù)使用全局變量、靜態(tài)變量、引用文件外過(guò)程調(diào)用都要經(jīng)過(guò).got。.got就像indirection table一樣,解決多進(jìn)程共享動(dòng)態(tài)鏈接庫(kù)。
這樣,動(dòng)態(tài)鏈接庫(kù)雖然在不同進(jìn)程中有不同的映射虛擬空間,但物理空間上共享。.got在不同進(jìn)程中,虛擬空間和物理空間都不共享。如下圖所示:
virtual address -> physical address
—— —— —— —— —— —— ——
|processA |
|—— —— —— —— —— —— ——|
|..... |
|—— —— —— —— —— —— ——|
|dynamic libiraries |----------
|—— —— —— —— —— —— ——| |
|..... | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|.got |---------|---------->|.got |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|..... | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
----------->|dynamic libiraries |
—— —— —— —— —— —— —— | |—— —— —— —— —— —— ——|
|processB | | |..... |
|—— —— —— —— —— —— ——| | |—— —— —— —— —— —— ——|
|..... | | ------>|.got |
|—— —— —— —— —— —— ——| | | |—— —— —— —— —— —— ——|
|dynamic libiraries |---------- | |..... |
|—— —— —— —— —— —— ——| |
|..... | |
|—— —— —— —— —— —— ——| |
|.got |---------------
|—— —— —— —— —— —— ——|
|..... |
|—— —— —— —— —— —— ——|
動(dòng)態(tài)鏈接庫(kù)文件外符號(hào)重定位用.got就搞定了,那動(dòng)態(tài)鏈接庫(kù)文件內(nèi)符號(hào)呢?靜態(tài)鏈接同樣是在鏈接階段重定位就搞定了。動(dòng)態(tài)鏈接將絕對(duì)尋址指令更換成相對(duì)尋址指令,只要指令的offset不變,相對(duì)尋址指令就可根據(jù)當(dāng)前地址和offset得到正確的地址,這樣文件內(nèi)符號(hào)根本不需要重定位了?;谝陨蟽牲c(diǎn)的處理,代碼段在鏈接后就不需要更改了,這樣的代碼段也叫做地址無(wú)關(guān)碼(PIC),也就是說(shuō)代碼段和裝載后的地址無(wú)關(guān)。
延遲綁定(PLT)
由于要跳過(guò).got引用動(dòng)態(tài)鏈接庫(kù)的符號(hào),動(dòng)態(tài)鏈接庫(kù)比靜態(tài)鏈接庫(kù)慢5%左右,但相比于節(jié)省的大量空間還是很劃算的。除此之外,動(dòng)態(tài)鏈接還會(huì)有其他的問(wèn)題,裝載時(shí)需要進(jìn)行重定位,會(huì)導(dǎo)致性能下降。不如,直接延遲綁定,等到過(guò)程調(diào)用符號(hào)運(yùn)行時(shí)被用到再進(jìn)行重定位。
整個(gè)過(guò)程強(qiáng)烈推薦這篇文章,要想理解動(dòng)態(tài)鏈接重定位,沒(méi)有比追匯編更好的方法了。動(dòng)態(tài)鏈接和延遲綁定整個(gè)過(guò)程都是由動(dòng)態(tài)鏈接器幫我們完成的。當(dāng)引用符號(hào)(callq)時(shí),先jmpq去plt結(jié)構(gòu),使用了PLT,引用符號(hào)就要先jmpq去plt結(jié)構(gòu)。如果沒(méi)找到相應(yīng)的地址,然后再jmpq去.got.plt或.got中。再把符號(hào)相應(yīng).rela.plt表中的索引和.got.plt相應(yīng)的表項(xiàng),pushq入棧,rela.plt中有符號(hào)的類型和名字。再jmp到動(dòng)態(tài)鏈接庫(kù)中(_dl_fixup),去全局符號(hào)表中找到符號(hào)相應(yīng)的地址。再將地址reloc到.got.plt或.got相應(yīng)表項(xiàng)。然后就完成了延遲綁定,下次引用同樣的符號(hào)就可以jmpq去plt結(jié)構(gòu)找到地址。
http://sysfork.com/post/linux-dynamic-lib-lazy-load/
引用
程序員的自我修養(yǎng)—鏈接、裝載與庫(kù)關(guān)注「ImportNew」,看技術(shù)干貨
編譯是從源代碼(通常為高級(jí)語(yǔ)言)到能直接被計(jì)算機(jī)或虛擬機(jī)執(zhí)行的目標(biāo)代碼(通常為低級(jí)語(yǔ)言或機(jī)器語(yǔ)言)的翻譯過(guò)程。然而,也存在從低級(jí)語(yǔ)言到高級(jí)語(yǔ)言的編譯器,這類編譯器中用來(lái)從由高級(jí)語(yǔ)言生成的低級(jí)語(yǔ)言代碼重新生成高級(jí)語(yǔ)言代碼的又被叫做反編譯器。也有從一種高級(jí)語(yǔ)言生成另一種高級(jí)語(yǔ)言的編譯器,或者生成一種需要進(jìn)一步處理的的中間代碼的編譯器(又叫級(jí)聯(lián))。
典型的編譯器輸出是由包含入口點(diǎn)的名字和地址, 以及外部調(diào)用(到不在這個(gè)目標(biāo)文件中的函數(shù)調(diào)用)的機(jī)器代碼所組成的目標(biāo)文件。一組目標(biāo)文件,不必是同一編譯器產(chǎn)生,但使用的編譯器必需采用同樣的輸出格式,可以鏈接在一起并生成可以由用戶直接執(zhí)行的EXE,
所以我們電腦上的文件都是經(jīng)過(guò)編譯后的文件。
一、什么是編譯
1、利用編譯程序從源語(yǔ)言編寫(xiě)的源程序產(chǎn)生目標(biāo)程序的過(guò)程。
2、用編譯程序產(chǎn)生目標(biāo)程序的動(dòng)作。 編譯就是把高級(jí)語(yǔ)言變成計(jì)算機(jī)可以識(shí)別的2進(jìn)制語(yǔ)言,計(jì)算機(jī)只認(rèn)識(shí)1和0,編譯程序把人們熟悉的語(yǔ)言換成2進(jìn)制的。 編譯程序把一個(gè)源程序翻譯成目標(biāo)程序的工作過(guò)程分為五個(gè)階段:詞法分析;語(yǔ)法分析;語(yǔ)義檢查和中間代碼生成;代碼優(yōu)化;目標(biāo)代碼生成。主要是進(jìn)行詞法分析和語(yǔ)法分析,又稱為源程序分析,分析過(guò)程中發(fā)現(xiàn)有語(yǔ)法錯(cuò)誤,給出提示信息。
二、什么是反編譯
計(jì)算機(jī)軟件反向工程(Reverse engineering)也稱為計(jì)算機(jī)軟件還原工程,是指通過(guò)對(duì)他人軟件的目標(biāo)程序(可執(zhí)行程序)進(jìn)行“逆向分析、研究”工作,以推導(dǎo)出他人的軟件產(chǎn)品所使用的思路、原理、結(jié)構(gòu)、算法、處理過(guò)程、運(yùn)行方法等設(shè)計(jì)要素,某些特定情況下可能推導(dǎo)出源代碼。反編譯作為自己開(kāi)發(fā)軟件時(shí)的參考,或者直接用于自己的軟件產(chǎn)品中。
三、 Java類的編譯與反編譯
我們?cè)谧畛鯇W(xué)習(xí)Java的時(shí)候,會(huì)接觸到兩個(gè)命令:javac和java,那個(gè)時(shí)候我們就知道,javac是用來(lái)編譯Java類的,就是將我們寫(xiě)好的helloworld.java文件編譯成helloworld.class文件。
class文件打破了C或者C++等語(yǔ)言所遵循的傳統(tǒng),使用這些傳統(tǒng)語(yǔ)言寫(xiě)的程序通常首先被編譯,然后被連接成單獨(dú)的、專門(mén)支持特定硬件平臺(tái)和操作系統(tǒng)的二進(jìn)制文件。通常情況下,一個(gè)平臺(tái)上的二進(jìn)制可執(zhí)行文件不能在其他平臺(tái)上工作。而Java class文件是可以運(yùn)行在任何支持Java虛擬機(jī)的硬件平臺(tái)和操作系統(tǒng)上的二進(jìn)制文件。
那么反編譯呢,就是通過(guò)helloworld.class文件得到j(luò)ava文件(或者說(shuō)是程序員能看懂的Java文件)
四、什么時(shí)候會(huì)用到反編譯
1、我們只有一個(gè)類的class文件,但是我們又看不懂Java的class文件,那么我們可以把它反編譯成我們可以看得懂的文件。
2、學(xué)習(xí)Java過(guò)程中,JDK的每個(gè)版本都會(huì)加入越來(lái)越多的語(yǔ)法糖,有些時(shí)候我們想知道Java一些實(shí)現(xiàn)細(xì)節(jié),我們可以借助反編譯。
關(guān)注“動(dòng)力節(jié)點(diǎn)Java學(xué)院”微信公眾號(hào),獲取更多最新Java技術(shù),如果你對(duì)編程有興趣,想要成為優(yōu)秀的Java程序員,那么動(dòng)力節(jié)點(diǎn)Java零基礎(chǔ)班現(xiàn)已開(kāi)啟免費(fèi)試學(xué)階段,對(duì)于想學(xué)Java的同學(xué)無(wú)疑是好消息,親自考察教學(xué)質(zhì)量,機(jī)會(huì)就在眼前,針對(duì)不方便前來(lái)的同學(xué),可以關(guān)注動(dòng)力節(jié)點(diǎn)Java全套免費(fèi)視頻,趕快學(xué)起來(lái)吧