gcc編譯流程及中間表示層RTL的探索

內容摘要 本文將以 C 語言爲例,介紹 gcc 在接受一個 .c 文件的輸入之後,其前端是如何進行處理並得到一箇中間表示並轉交給後端處理。然後,在瞭解了 gcc[1] 的工作流程後,介紹一下作者嘗試在 gcc 內部的 RTL 表示層中 hack gcc 的過程,與大家分享一些經驗,希望能給對有興趣研究和開發 gcc 的讀者有所幫助。

1. GCC 簡介
編譯器的工作是將源代碼(通常使用高級語言編寫)翻譯成目標代碼(通常是低級的目標代碼或者機器語言),在現代編譯器的實現中,這個工作一般是分爲兩個階段來實現的:

第一階段,編譯器的前端接受輸入的源代碼,經過詞法、語法和語義分析等等得到源程序的某種中間表示方式。

第二階段,編譯器的後端將前端處理生成的中間表示方式進行一些優化,並最終生成在目標機器上可運行的代碼。

GCC(GNU Compiler Collection) 是在 UNIX 以及類 UNIX 平臺上廣泛使用的編譯器集合,它能夠支持多種語言前端,包括 C, C++, Objective-C, Ada, Fortran, Java 和 treelang 等。

GCC 設計中有兩個重要的目標,其中一個是在構建支持不同硬件平臺的編譯器時,它的代碼能夠最大程度的被複用,所以 GCC 必須要做到一定程度的硬件無關性;另一個是要生成高質量的可執行代碼,這就需要對代碼進行集中的優化。爲了實現這兩個目標,GCC 內部使用了一種硬件平臺無關的語言,它能對實際的體系結構做一種抽象,這個中間語言就是 RTL(Register Transfer Language)。

雖然關於 GCC 的研究和開發工作側重於 GCC 後端代碼優化方面,但本文中我們關注的目標是在 GCC 的編譯過程中前端是如何工作的。

把 GCC 的前端獨立出來研究目的在於,在設計新的編譯器的時候,我們僅僅需要關注如何設計新編譯器的前端,而將代碼優化和目標代碼的生成留給 GCC 後端去完成,避免了後端設計的重複性勞動。

本文將以 C 語言爲例,介紹 gcc[2] 在接受一個 .c 文件的輸入之後,其前端是如何進行處理並得到一箇中間表示並轉交給後端處理。然後,在瞭解了 gcc 的工作流程後,介紹一下作者嘗試在 gcc 內部的RTL表示層中 hack gcc 的過程,與大家分享一些經驗,希望能給對有興趣研究和開發 gcc 的讀者有所幫助。


[1] 如無特別說明,下文中 gcc 均指GNU C Compiler,即GCC編譯器集合中的C語言編譯器。

2. gcc 的工作流程
gcc 是一個驅動程序,它接受並解釋命令行參數,根據對命令行參數分析的結果決定下一步動作,gcc 提供了多種選項以達到控制 gcc 編譯過程的目的,我們可以在 GCC 的手冊中查找這些編譯選項的詳細信息。

gcc 的使用是比較簡單的,但是要深入到其內部去了解編譯流程,情況就比較複雜了。面對龐大的[3] gcc,我們只能選擇感興趣的部分來分析。但我們無法獲得關於 gcc 編譯流程的詳盡文檔[4] ,這主要是由於 gcc 本身過於繁雜,而且它處於不斷的變化當中,所以我們只有通過其它途徑來了解 gcc。有兩個比較好的方法:一是閱讀 source,對感興趣的函數可以跟蹤過去看一看,閱讀代碼看起來可怕,但其實代碼中會有很多註釋說明它的功能,使得我們的閱讀變得更簡單一些,這種方法 便於從整體上把握 gcc;另外一個是 debug gcc,就是使用調試器來跟蹤 gcc 的編譯過程,這樣可以看清 gcc 編譯的實際流程,也可以追蹤我們感興趣的細節部分。我們先從大處着眼,從 source 中看看 gcc 一些比較重要的函數以及它們之間的調用關係,然後在 hack gcc 的時候,對 gcc 進行 debug 來追蹤我們關心的細節,並且可以通過調試來發現和修改 patch 中的錯誤。


