欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
GCC 的編譯流程及中間表示層 RTL 的初步探索
本文將以 C 語(yǔ)言為例,介紹 gcc 在接受一個(gè) .c 文件的輸入之后,其前端是如何進(jìn)行處理并得到一個(gè)中間表示并轉交給后端處理。然后,在了解了 gcc[1] 的工作流程后,介紹一下作者嘗試在 gcc 內部的 RTL 表示層中 hack gcc 的過(guò)程,與大家分享一些經(jīng)驗,希望能給對有興趣研究和開(kāi)發(fā) gcc 的讀者有所幫助。
編譯器的工作是將源代碼(通常使用高級語(yǔ)言編寫(xiě))翻譯成目標代碼(通常是低級的目標代碼或者機器語(yǔ)言),在現代編譯器的實(shí)現中,這個(gè)工作一般是分為兩個(gè)階段來(lái)實(shí)現的:
第一階段,編譯器的前端接受輸入的源代碼,經(jīng)過(guò)詞法、語(yǔ)法和語(yǔ)義分析等等得到源程序的某種中間表示方式。
第二階段,編譯器的后端將前端處理生成的中間表示方式進(jìn)行一些優(yōu)化,并最終生成在目標機器上可運行的代碼。
GCC(GNU Compiler Collection) 是在 UNIX 以及類(lèi) UNIX 平臺上廣泛使用的編譯器集合,它能夠支持多種語(yǔ)言前端,包括 C, C++, Objective-C, Ada, Fortran, Java 和 treelang 等。
GCC 設計中有兩個(gè)重要的目標,其中一個(gè)是在構建支持不同硬件平臺的編譯器時(shí),它的代碼能夠最大程度的被復用,所以 GCC 必須要做到一定程度的硬件無(wú)關(guān)性;另一個(gè)是要生成高質(zhì)量的可執行代碼,這就需要對代碼進(jìn)行集中的優(yōu)化。為了實(shí)現這兩個(gè)目標,GCC 內部使用了一種硬件平臺無(wú)關(guān)的語(yǔ)言,它能對實(shí)際的體系結構做一種抽象,這個(gè)中間語(yǔ)言就是 RTL(Register Transfer Language)。
雖然關(guān)于 GCC 的研究和開(kāi)發(fā)工作側重于 GCC 后端代碼優(yōu)化方面,但本文中我們關(guān)注的目標是在 GCC 的編譯過(guò)程中前端是如何工作的。
把 GCC 的前端獨立出來(lái)研究目的在于,在設計新的編譯器的時(shí)候,我們僅僅需要關(guān)注如何設計新編譯器的前端,而將代碼優(yōu)化和目標代碼的生成留給 GCC 后端去完成,避免了后端設計的重復性勞動(dòng)。
本文將以 C 語(yǔ)言為例,介紹 gcc[2] 在接受一個(gè) .c 文件的輸入之后,其前端是如何進(jìn)行處理并得到一個(gè)中間表示并轉交給后端處理。然后,在了解了 gcc 的工作流程后,介紹一下作者嘗試在 gcc 內部的RTL表示層中 hack gcc 的過(guò)程,與大家分享一些經(jīng)驗,希望能給對有興趣研究和開(kāi)發(fā) gcc 的讀者有所幫助。
[1] 如無(wú)特別說(shuō)明,下文中 gcc 均指GNU C Compiler,即GCC編譯器集合中的C語(yǔ)言編譯器。
gcc 是一個(gè)驅動(dòng)程序,它接受并解釋命令行參數,根據對命令行參數分析的結果決定下一步動(dòng)作,gcc 提供了多種選項以達到控制 gcc 編譯過(guò)程的目的,我們可以在 GCC 的手冊中查找這些編譯選項的詳細信息。
gcc 的使用是比較簡(jiǎn)單的,但是要深入到其內部去了解編譯流程,情況就比較復雜了。面對龐大的[3] gcc,我們只能選擇感興趣的部分來(lái)分析。但我們無(wú)法獲得關(guān)于 gcc 編譯流程的詳盡文檔[4] ,這主要是由于 gcc 本身過(guò)于繁雜,而且它處于不斷的變化當中,所以我們只有通過(guò)其它途徑來(lái)了解 gcc。有兩個(gè)比較好的方法:一是閱讀 source,對感興趣的函數可以跟蹤過(guò)去看一看,閱讀代碼看起來(lái)可怕,但其實(shí)代碼中會(huì )有很多注釋說(shuō)明它的功能,使得我們的閱讀變得更簡(jiǎn)單一些,這種方法便于從整體上把握 gcc;另外一個(gè)是 debug gcc,就是使用調試器來(lái)跟蹤 gcc 的編譯過(guò)程,這樣可以看清 gcc 編譯的實(shí)際流程,也可以追蹤我們感興趣的細節部分。我們先從大處著(zhù)眼,從 source 中看看 gcc 一些比較重要的函數以及它們之間的調用關(guān)系,然后在 hack gcc 的時(shí)候,對 gcc 進(jìn)行 debug 來(lái)追蹤我們關(guān)心的細節,并且可以通過(guò)調試來(lái)發(fā)現和修改 patch 中的錯誤。
[3] 完整的gcc 3.4.0解壓縮后占用超過(guò)150M的空間,有超過(guò)13000個(gè)代碼和文檔文件。
在開(kāi)始閱讀 gcc 的代碼之前,推薦您閱讀一下 GCC internals 中 passes and files of the compiler 一章——如果您以前沒(méi)有看過(guò)的話(huà),這段內容會(huì )幫助您對 gcc 的結構建立一個(gè)大概的映像。
好了,我們以 gcc 中的函數為單位,希望能夠盡量詳細地描述 gcc 中自頂向下的函數調用關(guān)系。在 gcc 源碼目錄中,很容易就發(fā)現了一個(gè)文件 main.c,應該是 gcc 的入口了,這個(gè)main.c 文件中只有一個(gè)函數 main,而這個(gè) main 函數中也只有一條語(yǔ)句,調用了一下toplev_main 函數。之所以單獨用一個(gè) main 函數來(lái)調用 toplev_main,是為了讓不同的語(yǔ)言前端可以方便設計不同的 main 函數。
[4] 作者認為關(guān)于GCC學(xué)習比較好的文檔列在本文后面的參考文檔一節中,但本文不準備重復這些文檔中已有的內容。
toplev_main 函數是在 toplev.c 文件中定義的,從名字中就可以看出這個(gè)文件應該是用來(lái)控制 gcc 最頂層的編譯流程的,在程序開(kāi)始的注釋中也說(shuō)明了它是用來(lái)處理命令行參數、打開(kāi)文件、以合適的順序調用各個(gè)分析程序[5] 并記錄它們各自所用的處理時(shí)間。toplev_main 首先對 gcc 做了一下初始化,主要是設置環(huán)境變量和診斷信息等等,然后就開(kāi)始解析命令行參數,我們對這些并不感興趣,重要的是接下來(lái)調用了 do_compile 函數,這個(gè)函數看從名字看就是做編譯工作的,而在此之后 toplev_main 函數就返回了。
do_compile 函數也是在 tolev.c 中定義的,它調用了一些函數來(lái)做進(jìn)一步的初始化,比如對編譯過(guò)程中計時(shí)器的初始化、針對特定程序設計語(yǔ)言的初始化以及對后端的初始化等等,同時(shí)它還對 toplev_main 函數中解析的命令行參數做了進(jìn)一步處理。在完成了上述工作后,調用了 compile_file() 函數,這個(gè)函數應該是用來(lái)進(jìn)行真正的編譯工作了。
[5] 分析程序對應的英文是pass,即多趟掃描編譯器中某一趟掃描所使用的掃描程序。
compile_file 函數還是在 toplev.c 中定義的,這里提一下 compile_file 函數和上面的do_compile 函數,它們是參數和返回類(lèi)型都為 void 的函數,在編譯的時(shí)候需要的各種參數包括編譯的文件名、編譯參數以及 gcc 內部使用的一些鉤子函數等等都是采用全局變量來(lái)表示的,當然,這些全局變量在前面各種初始化函數中都已經(jīng)被適當地初始化了。接著(zhù)說(shuō) compile_file 函數,它又做了一些我們并不太關(guān)心的初始化工作,之后,它終于調用了一個(gè)鉤子函數來(lái)分析(parse)整個(gè)輸入文件了:
(*lang_hooks.parse_file)(set_yydebug);
這里的 lang_hooks 是一個(gè)全局變量,不同語(yǔ)言的前端對此賦以不同的值,以便調用各自特有的分析程序,關(guān)于 lang_hooks 結構的定義和初始化等等可以參見(jiàn)源碼中的 langhooks.h、langhooks.c 和 langhooks-def.h 等文件,這里就不詳細追究了。對于 C 語(yǔ)言來(lái)說(shuō),這條語(yǔ)句相當于調用了 c-opts.c 中的 c_common_parse_file 函數。
c_common_parse_file中調用了c-parse.c中的c_parse_file函數,在此函數中又調用了同樣位于c-parse.c中的yyparse函數。有必要介紹一下c-parse.c文件,它是由GNU bison[6] 從c-parse.y中得到的一個(gè)語(yǔ)法解析器。c-parse.y則是一個(gè)YACC文件,它使用BNF(Backus Naur Form)來(lái)描述了某種程序設計語(yǔ)言的語(yǔ)法。[7]
[6] GNU bison是一個(gè)通用的生成語(yǔ)法解析器的工具,它可以把LALR(1)上下文無(wú)關(guān)的語(yǔ)法描述轉換成C語(yǔ)言程序,以便對使用該語(yǔ)法描述的語(yǔ)言進(jìn)行語(yǔ)法分析。
至此,我們對gcc中主要的函數調用關(guān)系還是相當清楚的,從main函數層層深入,進(jìn)入了c-parse.c中的yyparse函數。前面提到過(guò)c- parse.c文件是由GNU bison對c-parse.y這個(gè)YACC文件作用后自動(dòng)生成的,這導致這段代碼閱讀起來(lái)比較困難,因為bison生成的c-parse.c文件中有很多條goto語(yǔ)句以及超過(guò)500個(gè)case的switch語(yǔ)句,如此多的選擇和跳轉語(yǔ)句無(wú)疑給追蹤gcc的函數調用帶來(lái)了極大的困難,我們不可能再繼續下去了。
再回過(guò)頭去看看前面那些代碼和注釋以及一些文檔,注意到多次提到過(guò)一個(gè)函數――rest_of_compilation,這似乎是一個(gè)很重要的函數,我們可以過(guò)去看看。
[7] 關(guān)于詞法解析器和語(yǔ)法解析器的生成,本文不作詳細討論,有興趣的讀者可以參考GNU bison和flex的手冊。
在toplev.c 中我們找到了這個(gè)函數,注釋中說(shuō)明它的作用是:在對程序中頂層的函數定義或者變量的定義處理以后,接著(zhù)對這些函數或者變量進(jìn)行編譯并輸出相應的匯編代碼,在此函數返回后,gcc內部使用的tree結構就消亡了??磥?lái)這個(gè)函數的功能比較復雜,它已經(jīng)把源程序對應的匯編代碼生成了,并且把對應的tree結構占用的空間已經(jīng)釋放了,而我們所感興趣的部分是gcc編譯過(guò)程中內部使用RTL表示的情況,這部分處理應該是在rest_of_compilation這個(gè)函數返回之前做的。
前面我們從main函數跟蹤到了yyparse函數,這里又發(fā)現了一個(gè)很重要的rest_of_compilation函數,但中間這段過(guò)程gcc做了些什么我們還不清楚,也許我們所關(guān)心的有關(guān)RTL的處理就在其中。
現在我們只有對gcc進(jìn)行調試才能確切的看清進(jìn)入yyparse后函數調用的情況了,這里介紹一下調試gcc的方法:
對gcc進(jìn)行調試,其實(shí)是對編譯gcc源代碼所得到的cc1程序調試,進(jìn)入到cc1所在的目錄,運行命令:
$ gdb cc1 $ break main $ run -dr /PATH/test.c
這樣就是以-dr為編譯參數運行 gcc來(lái)編譯test.c文件了,并且在main函數的入口處設置了一個(gè)斷點(diǎn),-dr作為編譯參數就是要求在RTL表示生成以后將其dump到一個(gè)以. rtl結尾的文件中去。接下來(lái)在rest_of_compilation之前再設置一個(gè)斷點(diǎn),并用continue命令運行到該斷點(diǎn),用 backtrace命令查看此時(shí)函數棧幀的情況:
$ break rest_of_compilation $ continue $ backtrace
下表1給出了使用gdb調試時(shí)顯示出的從main到rest_of_compilation的函數調用情況:
調用順序 函數名字 所在文件名 #1
#2
#3
#4
#5
#6
#7
#8
#9
#10
#11
#12
#13
#14
#15
main
toplev_main
do_compile
compile_file
c_common_parse_file
c_parse_file
yyparse
finish_function
cgraph_finalize_function
cgraph_assemble_pending_functions
cgraph_expand_function
c_expand_body
c_expand_body_1
tree_rest_of_compilation
rest_of_compilation
main.c
toplev.c
toplev.c
toplev.c
c-opts.c
c-parse.y
c-parse.y
c-decl.c
cgraphunit.c
cgraphunit.c
cgraphunit.c
c-decl.c
c-decl.c
tree-optimize.c
toplev.c
表1. 部分函數調用棧幀列表
調試的結果證實(shí)我們前面的分析是正確的,從main函數到yyparse函數的調用順序與我們閱讀代碼時(shí)所分析得到的結果是吻合的?,F在我們得到了gcc編譯時(shí)從yypare到rest_of_compilation之間的一系列函數調用,這些都是值得關(guān)注的目標,讓我們返回到源碼中去看看這些函數的功能。
時(shí)刻記得我們的目標:對于gcc如何生成tree結構我們并不關(guān)心,也不關(guān)心gcc是如何由中間表示層RTL生成匯編代碼的,我們感興趣的是RTL表示是如何生成的,并希望在RTL表示層做一些修改,以達到我們的目的。為了省去一些篇幅,本文中略去了對那些我們不太關(guān)心的函數的分析,直接跳轉到RTL生成和處理相關(guān)的部分。
終于,在tree-optimize.c中的tree_rest_of_compilation中,我們發(fā)現了一系列看起來(lái)是與RTL生成有關(guān)的函數調用,特別引起我們注意的又是一個(gè)鉤子函數:
(*lang_hooks.rtl_expand.stmt) (DECL_SAVED_TREE (fndecl));
這行代碼的注釋說(shuō)這個(gè)鉤子函數用來(lái)生成一個(gè)被編譯函數的RTL表示,接下來(lái)還調用了幾個(gè)函數來(lái)進(jìn)行RTL生成階段的最后處理(包括調用gcc編譯時(shí)內部使用的垃圾收集函數),然后就調用了rest_of_compilation了。前面已經(jīng)提到了,rest_of_compilation的作用是對RTL表示做優(yōu)化并且生成匯編代碼輸出,至此我們可以做出這樣的推斷:在tree_rest_of_compilation調用了一系列生成RTL表示的函數之后,到調用rest_of_compilation之前,gcc的內部保存了一個(gè)原始的、未優(yōu)化的RTL中間表示。如果我們希望對函數的RTL表示做一些修改,在這里插入代碼做改動(dòng)應該是一個(gè)不錯的選擇。
到這里,我們所關(guān)心的gcc編譯流程基本已經(jīng)結束了,也搞清了RTL表示在什么地方生成的,我們應該有一定的信心在RTL表示層上對gcc進(jìn)行hack了。
我們的目標是在RTL表示層上hack gcc,所以有必要對RTL做一些介紹。在gcc internals中有專(zhuān)門(mén)的一章描述RTL,如果對RTL沒(méi)有任何了解,那么它很值得您一看;同時(shí),在理解和插入RTL語(yǔ)句的時(shí)候,這份文檔也可以作為比較詳盡的手冊來(lái)參照。
在gcc的編譯過(guò)程中,有三次比較重要的轉換:
待編譯的源代碼―<gcc抽象語(yǔ)法樹(shù)表示。
gcc抽象語(yǔ)法樹(shù)―<RTL表示。
RTL表示-<匯編代碼輸出。
RTL是gcc內部使用的中間表示語(yǔ)言,為了對其有一個(gè)直觀(guān)點(diǎn)的印象,我們可以把它dump出來(lái)看一看。使用
$ gcc -dr test.c
就可以得到test.c的RTL表示,文件名一般為test.c.00.rtl。
RTL 的設計據說(shuō)是從LISP語(yǔ)言得到了靈感,所以我們dump出來(lái)的.rtl文件看起來(lái)也像是一個(gè)LISP程序,每條RTL語(yǔ)句都是用來(lái)描述需要輸出的指令的,可以對照我們dump出的.rtl文件以及上面提到的文檔來(lái)深入學(xué)習RTL。但我們的要求不僅如此,我們需要插入自己的RTL語(yǔ)句來(lái)hack cc,必須閱讀gcc源代碼提供的RTL操作的接口,這個(gè)過(guò)程比較繁瑣而且沒(méi)有文檔可以參考,唯一有幫助的就是已有的在RTL表示層上對gcc做的補丁,以吸取其他gcc hackers的經(jīng)驗,作者在嘗試自己的補丁時(shí)曾經(jīng)參考過(guò)StackGuard[8] 的代碼,另外可以在gcc的maillist上看到有些hacker提供的patch,這些已有的工作對于gcc hacker newbie來(lái)說(shuō)是很有裨益的。
[8] StackGuard實(shí)際上是一個(gè)做過(guò)補丁的GCC,可以做為在RTL表示層上hack GCC的一個(gè)例子來(lái)參考。
僅僅這么多文字來(lái)介紹RTL還遠遠不夠,但是如果希望把RTL描述得十分清楚,那應該由另外一篇文章來(lái)完成了,本文就不再詳述了。
下面進(jìn)入hack gcc的實(shí)戰階段了,先說(shuō)一下我的目的:我希望使用修改過(guò)的gcc編譯程序的時(shí)候,能夠在每個(gè)函數的開(kāi)始和結束的地方插入一個(gè)函數調用語(yǔ)句,也就是說(shuō),在每個(gè)函數的第一條指令之前,由編譯器強制插入一個(gè)函數調用,在函數最后一條指令結束之后,也要插入一個(gè)函數調用。下面用兩段C語(yǔ)言代碼來(lái)表達這個(gè)補丁的效果:
int foo()
{
first statement;
last statement;
}
int foo()
{
my_function_begin;
first statement;
last statement;
my_function_end;
}
左邊一列是程序員正常編寫(xiě)的普通函數,我希望使用修改過(guò)的gcc編譯該函數后,能夠得到相當于編譯右邊這段函數的結果,就是對程序員透明地在每個(gè)函數的第一條語(yǔ)句之前和最后一條語(yǔ)句之后自動(dòng)插入兩個(gè)函數調用:my_function_begin和my_function_end。當然,這兩個(gè)函數具體實(shí)現什么功能可以由程序員來(lái)編寫(xiě),最簡(jiǎn)單的實(shí)現可以?xún)H僅在標準輸出上分別打印一句話(huà)表示該函數確實(shí)被調用了即可。
gcc 中生成抽象語(yǔ)法樹(shù)表示和RTL表示都是以一個(gè)完整的函數定義或者top level的聲明為單位的,這也就意味著(zhù)在tree_rest_of_compilation這個(gè)函數調用了一系列用于生成RTL表示的函數之后,我們所得到的只是當前正在被編譯的函數的RTL表示,而并不是整個(gè)源程序的RTL表示,這正好方便我們以函數為單位來(lái)進(jìn)行修改。
我們在tree_rest_of_compilation函數中調用rest_of_compilation之前插入一條語(yǔ)句,調用一個(gè)新函數 modify_rtl來(lái)對gcc生成的RTL表示做一些處理。函數modify_rtl的定義放在function.c文件中,這是因為gcc在生成 RTL表示時(shí)需要的相關(guān)函數大部分都定義在這個(gè)文件中,我們的補丁也可以看作是gcc生成RTL表示的一部分工作,所以把modify_rtl放到這個(gè)文件中定義是最合適的。
接下來(lái)工作的關(guān)鍵就集中到如何定義modify_rtl函數了?,F在我們得到了當前編譯函數的RTL表示,我們可以對這個(gè)RTL單元進(jìn)行掃描,找到合適的位置分別調用my_function_begin和my_function_end函數即可。函數的RTL表示是一個(gè)雙向連接的鏈表結構,其中每個(gè)節點(diǎn)稱(chēng)為一個(gè)insn[9] ,有的insn可能表示一條真實(shí)的匯編指令,有的則表示jump指令跳轉的標簽或者其它各種聲明信息。為了簡(jiǎn)便起見(jiàn),這里直接給出一個(gè)常用的gcc所提供的訪(fǎng)問(wèn)insn的宏和函數列表,并給出它們的功能:
[9] StackGuard實(shí)際上是一個(gè)做過(guò)補丁的GCC,可以做為在RTL表示層上hack GCC的一個(gè)例子來(lái)參考。
宏(函數)名 功能 INSN_UID(insn) 獲取該insn的id PREV_INSN(insn) 獲取insn鏈表中該insn的前一個(gè)insn NEXT_INSN(insn) 獲取insn鏈表中該insn的后一個(gè)insn GET_CODE(insn) 獲取該insn的code NOTE_LINE_NUMBER(insn) 如果insn的code是NOTE,則返回該insn對應源代碼的行號,否則返回一個(gè)負數 Get_insns() 獲取當前函數RTL表示的第一個(gè)insn Get_last_insn() 返回當前函數RTL表示的最后一個(gè)insn
表2. 部分gcc提供的insn操作接口列表
一個(gè)函數完整的、未被優(yōu)化的RTL表示中會(huì )有兩個(gè)note insn表示函數的開(kāi)始和結束,gcc定義了兩個(gè)全局變量NOTE_INSN_FUNCTION_BEGIN和 NOTE_INSN_FUNCTION_END來(lái)表示這兩個(gè)note insn的行數。這樣我們就可以?huà)呙璁斍癛TL單元,當碰到這兩個(gè)note insn的時(shí)候,就可以插入相應的函數調用語(yǔ)句了。
gcc提供了emit_library_call函數來(lái)插入一個(gè)函數調用,這個(gè)函數返回的是一個(gè)表示函數調用的RTL表達式,并默認地把這個(gè)RTL表達式插入到當前RTL單元的最后一個(gè)insn之后。所以如果直接調用emit_library_call,就會(huì )把函數調用語(yǔ)句插入到RTL單元最后一個(gè)insn之后,而不是我們所希望的函數開(kāi)始和結束的地方,我們可以使用start_sequence和end_sequence函數,它們產(chǎn)生一個(gè)相對獨立的sequence并把函數調用語(yǔ)句保存到一個(gè)RTL表達式中以備后用。
我們已經(jīng)找到插入函數調用的點(diǎn),并且也生成了表示函數調用的RTL語(yǔ)句,現在就可以使用gcc提供的emit_insn_before和emit_insn_after函數來(lái)插入RTL語(yǔ)句了。
到這里,modify_rtl函數的實(shí)現基本已經(jīng)成型了,下面這段示例代碼就可以完成在每個(gè)函數的開(kāi)始處插入RTL語(yǔ)句的功能:
int modify_rtl() { rtx insn; rtx seq; //emit my_function_begin at the beginnig of each function start_sequence(); emit_libarary_call(gen_rtx(SYMBOL_REF, Pmode, my_function_begin), 0, VOIDmode, 0); seq = get_insns(); end_sequence(); for(insn = get_insns(); ; insn = NEXT_INSN(insn)) if((GET_CODE(insn) == NOTE) && (NOTE_LINE_NUMBER(insn) == NOTE_INSN_FUNCTION_BEGIN)) break; emit_insn_after(seq, insn); … }
這段代碼中所使用數據結構、函數的具體功能和用法,屬于十分細節的內容,無(wú)須在這里描述清楚,請讀者參考gcc源代碼。
對于在函數結束的地方插入my_function_end函數同樣如此,我們可以用get_last_insn得到RTL單元的最后一個(gè)insn,然后使用 PREV_INSN(insn)開(kāi)始向前掃描,遇到行號為NOTE_INSN_FUNCTION_END的note insn時(shí),用emit_insn_before把相應的函數調用RTL表達式插入到這個(gè)insn之前即可。
現在這個(gè)patch的基本功能已經(jīng)完成了,我們還可以再做一些工作使得它功能更強大和實(shí)用一些,比如加入一個(gè)編譯選項(比如-finsert- function)來(lái)指定是否啟用這個(gè)patch的,當編譯的命令行參數中沒(méi)有提供這個(gè)編譯選項時(shí),我們所作的補丁就不起作用。關(guān)于如何增加編譯選項,我們可以參考opts.c中的decode-options函數,在此就不詳細分析了。
在modify_rtl中調用current_function_name函數可以得到當前正在被編譯的函數名,我們可以把這些函數名寫(xiě)到一個(gè)文件中去,這樣可以記錄我們對哪些函數做了修改;還可以實(shí)現一個(gè)過(guò)濾器,在啟用了patch的情況下,對于指定的函數,我們還可以將其過(guò)濾掉,不對其做處理,這些功能也是很容易實(shí)現的。
我們還可以再實(shí)現一些功能,比如在掃描RTL的時(shí)候,如果發(fā)現一條call_insn,可以把這條call指令所調用的函數名記錄下來(lái),這樣我們甚至可以得到一個(gè)程序運行時(shí)刻的動(dòng)態(tài)的函數調用關(guān)系圖,這就可以描繪程序的實(shí)際運行軌跡。
最后,還需要把my_function_begin和my_function_end兩個(gè)函數實(shí)現一下,可以把它們的功能擴展一下,不是僅僅輸出一條語(yǔ)句到標準輸出,而是記錄一些信息到文件中,這樣就可以得到一個(gè)以函數為粒度的運行時(shí)刻日志,甚至可以使這兩個(gè)函數與linux內核聯(lián)系起來(lái),做一些特殊的檢查工作等等,這樣就使得我們的patch有一些實(shí)用性了。這兩個(gè)函數我們可以在mylib.c中實(shí)現,編譯成一個(gè)shared object,使用如下命令編譯:
$ gcc mylib.c -c -fPIC $ gcc mylib.o -shared -o libmylib.so
把libmylib.so放到/usr/lib目錄下,那么在編譯的時(shí)候只需加上-lmylib參數就可以使用這個(gè)shared object中的函數了。
剩下的工作就是進(jìn)行調試和測試了,當我們解決了各種問(wèn)題,使這個(gè)修改過(guò)的編譯器能夠完美的運行起來(lái)的時(shí)候,也許我們就能體會(huì )到gcc hacker的那種成就感和喜悅之情了。
先說(shuō)一下我自己嘗試的結果,我是基于gcc version 3.4.0工作的,給gcc加入了一個(gè)編譯選項以選擇是否啟用添加的補丁,可以在每個(gè)函數的開(kāi)始和結束的時(shí)候插入函數調用,也可以在函數調用之前和返回之后插入函數調用,實(shí)現了一個(gè)過(guò)濾器,可以忽略一些函數不對其做處理,并且可以在運行時(shí)將一些信息記錄到文件中去留待分析。這個(gè)補丁的功能基本上就是這些了,實(shí)現方法可能和本文中的方法有所不同,文中描述的方法是較早的時(shí)候我采用的方法,現在則進(jìn)行了一些改動(dòng),這里就不詳加介紹了。我已經(jīng)成功的使用“我的”gcc編譯了emacs和lynx等實(shí)用軟件,運行正常,補丁功能也正常,可以說(shuō)是取得了一個(gè)小小的成功。但是我沒(méi)有空間可以上載我的補丁,有興趣的讀者可以通過(guò)e-mail向我索取。
最后談?wù)勎业慕?jīng)驗:
在理解gcc的編譯流程以及試圖找到做補丁的思路的時(shí)候,需要多閱讀文檔,包括學(xué)習已有的工作是怎么做的。不要貿然嘗試,不要奢望可以憑運氣達成目的,盡量找到最合適的實(shí)現方法,在確立了一個(gè)基本思路之后,可以在gcc的maillist上咨詢(xún)一下,看看有沒(méi)有人提供更好的思路,在確信自己思路的可行性之后再開(kāi)始具體的工作。
在做具體實(shí)現的時(shí)候,肯定會(huì )遇到各種各樣的問(wèn)題,比如在編譯自己修改過(guò)的gcc時(shí)會(huì )出錯,或者用patch過(guò)的gcc編譯程序時(shí)出錯,或者是編譯通過(guò)運行時(shí)刻出錯等等,這時(shí)候需要耐心地檢查代碼和進(jìn)行debug,盡量自己解決問(wèn)題,不要把一些特別細節地問(wèn)題拿到maillist上討論。我記得在maillist上曾經(jīng)有人嚴厲地告誡我:“you won‘t go very far if you ask a question each time you get an error”,自己debug才是解決問(wèn)題的最好方法,當然如果實(shí)在不明白的問(wèn)題必須拿到maillist上去討論,這時(shí)候要盡量詳細的描述自己的目的和問(wèn)題,才能夠得到有效的幫助。
好了,這就是我自己學(xué)習和嘗試hack gcc的工作過(guò)程,希望我的一些經(jīng)驗能夠給您幫助,如果對本文中的觀(guān)點(diǎn)有疑問(wèn)或者在學(xué)習gcc的時(shí)候碰到困難,歡迎與我探討。
GCC internals.http://gcc.gnu.org/onlinedocs/gccint/,這是得到公認的除了gcc源碼以外,學(xué)習gcc最好的材料,雖然比較難讀,但它的權威性和全面性是毋庸置疑的,GCC的手冊和info也是很好的參考資料。
GCC Front end HOWTO, 通過(guò)自己設計一個(gè)GCC前端來(lái)幫助GCC hacker newbie來(lái)學(xué)習和了解GCC,http://www.icewalkers.com/Linux/Howto/GCC-Frontend-HOWTO.html#toc1,適合新手閱讀,作者還提供了相關(guān)程序的軟件包。
StackGuard,http://www.immunix.org/stackguard.html,可以作為一個(gè)在RTL表示層hack gcc的實(shí)例來(lái)學(xué)習。
關(guān)于作者
王逸,2002 年秋季入學(xué)的南京大學(xué)計算機科學(xué)與技術(shù)系碩士研究生,在分布式系統國家重點(diǎn)實(shí)驗室學(xué)習。對于軟件安全、linux系統和無(wú)線(xiàn)網(wǎng)絡(luò )有興趣,目前工作主要集中在緩沖區溢出攻擊的防范上。您可以通過(guò)cnnjuwy@hotmail.com與我聯(lián)系,也可以在南京大學(xué)小百合bbs上(http://bbs.nju.edu.cn或者http://lilybbs.net)與LinuxLover賬號聯(lián)系。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
一圖總結gcc編譯&函數庫編譯、加載過(guò)程!
Linux下如何編譯并運行C程序
基于GCC開(kāi)發(fā)C編譯器的研究與實(shí)踐
HotSpot(熱點(diǎn)最新的一種的java虛擬機)詳解
gcc編譯操作
【第1206期】看清楚真正的 Webpack 插件
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久