[3] 完整的gcc 3.4.0解壓縮後佔用超過150M的空間,有超過13000個代碼和文檔文件。

在開始閱讀 gcc 的代碼之前,推薦您閱讀一下 GCC internals 中 passes and files of the compiler 一章——如果您以前沒有看過的話,這段內容會幫助您對 gcc 的結構建立一個大概的映像。

好了,我們以 gcc 中的函數爲單位,希望能夠儘量詳細地描述 gcc 中自頂向下的函數調用關係。在 gcc 源碼目錄中,很容易就發現了一個文件 main.c,應該是 gcc 的入口了,這個main.c 文件中只有一個函數 main,而這個 main 函數中也只有一條語句,調用了一下toplev_main 函數。之所以單獨用一個 main 函數來調用 toplev_main,是爲了讓不同的語言前端可以方便設計不同的 main 函數。


[4] 作者認爲關於GCC學習比較好的文檔列在本文後面的參考文檔一節中,但本文不準備重複這些文檔中已有的內容。

toplev_main 函數是在 toplev.c 文件中定義的,從名字中就可以看出這個文件應該是用來控制 gcc 最頂層的編譯流程的,在程序開始的註釋中也說明了它是用來處理命令行參數、打開文件、以合適的順序調用各個分析程序 [5] 並記錄它們各自所用的處理時間。toplev_main 首先對 gcc 做了一下初始化,主要是設置環境變量和診斷信息等等,然後就開始解析命令行參數,我們對這些並不感興趣,重要的是接下來調用了 do_compile 函數,這個函數看從名字看就是做編譯工作的,而在此之後 toplev_main 函數就返回了。

do_compile 函數也是在 tolev.c 中定義的,它調用了一些函數來做進一步的初始化,比如對編譯過程中計時器的初始化、針對特定程序設計語言的初始化以及對後端的初始化等等,同時它還對 toplev_main 函數中解析的命令行參數做了進一步處理。在完成了上述工作後,調用了 compile_file() 函數,這個函數應該是用來進行真正的編譯工作了。


[5] 分析程序對應的英文是pass,即多趟掃描編譯器中某一趟掃描所使用的掃描程序。

compile_file 函數還是在 toplev.c 中定義的,這裏提一下 compile_file 函數和上面的do_compile 函數,它們是參數和返回類型都爲 void 的函數,在編譯的時候需要的各種參數包括編譯的文件名、編譯參數以及 gcc 內部使用的一些鉤子函數等等都是採用全局變量來表示的,當然,這些全局變量在前面各種初始化函數中都已經被適當地初始化了。接着說 compile_file 函數,它又做了一些我們並不太關心的初始化工作,之後,它終於調用了一個鉤子函數來分析(parse)整個輸入文件了:


(*lang_hooks.parse_file)(set_yydebug);

這裏的 lang_hooks 是一個全局變量,不同語言的前端對此賦以不同的值,以便調用各自特有的分析程序,關於 lang_hooks 結構的定義和初始化等等可以參見源碼中的 langhooks.h、langhooks.c 和 langhooks-def.h 等文件,這裏就不詳細追究了。對於 C 語言來說,這條語句相當於調用了 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中得到的一個語法解析器。c-parse.y則是一個YACC文件,它使用BNF(Backus Naur Form)來描述了某種程序設計語言的語法。 [7]


[6] GNU bison是一個通用的生成語法解析器的工具,它可以把LALR(1)上下文無關的語法描述轉換成C語言程序,以便對使用該語法描述的語言進行語法分析。

至此,我們對gcc中主要的函數調用關係還是相當清楚的,從main函數層層深入,進入了c-parse.c中的yyparse函數。前面提到過c -parse.c文件是由GNU bison對c-parse.y這個YACC文件作用後自動生成的,這導致這段代碼閱讀起來比較困難,因爲bison生成的c-parse.c文件中有很 多條goto語句以及超過500個case的switch語句,如此多的選擇和跳轉語句無疑給追蹤gcc的函數調用帶來了極大的困難,我們不可能再繼續下 去了。

再回過頭去看看前面那些代碼和註釋以及一些文檔,注意到多次提到過一個函數――rest_of_compilation,這似乎是一個很重要的函數,我們可以過去看看。


[7] 關於詞法解析器和語法解析器的生成,本文不作詳細討論,有興趣的讀者可以參考GNU bison和flex的手冊。

在toplev.c中我們找到了這個函數,註釋中說明它的作用是:在對程序中頂層的函數定義或者變量的定義處理以後,接着對這些函數或者變量進行編 譯並輸出相應的彙編代碼,在此函數返回後,gcc內部使用的tree結構就消亡了。看來這個函數的功能比較複雜,它已經把源程序對應的彙編代碼生成了,並 且把對應的tree結構佔用的空間已經釋放了,而我們所感興趣的部分是gcc編譯過程中內部使用RTL表示的情況,這部分處理應該是在 rest_of_compilation這個函數返回之前做的。

前面我們從main函數跟蹤到了yyparse函數,這裏又發現了一個很重要的rest_of_compilation函數,但中間這段過程gcc做了些什麼我們還不清楚,也許我們所關心的有關RTL的處理就在其中。

現在我們只有對gcc進行調試才能確切的看清進入yyparse後函數調用的情況了,這裏介紹一下調試gcc的方法:

對gcc進行調試,其實是對編譯gcc源代碼所得到的cc1程序調試,進入到cc1所在的目錄,運行命令:


$ gdb cc1
$ break main
$ run -dr /PATH/test.c

這樣就是以-dr爲編譯參數運行gcc來編譯test.c文件了,並且在main函數的入口處設置了一個斷點,-dr作爲編譯參數就是要求在RTL 表示生成以後將其dump到一個以.rtl結尾的文件中去。接下來在rest_of_compilation之前再設置一個斷點,並用continue命 令運行到該斷點,用backtrace命令查看此時函數棧幀的情況:


$ break rest_of_compilation
$ continue
$ backtrace

下表1給出了使用gdb調試時顯示出的從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. 部分函數調用棧幀列表

調試的結果證實我們前面的分析是正確的,從main函數到yyparse函數的調用順序與我們閱讀代碼時所分析得到的結果是吻合的。現在我們得到了 gcc編譯時從yypare到rest_of_compilation之間的一系列函數調用,這些都是值得關注的目標,讓我們返回到源碼中去看看這些函數 的功能。

時刻記得我們的目標:對於gcc如何生成tree結構我們並不關心,也不關心gcc是如何由中間表示層RTL生成彙編代碼的,我們感興趣的是RTL 表示是如何生成的,並希望在RTL表示層做一些修改,以達到我們的目的。爲了省去一些篇幅,本文中略去了對那些我們不太關心的函數的分析,直接跳轉到 RTL生成和處理相關的部分。

終於,在tree-optimize.c中的tree_rest_of_compilation中,我們發現了一系列看起來是與RTL生成有關的函數調用,特別引起我們注意的又是一個鉤子函數:

(*lang_hooks.rtl_expand.stmt) (DECL_SAVED_TREE (fndecl));

這行代碼的註釋說這個鉤子函數用來生成一個被編譯函數的RTL表示,接下來還調用了幾個函數來進行RTL生成階段的最後處理(包括調用gcc編譯時 內部使用的垃圾收集函數),然後就調用了rest_of_compilation了。前面已經提到了,rest_of_compilation的作用是對 RTL表示做優化並且生成彙編代碼輸出,至此我們可以做出這樣的推斷:在tree_rest_of_compilation調用了一系列生成RTL表示的 函數之後,到調用rest_of_compilation之前,gcc的內部保存了一個原始的、未優化的RTL中間表示。如果我們希望對函數的RTL表示 做一些修改,在這裏插入代碼做改動應該是一個不錯的選擇。

到這裏,我們所關心的gcc編譯流程基本已經結束了,也搞清了RTL表示在什麼地方生成的,我們應該有一定的信心在RTL表示層上對gcc進行hack了。

3. RTL簡介
我們的目標是在RTL表示層上hack gcc,所以有必要對RTL做一些介紹。在gcc internals中有專門的一章描述RTL,如果對RTL沒有任何瞭解,那麼它很值得您一看;同時,在理解和插入RTL語句的時候,這份文檔也可以作爲比較詳盡的手冊來參照。

 

在gcc的編譯過程中,有三次比較重要的轉換:

  1. 待編譯的源代碼―<gcc抽象語法樹表示。
  2. gcc抽象語法樹―<RTL表示。
  3. RTL表示-<彙編代碼輸出。

RTL是gcc內部使用的中間表示語言,爲了對其有一個直觀點的印象,我們可以把它dump出來看一看。使用

$ gcc -dr test.c

就可以得到test.c的RTL表示,文件名一般爲test.c.00.rtl。

RTL的設計據說是從LISP語言得到了靈感,所以我們dump出來的.rtl文件看起來也像是一個LISP程序,每條RTL語句都是用來描述需要 輸出的指令的,可以對照我們dump出的.rtl文件以及上面提到的文檔來深入學習RTL。但我們的要求不僅如此,我們需要插入自己的RTL語句來 hack cc,必須閱讀gcc源代碼提供的RTL操作的接口,這個過程比較繁瑣而且沒有文檔可以參考,唯一有幫助的就是已有的在RTL表示層上對gcc做的補丁, 以吸取其他gcc hackers的經驗,作者在嘗試自己的補丁時曾經參考過StackGuard [8] 的代碼,另外可以在gcc的maillist上看到有些hacker提供的patch,這些已有的工作對於gcc hacker newbie來說是很有裨益的。


[8] StackGuard實際上是一個做過補丁的GCC,可以做爲在RTL表示層上hack GCC的一個例子來參考。

僅僅這麼多文字來介紹RTL還遠遠不夠,但是如果希望把RTL描述得十分清楚,那應該由另外一篇文章來完成了,本文就不再詳述了。

4. Let's hack gcc!
下 面進入hack gcc的實戰階段了,先說一下我的目的:我希望使用修改過的gcc編譯程序的時候,能夠在每個函數的開始和結束的地方插入一個函數調用語句,也就是說,在 每個函數的第一條指令之前,由編譯器強制插入一個函數調用,在函數最後一條指令結束之後,也要插入一個函數調用。下面用兩段C語言代碼來表達這個補丁的效 果:

int foo()
{
    first statement;
    …
    …
    …
    last statement;
}
int foo()
{
    my_function_begin;
    first statement;
    …
    last statement;
    my_function_end;
}

左邊一列是程序員正常編寫的普通函數,我希望使用修改過的gcc編譯該函數後,能夠得到相當於編譯右邊這段函數的結果,就是對程序員透明地在每個函 數的第一條語句之前和最後一條語句之後自動插入兩個函數調用:my_function_begin和my_function_end。當然,這兩個函數具 體實現什麼功能可以由程序員來編寫,最簡單的實現可以僅僅在標準輸出上分別打印一句話表示該函數確實被調用了即可。

gcc中生成抽象語法樹表示和RTL表示都是以一個完整的函數定義或者top level的聲明爲單位的,這也就意味着在tree_rest_of_compilation這個函數調用了一系列用於生成RTL表示的函數之後,我們所 得到的只是當前正在被編譯的函數的RTL表示,而並不是整個源程序的RTL表示,這正好方便我們以函數爲單位來進行修改。

我們在tree_rest_of_compilation函數中調用rest_of_compilation之前插入一條語句,調用一個新函數 modify_rtl來對gcc生成的RTL表示做一些處理。函數modify_rtl的定義放在function.c文件中,這是因爲gcc在生成 RTL表示時需要的相關函數大部分都定義在這個文件中,我們的補丁也可以看作是gcc生成RTL表示的一部分工作,所以把modify_rtl放到這個文 件中定義是最合適的。

接下來工作的關鍵就集中到如何定義modify_rtl函數了。現在我們得到了當前編譯函數的RTL表示,我們可以對這個RTL單元進行掃描,找到 合適的位置分別調用my_function_begin和my_function_end函數即可。函數的RTL表示是一個雙向連接的鏈表結構,其中每個 節點稱爲一個insn [9] ,有的insn可能表示一條真實的彙編指令,有的則表示jump指令跳轉的標籤或者其它各種聲明信息。爲了簡便起見,這裏直接給出一個常用的gcc所提供的訪問insn的宏和函數列表,並給出它們的功能:


[9] StackGuard實際上是一個做過補丁的GCC,可以做爲在RTL表示層上hack GCC的一個例子來參考。

宏(函數)名 功能
INSN_UID(insn) 獲取該insn的id
PREV_INSN(insn) 獲取insn鏈表中該insn的前一個insn
NEXT_INSN(insn) 獲取insn鏈表中該insn的後一個insn
GET_CODE(insn) 獲取該insn的code
NOTE_LINE_NUMBER(insn) 如果insn的code是NOTE,則返回該insn對應源代碼的行號,否則返回一個負數
Get_insns() 獲取當前函數RTL表示的第一個insn
Get_last_insn() 返回當前函數RTL表示的最後一個insn

表2. 部分gcc提供的insn操作接口列表

一個函數完整的、未被優化的RTL表示中會有兩個note insn表示函數的開始和結束,gcc定義了兩個全局變量NOTE_INSN_FUNCTION_BEGIN和 NOTE_INSN_FUNCTION_END來表示這兩個note insn的行數。這樣我們就可以掃描當前RTL單元,當碰到這兩個note insn的時候,就可以插入相應的函數調用語句了。

gcc提供了emit_library_call函數來插入一個函數調用,這個函數返回的是一個表示函數調用的RTL表達式,並默認地把這個RTL 表達式插入到當前RTL單元的最後一個insn之後。所以如果直接調用emit_library_call,就會把函數調用語句插入到RTL單元最後一個 insn之後,而不是我們所希望的函數開始和結束的地方,我們可以使用start_sequence和end_sequence函數,它們產生一個相對獨 立的sequence並把函數調用語句保存到一個RTL表達式中以備後用。

我們已經找到插入函數調用的點,並且也生成了表示函數調用的RTL語句,現在就可以使用gcc提供的emit_insn_before和emit_insn_after函數來插入RTL語句了。

到這裏,modify_rtl函數的實現基本已經成型了,下面這段示例代碼就可以完成在每個函數的開始處插入RTL語句的功能:


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);


}

這段代碼中所使用數據結構、函數的具體功能和用法,屬於十分細節的內容,無須在這裏描述清楚,請讀者參考gcc源代碼。

對於在函數結束的地方插入my_function_end函數同樣如此,我們可以用get_last_insn得到RTL單元的最後一個insn, 然後使用PREV_INSN(insn)開始向前掃描,遇到行號爲NOTE_INSN_FUNCTION_END的note insn時,用emit_insn_before把相應的函數調用RTL表達式插入到這個insn之前即可。

現在這個patch的基本功能已經完成了,我們還可以再做一些工作使得它功能更強大和實用一些,比如加入一個編譯選項(比如-finsert- function)來指定是否啓用這個patch的,當編譯的命令行參數中沒有提供這個編譯選項時,我們所作的補丁就不起作用。關於如何增加編譯選項,我 們可以參考opts.c中的decode-options函數,在此就不詳細分析了。

在modify_rtl中調用current_function_name函數可以得到當前正在被編譯的函數名,我們可以把這些函數名寫到一個文件 中去,這樣可以記錄我們對哪些函數做了修改;還可以實現一個過濾器,在啓用了patch的情況下,對於指定的函數,我們還可以將其過濾掉,不對其做處理, 這些功能也是很容易實現的。

我們還可以再實現一些功能,比如在掃描RTL的時候,如果發現一條call_insn,可以把這條call指令所調用的函數名記錄下來,這樣我們甚至可以得到一個程序運行時刻的動態的函數調用關係圖,這就可以描繪程序的實際運行軌跡。

最後,還需要把my_function_begin和my_function_end兩個函數實現一下,可以把它們的功能擴展一下,不是僅僅輸出一 條語句到標準輸出,而是記錄一些信息到文件中,這樣就可以得到一個以函數爲粒度的運行時刻日誌,甚至可以使這兩個函數與linux內核聯繫起來,做一些特 殊的檢查工作等等,這樣就使得我們的patch有一些實用性了。這兩個函數我們可以在mylib.c中實現,編譯成一個shared object,使用如下命令編譯:


$ gcc mylib.c -c -fPIC
$ gcc mylib.o -shared -o libmylib.so

把libmylib.so放到/usr/lib目錄下,那麼在編譯的時候只需加上-lmylib參數就可以使用這個shared object中的函數了。

剩下的工作就是進行調試和測試了,當我們解決了各種問題,使這個修改過的編譯器能夠完美的運行起來的時候,也許我們就能體會到gcc hacker的那種成就感和喜悅之情了。

5. 經驗總結
先說一下我自 己嘗試的結果,我是基於gcc version 3.4.0工作的,給gcc加入了一個編譯選項以選擇是否啓用添加的補丁,可以在每個函數的開始和結束的時候插入函數調用,也可以在函數調用之前和返回之 後插入函數調用,實現了一個過濾器,可以忽略一些函數不對其做處理,並且可以在運行時將一些信息記錄到文件中去留待分析。這個補丁的功能基本上就是這些 了,實現方法可能和本文中的方法有所不同,文中描述的方法是較早的時候我採用的方法,現在則進行了一些改動,這裏就不詳加介紹了。我已經成功的使用“我 的”gcc編譯了emacs和lynx等實用軟件,運行正常,補丁功能也正常,可以說是取得了一個小小的成功。但是我沒有空間可以上載我的補丁,有興趣的 讀者可以通過e-mail向我索取。

最後談談我的經驗:

在理解gcc的編譯流程以及試圖找到做補丁的思路的時候,需要多閱讀文檔,包括學習已有的工作是怎麼做的。不要貿然嘗試,不要奢望可以憑運氣達成目 的,儘量找到最合適的實現方法,在確立了一個基本思路之後,可以在gcc的maillist上諮詢一下,看看有沒有人提供更好的思路,在確信自己思路的可 行性之後再開始具體的工作。

在做具體實現的時候,肯定會遇到各種各樣的問題,比如在編譯自己修改過的gcc時會出錯,或者用patch過的gcc編譯程序時出錯,或者是編譯通 過運行時刻出錯等等,這時候需要耐心地檢查代碼和進行debug,儘量自己解決問題,不要把一些特別細節地問題拿到maillist上討論。我記得在 maillist上曾經有人嚴厲地告誡我:“you won't go very far if you ask a question each time you get an error”,自己debug纔是解決問題的最好方法,當然如果實在不明白的問題必須拿到maillist上去討論,這時候要儘量詳細的描述自己的目的和 問題,才能夠得到有效的幫助。

好了,這就是我自己學習和嘗試hack gcc的工作過程,希望我的一些經驗能夠給您幫助,如果對本文中的觀點有疑問或者在學習gcc的時候碰到困難,歡迎與我探討。

參考資料

  1. GCC internals.http://gcc.gnu.org/onlinedocs/gccint/,這是得到公認的除了gcc源碼以外,學習gcc最好的材料,雖然比較難讀,但它的權威性和全面性是毋庸置疑的,GCC的手冊和info也是很好的參考資料。
  2. GCC Front end HOWTO, 通過自己設計一個GCC前端來幫助GCC hacker newbie來學習和了解GCC,http://www.icewalkers.com/Linux/Howto/GCC-Frontend-HOWTO.html#toc1,適合新手閱讀,作者還提供了相關程序的軟件包。
  3. StackGuard,http://www.immunix.org/stackguard.html,可以作爲一個在RTL表示層hack gcc的實例來學習。
關於作者
王逸,2002 年秋季入學的南京大學計算機科學與技術系碩士研究生,在分佈式系統國家重點實驗室學習。對於軟件安全、linux系統和無線網絡有興趣,目前工作主要集中 在緩衝區溢出攻擊的防範上。您可以通過[email protected]與我聯繫,也可以在南京大學小百合bbs上(http://bbs.nju.edu.cn/或者http://lilybbs.net/)與LinuxLover賬號聯繫。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章