日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux 可执行文件的分析(gcc GUN BUILEIN)

發(fā)布時間:2023/12/18 linux 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux 可执行文件的分析(gcc GUN BUILEIN) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1、GCC

The History of GCC


?

1984年,Richard Stallman發(fā)起了自由軟件運動,GNU (Gnu's Not Unix)項目應運而生,3年后,最初版的GCC橫空出世,成為第一款可移植、可優(yōu)化、支持ANSI C的開源C編譯器。
GCC最初的全名是GNU C Compiler,之后,隨著GCC支持的語言越來越多,它的名稱變成了GNU Compiler Collection。
這里介紹的gcc是GCC的前端,C編譯器.


警告信息


-Wall : 顯示所有常用的編譯警告信息。
?? ?-W??? : 顯示更多的常用編譯警告,如:變量未使用、一些邏輯錯誤。
?? ?-Wconversion : 警告隱式類型轉換。
?? ?-Wshadow : 警告影子變量(在代碼塊中再次聲明已聲明的變量)
?? ?-Wcast-qual :警告指針修改了變量的修飾符。如:指針修改const變量。
?? ?-Wwrite-strings : 警告修改const字符串。
?? ?-Wtraditional : 警告ANSI編譯器與傳統(tǒng)C編譯器有不同的解釋。
?? ?-Werror : 即使只有警告信息,也不編譯。(gcc默認:若只有警告信息,則進行編譯,若有錯誤信息,則不編譯)


C語言標準


你可以在gcc的命令行中通過指定選項來選擇相應的C語言標準: 從傳統(tǒng)c到最新的GNU擴展C. 默認情況下, gcc使用最新的GNU C擴展.

?? ?-ansi : 關閉GNU擴展中與ANSI C相抵觸的部分。
?? ?-pedantic????????? : 關閉所有的GNU擴展。
?? ?-std=c89?????????? : 遵循C89標準
?? ?-std=c99?????????? : 遵循C99標準
??? -std=traditional : 使用原始C
注意:后4個選項可以與-ansi結合使用,也可以單獨使用。

可在gcc中使用大量GNU C擴展.

生成特定格式的文件


以hello.c為例子,可以設置選項生成hello.i, hello.s, hello.o以及最終的hello文件:

?? ?hello.c : 最初的源代碼文件;
?? ?hello.i : 經過編譯預處理的源代碼;
?? ?hello.s : 匯編處理后的匯編代碼;
?? ?hello.o : 編譯后的目標文件,即含有最終編譯出的機器碼,但它里面所引用的其他文件中函數(shù)的內存位置尚未定義。
??? hello / a.out : 最終的可執(zhí)行文件
??? (還有.a(靜態(tài)庫文件), .so(動態(tài)庫文件), .s(匯編源文件)留待以后討論)

如果你不通過-o指定生成可執(zhí)行文件名,那么會默認生成a.out. 不指定生成文件名肯能覆蓋你上次生成的a.out.

e.g.
$ gcc hello.c
在不給gcc傳遞任何參數(shù)的情況下, gcc執(zhí)行默認的操作: 將源文件編譯為目標文件--> 將目標文件連接為可執(zhí)行文件(名為a.out) --> 刪除目標文件.

-c生成.o文件時,默認生成與源代碼的主干同名的.o文件。比如對應hello.c生成hello.o. 但也可在生成目標文件時指定目標文件名(注意同時要給出.o后綴): $gcc -c -o demo.o demo.c

??? $ gcc -Wall -c hello.c????????????? : 生成hello.o
?? ?$ gcc -Wall -c -save-temps hello.c? : 生成hello.i, hello.s, hello.o
?? ?注意-Wall 選項的使用場合:僅在涉及到編譯(即會生成.o文件時,用-Wall)??

多文件編譯、連接


如果原文件分布于多個文件中:file1.c, file2,c
?? ?$ gcc -Wall file1.c file2.c -o name

若對其中一個文件作了修改,則可只重新編譯該文件,再連接所有文件:
?? ?$ gcc -Wall -c file2.c
?? ?$ gcc file1.c file2.o -c name

注意:若編譯器在命令行中從左向右順序讀取.o文件,則它們的出現(xiàn)順序有限制:含有某函數(shù)定義的文件必須出現(xiàn)在含有調用該函數(shù)的文件之后。好在GCC無此限制。

編譯預處理


以上述的hello.c為例, 要對它進行編譯預備處理, 有兩種方法: 在gcc中指定-E選項, 或直接調用cpp.gcc的編譯預處理命令程序為cpp,比較新版本的gcc已經將cpp集成了,但仍提供了cpp命令. 可以直接調用cpp命令, 也可以在gcc中指定-E選項指定它只進行編譯預處理.

$ gcc -E hello.c??????????????????????????? ==? $ cpp hello.c
上述命令馬上將預處理結果顯示出來. 不利于觀看. 可采用-c將預處理結果保存:
$ gcc -E -c hello.i hello.c????????????? ==? $ cpp -o hello.i hello.c
注意, -c指定名稱要給出".i"后綴.

另外, gcc針對編譯預處理提供了一些選項:
(1) 除了直接在源代碼中用 #define NAME來定義宏外,gcc可在命令行中定義宏:-DNAME(其中NAME為宏名),? 也可對宏賦值: -DNAME=value 注意等號兩邊不能有空格! 由于宏擴展只是一個替換過程,也可以將value換成表達式,但要在兩邊加上雙括號: -DNAME="statement"
e.g. $ gcc -Wall -DVALUE="2+2" tmp.c -o tmp
如果不顯示地賦值,如上例子,只給出:-DVALUE,gcc將使用默認值:1.

(2) 除了用戶定義的宏外, 有一些宏是編譯器自動定義的,它們以__開頭,運行: $ cpp -dM /dev/null, 可以看到這些宏. 注意, 其中含有不以__開頭的非ANSI宏,它們可以通過-ansi選項被禁止。
?
查看宏擴展

1, 運行 $ gcc -E test.c ,gcc對test.c進行編譯預處理,并立馬顯示結果. (不執(zhí)行編譯) 2, 運行 $gcc -c -save-temps test.c ,不光產生test.o,還產生test.i, test.s,前者是編譯預處理結果, 后者是匯編結果.
???
利用Emacs查看編譯預處理結果

針對含有編譯預處理命令的代碼,可以利用emacs方便地查看預處理結果,而不需執(zhí)行編譯,更為方便的是,可以只選取一段代碼,而非整個文件:
1,選擇想要查看的代碼
2,C-c C-e (M-x c-macro-expand)
這樣,就自動在一個名為"Macroexpansion"的buffer中顯示pre-processed結果.

生成匯編代碼

使用"-S"選項指定gcc生成以".s"為后綴的匯編代碼:
$ gcc -S hello.c
$ gcc -S -o hello.s hello.c

生成匯編語言的格式取決于目標平臺. 另外, 如果是多個.c文件, 那么針對每一個.c文件生成一個.s文件.

包含頭文件在程序中包含與連接庫對應的頭文件是很重要的方面,要使用庫,就一定要能正確地引用頭文件。一般在代碼中通過#include引入頭文件, 如果頭文件位于系統(tǒng)默認的包含路徑(/usr/includes), 則只需在#include中給出頭文件的名字, 不需指定完整路徑.? 但若要包含的頭文件位于系統(tǒng)默認包含路徑之外, 則有其它的工作要做: 可以(在源文件中)同時指定頭文件的全路徑. 但考慮到可移植性,最好通過-I在調用gcc的編譯命令中指定。

?

?

下面看這個求立方的小程序(陰影語句表示剛開始不存在):

#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
? double x = pow (2.0, 3.0);
? printf("The cube of 2.0 is %f/n", x);
? return 0;
}

使用gcc-2.95來編譯它(-lm選項在后面的連接選項中有介紹, 這里只討論頭文件的包含問題):
$ gcc-2.95 -Wall pow.c -lm -o pow_2.95
pow.c: In function `main':
pow.c:5: warning: implicit declaration of function `pow'

程序編譯成功,但gcc給出警告: pow函數(shù)隱式聲明。
$ ./pow_2.95
The cube of 2.0 is 1.000000

明顯執(zhí)行結果是錯誤的,在源程序中引入頭文件(#include <math.h>),消除了錯誤。

不要忽略Warning信息!它可能預示著,程序雖然編譯成功,但運行結果可能有錯。故,起碼加上"-Wall"編譯選項!并盡量修正Warning警告。

搜索路徑

首先要理解 #include<file.h>和#include"file.h"的區(qū)別:
#include<file.h>只在默認的系統(tǒng)包含路徑搜索頭文件
#include"file.h"首先在當前目錄搜索頭文件, 若頭文件不位于當前目錄, 則到系統(tǒng)默認的包含路徑搜索頭文件.

UNIX類系統(tǒng)默認的系統(tǒng)路徑為:

頭文件,包含路徑: /usr/local/include/? or? /usr/include/
庫文件,連接路徑: /usr/local/lib/????????? or? /usr/lib/???

對于標準c庫(glibc或其它c庫)的頭文件, 我們可以直接在源文件中使用#include <file.h>來引入頭文件.

如果要在源文件中引入自己的頭文件, 就需要考慮下面的問題:

1, 如果使用非系統(tǒng)頭文件, 頭文件和源文件位于同一個目錄, 如何引用頭文件呢?
——我們可以簡單地在源文件中使用 #include "file.h", gcc將當前目錄的file.h引入到源文件. 如果你很固執(zhí), 仍想使用#include <file.h>語句, 可以在調用gcc時添加"-I."來將當前目錄添加到系統(tǒng)包含路徑. 細心的朋友可能會想到: 這樣對引用其它頭文件會不會有影響? 比如, #include<file.h>之后緊接著一個#include<math.h>, 它能正確引入math.h嗎? 答案是: 沒有影響. 仍然能正確引用math.h. 我的理解是: "-I."將當前目錄作為包含路徑的第一選擇, 若在當前目錄找不到頭文件, 則在默認路徑搜索頭文件. 這實際上和#include"file.h"是一個意思.

2, 對于比較大型的工程, 會有許多用戶自定義的頭文件, 并且頭文件和.c文件會位于不同的目錄. 又該如何在.c文件中引用頭文件呢?
—— 可以直接在.c文件中利用#include“/path/file.h", 通過指定頭文件的路徑(可以是絕對路徑, 也可以是相對路徑)來包含頭文件. 但這明顯降低了程序的可移植性. 在別的系統(tǒng)環(huán)境下編譯可能會出現(xiàn)問題. 所以還是利用"-I"選項指定頭文件完整的包含路徑.

針對頭文件比較多的情況, 最好把它們統(tǒng)一放在一個目錄中, 比如~/project/include. 這樣就不需為不同的頭文件指定不同的路徑. 如果你嫌每次輸入這么多選項太麻煩, 你可以通過設置環(huán)境變量來添加路徑:
$ C_INCLUDE_PATH=/opt/gdbm-1.8.3/include
$ export C_INCLUDE_PATH
$ LIBRART_PATH=/opt/gdbm-1.8.3/lib
$ export LIBRART_PATH

可一次指定多個搜索路徑,":"用于分隔它們,"."表示當前路徑,如:
$ C_INCLUDE_PATH=.:/opt/gdbm-1.8.3/include:/net/include
$ LIBRARY_PATH=.:/opt/gdbm-1.8.3/lib:/net/lib
(可以添加多個路徑,路徑之間用:相隔,.代表當前目錄,若.在最前頭,也可省略)

當然,若想永久地添加這些路徑,可以在.bash_profile中添加上述語句.

3, 還有一個比較猥瑣的辦法: 系統(tǒng)默認的包含路徑不是/usr/include或/usr/local/include么? 我把自己的頭文件拷貝到其中的一個目錄, 不就可以了么? 的確可以這樣, 如果你只想在你自己的機器上編譯運行這個程序的話.

前面介紹了三種添加搜索路徑的方法,如果這三種方法一起使用,優(yōu)先級如何呢?
命令行設置 > 環(huán)境變量設置 > 系統(tǒng)默認

與外部庫連接


前面介紹了如何包含頭文件. 而頭文件和庫是息息相關的, 使用庫時, 要在源代碼中包含適當?shù)念^文件,這樣才能聲明庫中函數(shù)的原型(發(fā)布庫時, 就需要給出相應的頭文件).

和包含路徑一樣, 系統(tǒng)也有默認的連接路徑:
頭文件,包含路徑: /usr/local/include/? or? /usr/include/
庫文件,連接路徑: /usr/local/lib/????????? or? /usr/lib/??

同樣地, 我們想要使用某個庫里的函數(shù), 必須將這個庫連接到使用那些函數(shù)的程序中.

有一個例外: libc.a或libc.so (C標準庫,它包含了ANSI C所定義的C函數(shù))是不需要你顯式連接的, 所有的C程序在運行時都會自動加載c標準庫.

除了C標準庫之外的庫稱之為"外部庫", 它可能是別人提供給你的, 也可能是你自己創(chuàng)建的(后面有介紹如何創(chuàng)建庫的內容).

外部庫有兩種:(1)靜態(tài)連接庫lib.a
???????????????????? (2)共享連接庫lib.so

兩者的共同點:
??? .a, .so都是.o目標文件的集合,這些目標文件中含有一些函數(shù)的定義(機器碼),而這些函數(shù)將在連接時會被最終的可執(zhí)行文件用到。

兩者的區(qū)別:
?? ?靜態(tài)庫.a? : 當程序與靜態(tài)庫連接時,庫中目標文件所含的所有將被程序使用的函數(shù)的機器碼被copy到最終的可執(zhí)行文件中. 靜態(tài)庫有個缺點: 占用磁盤和內存空間. 靜態(tài)庫會被添加到和它連接的每個程序中, 而且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.

??? 共享庫.so : 與共享庫連接的可執(zhí)行文件只包含它需要的函數(shù)的引用表,而不是所有的函數(shù)代碼,只有在程序執(zhí)行時, 那些需要的函數(shù)代碼才被拷貝到內存中, 這樣就使可執(zhí)行文件比較小, 節(jié)省磁盤空間(更進一步,操作系統(tǒng)使用虛擬內存,使得一份共享庫駐留在內存中被多個程序使用).共享庫還有個優(yōu)點: 若庫本身被更新, 不需要重新編譯與它連接的源程序。

靜態(tài)庫

下面我們來看一個簡單的例子,計算2.0的平方根(假設文件名為sqrt.c):

#include <math.h>
#include <stdio.h>
int
main (void)
{
double x = sqrt (2.0);
printf ("The square root of 2.0 is %f/n", x);
return 0;
}

用gcc將它編譯為可執(zhí)行文件:
$ gcc -Wall sqrt.c -o sqrt
編譯成功,沒有任何警告或錯誤信息。執(zhí)行結果也正確。
$ ./sqrt
The square root of 2.0 is 1.414214
????
下面我們來看看剛才使用的gcc版本:
$ gcc --version
??gcc (GCC) 4.0.2 20050808 (prerelease) (Ubuntu 4.0.1-4ubuntu9)

現(xiàn)在我用2.95版的gcc把sqrt.c再編譯一次:
$ gcc-2.95 -Wall sqrt.c -o sqrt_2.95
??/tmp/ccVBJd2H.o: In function `main':
??sqrt.c:(.text+0x16): undefined reference to `sqrt'
???? collect2: ld returned 1 exit status
????
編譯器會給出上述錯誤信息,這是因為sqrt函數(shù)不能與外部數(shù)學庫"libm.a"相連。sqrt函數(shù)沒有在程序中定義,也不存在于默認C庫 "libc.a"中,如果用gcc-2.95,應該顯式地選擇連接庫。上述出錯信息中的"/tmp/ccVBJd2H.o"是gcc創(chuàng)造的臨時目標文件,用作連接時用。

使用下列的命令可以成功編譯:
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95
它告知gcc:在編譯sqrt.c時,加入位于/usr/lib中的libm.a庫(C數(shù)學庫)。

C庫文件默認位于/usr/lib, /usr/local/lib系統(tǒng)目錄中; gcc默認地從/usr/local/lib, /usr/lib中搜索庫文件。(在我的Ubuntu系統(tǒng)中,C庫文件位于/urs/lib中。

這里還要注意連接順序的問題,比如上述命令,如果我改成:
$ gcc-2.95 -Wall /usr/lib/libm.asqrt.c -o sqrt_2.95
gcc會給出出錯信息:
?/tmp/cc6b3bIa.o: In function `main':
?sqrt.c:(.text+0x16): undefined reference to `sqrt'
?collect2: ld returned 1 exit status

正如讀取目標文件的順序,gcc也在命令行中從左向右讀取庫文件——任何包含某函數(shù)定義的庫文件必須位于調用該函數(shù)的目標文件之后!

指定庫文件的絕對路徑比較繁瑣,有一種簡化方法,相對于上述命令,可以用下面的命令來替代:
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95
其中的"-l"表示與庫文件連接,"m"代表"libm.a"中的m。一般而言,"-lNAME"選項會使gcc將目標文件與名為"libNAME.a"的庫文件相連。(這里假設使用默認目錄中的庫,對于其他目錄中的庫文件,參考后面的“搜索路徑”。)

上面所提到的"libm.a"就是靜態(tài)庫文件,所有靜態(tài)庫文件的擴展名都是.a!
$ whereis libm.a
??libm: /usr/lib/libm.a /usr/lib/libm.so

正如前面所說,默認的庫文件位于/usr/lib/或/usr/local/lib/目錄中。其中,libm.a是靜態(tài)庫文件,libm.so是后面會介紹的動態(tài)共享庫文件。

如果調用的函數(shù)都包含在libc.a中(C標準庫被包含在/usr/lib/libc.a中,它包含了ANSI C所定義的C函數(shù))。那么沒有必要顯式指定libc.a:所有的C程序運行時都自動包含了C標準庫!(試試 $ gcc-2.95 -Wall hello.c -o hello)。

共享庫

正因為共享庫的優(yōu)點,如果系統(tǒng)中存在.so庫,gcc默認使用共享庫(在/usr/lib/目錄中,庫文件以共享和靜態(tài)兩種版本存在)。?

運行:$ gcc -Wall -L. hello.c -lNAME -o hello
gcc先檢查是否有替代的libNAME.so庫可用。???

正如前面所說,共享庫以.so為擴展名(so == shared object)。

那么,如果不想用共享庫,而只用靜態(tài)庫呢?可以加上 -static選項
$ gcc -Wall -static hello.c -lNAME -o hello
它等價于:
$ gcc -Wall hello.c libNAME.a -o hello

$ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so

$ ls -l sqrt*
-rwxr-xr-x? 1 zp zp? 21076 2006-04-25 14:52 sqrt_2.95_a
-rwxr-xr-x? 1 zp zp?? 7604 2006-04-25 14:52 sqrt_2.95_default
-rwxr-xr-x? 1 zp zp?? 7604 2006-04-25 14:52 sqrt_2.95_so
-rwxr-xr-x? 1 zp zp 487393 2006-04-25 14:52 sqrt_2.95_static

上述用四種方式編譯sqrt.c,并比較了可執(zhí)行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a為什么有區(qū)別?有知其原因著,懇請指明,在此謝謝了! :)

如果libNAME.a在當前目錄,應執(zhí)行下面的命令:
$ gcc -Wall -L. hello.c -lNAME -o hello
-L.表示將當前目錄加到連接路徑。

利用GNU archiver創(chuàng)建庫

$ ar cr libhello.a hello_fn.o by_fn.o
從hello_fn.o和by_fn.o創(chuàng)建libihello.a,其中cr表示:creat & replace
$ ar t libhello.a
列出libhello.a中的內容,t == table
(也可創(chuàng)建libhello.so)

關于創(chuàng)建庫的詳細介紹,可參考本blog的GNU binutils筆記


調試

?


一般地,可執(zhí)行文件中是不包含任何對源代碼的參考的,而debugger要工作,就要知道目標文件/可執(zhí)行文件中的機器碼對應的源代碼的信息(如:哪條語句、函數(shù)名、變量名...). debugger工作原理:將函數(shù)名、變量名,對它們的引用,將所有這些對象對應的代碼行號儲存到目標文件或可執(zhí)行文件的符號表中。

?

GCC提供-g選項,將調試信息加入到目標文件或可執(zhí)行文件中。
$ gcc -Wall -g hello.c -o hello

注意:若發(fā)生了段錯誤,但沒有core dump,是由于系統(tǒng)禁止core文件的生成!
$ ulimit -c  ,若顯示為0,則系統(tǒng)禁止了core dump

解決方法:
$ ulimit -c unlimited  (只對當前shell進程有效)
或在~/.bashrc 的最后加入: ulimit -c unlimited (一勞永逸)

優(yōu)化


GCC具有優(yōu)化代碼的功能,代碼的優(yōu)化是一項比較復雜的工作,它可歸為:源代碼級優(yōu)化、速度與空間的權衡、執(zhí)行代碼的調度。

?

GCC提供了下列優(yōu)化選項:
??? -O0? : 默認不優(yōu)化(若要生成調試信息,最好不優(yōu)化)
??? -O1? : 簡單優(yōu)化,不進行速度與空間的權衡優(yōu)化;???
??? -O2? : 進一步的優(yōu)化,包括了調度。(若要優(yōu)化,該選項最適合,它是GNU發(fā)布軟件的默認優(yōu)化級別;
??? -O3? : 雞肋,興許使程序速度更慢;
??? -funroll-loops? : 展開循環(huán),會使可執(zhí)行文件增大,而速度是否增加取決于特定環(huán)境;
??? -Os? : 生成最小執(zhí)行文件;

一般來說,調試時不優(yōu)化,一般的優(yōu)化選項用-O2(gcc允許-g與-O2聯(lián)用,這也是GNU軟件包發(fā)布的默認選項),embedded可以考慮-Os。

注意:此處為O!(非0或小寫的o,-o是指定可執(zhí)行文件名)。

檢驗優(yōu)化結果的方法:$ time ./prog

time測量指定程序的執(zhí)行時間,結果由三部分組成:
??? real : 進程總的執(zhí)行時間, 它和系統(tǒng)負載有關(包括了進程調度,切換的時間)
??? user: 被測量進程中用戶指令的執(zhí)行時間
??? sys? : 被測量進程中內核代用戶指令執(zhí)行的時間

user和sys的和被稱為CPU時間.

注意:對代碼的優(yōu)化可能會引發(fā)警告信息,移出警告的辦法不是關閉優(yōu)化,而是調整代碼。


2、ar


??? ar用于建立、修改、提取檔案文件(archive)。archive是一個包含多個被包含文件的單一文件(也稱之為庫文件),其結構保證了可以從中檢索并得到原始的被包含文件(稱之為archive中的member)。member的原始文件內容、模式(權限)、時間戳、所有著和組等屬性都被保存在 archive中。member被提取后,他們的屬性被恢復到初始狀態(tài)。
???
??? ar主要用于創(chuàng)建C庫文件

?

創(chuàng)建靜態(tài)庫?
??? (1) 生成目標文件:??

$ gcc -Wall -c file1.c file2.c file3.c
???
??? 不用指定生成.o文件名(默認生成file1.o, file2.o, file3.o)。

??? (2) 從.o目標文件創(chuàng)建靜態(tài)連接庫:
???
$ ar rv libNAME.a file1.o file2.o file3.o
???
??? ar生成了libNAME.a庫,并列出庫中的文件。
??? r : 將flie1.o, file2,o, file3.o插入archive,如故原先archive中已經存在某文件,則先將該文件刪除。
??? v : 顯示ar操作的附加信息(如被處理的member文件名)

注: 對于BSD系統(tǒng), 還需要在創(chuàng)建靜態(tài)庫之后創(chuàng)建索引: $ ranlib libNAME.a Linux中不需要這一步(運行它也是無害的).

創(chuàng)建動態(tài)庫(利用gcc,未用ar)

(1) 生成目標文件

$ gcc -Wall -c -fpic file1.c file2.c file3.c

-fpic: 指定生成的.o目標文件可被重定址. pic是position idependent code的縮寫: 位置無關代碼.

(2)生成動態(tài)庫文件

$ gcc -shared -o libNAME.so file1.o file2.o file3.o

一般地, 連接器使用main()函數(shù)作為程序入口. 但在動態(tài)共享庫中沒有這樣的入口. 所以就要指定-shared選項來避免編譯器顯示出錯信息.

實際上, 上述的兩條命令可以合并為下面這條:

$ gcc -Wall -shared -fpic -o libNAME.so file1.c file2.c file3.c


此后,將main函數(shù)所在的程序與libNAME.so連接(注意庫連接路徑和頭文件包含路徑,以及連接順序!參考 gcc筆記)
???
至此,與動態(tài)庫連接的函數(shù)編譯成了一個可執(zhí)行文件。貌似成功了,但還差最后一步。如果直接運行該程序,會給出這樣的錯誤信息:

error while loading shared libraries: libhello.so:
cannot open shared object file: No such file or directory

這是因為與動態(tài)庫連接的程序在運行時,首先將該動態(tài)庫加載到內存中,而gcc默認加載動態(tài)庫文件所在目錄為/usr/local/lib, /usr/lib。剛才的程序雖然能編譯成功,但如果我們自己建立的動態(tài)庫沒有位于默認目錄中,則執(zhí)行時會應為無法找到它而失敗。
??
解決辦法:改變加載路徑對應的環(huán)境變量,然后再執(zhí)行。
???
export LD_LIBRARY_PATH=動態(tài)庫所在目錄:$LD_LIBRARY_PATH

查看archive內容

$ ar tv archiveNAME

t : 顯示archive中member的內容,若不指定member,則列出所有。
v : 與t結合使用時,顯示member的詳細信息。

要想進了解ar的詳細選項,參考ar的 on-line manual


nm

??? nm用來列出目標文件中的符號,可以幫助程序員定位和分析執(zhí)行程序和目標文件中的符號信息和它的屬性。
??? 如果沒有目標文件作為參數(shù)傳遞給nm, nm假定目標文件為a.out.
??? 這里用一個簡單的示例程序來介紹nm的用法:

main.c:

int main(int argc, char *argv[])
{
? hello();
? bye();
? return 0;
}

hello.c:??
void hello(void)
{
? printf("hello!/n");
}

bye.c:
???
void bye(void)
{
? printf("good bye!/n");
}

??? 運行下列命令:
??? $ gcc -Wall -c main.c hello.c bye.c
??? gcc生成main.o, hello.o, bye.o三個目標文件(這里沒有聲明函數(shù)原型,加了-Wall,gcc會給出警告)
??? $ nm main.o hello.o bye.o

結果顯示如下:??
main.o:
???????????????? U bye
???????????????? U hello
00000000 T main

hello.o:
00000000 T hello
??????? ? ? ? ?? U puts

bye.o:
00000000 T bye
???????????????? U puts

??? 結合這些輸出結果,以及程序代碼,可以知道:
??? 對于main.o, bye和hello未被定義, main被定義了
??? 對于hello.o, hello被定義了, puts未被定義
??? 對于bye.o, bye被定義了,puts未被定義

幾個值得注意的問題:
??? (1)"目標文件"指.o文件, 庫文件, 最終的可執(zhí)行文件
??? .o? : 編譯后的目標文件,即含有最終編譯出的機器碼,但它里面所引用的其他文件中函數(shù)的內存位置尚未定義.
??? (2)如果用nm查看可執(zhí)行文件, 輸出會比較多, 仔細研究輸出, 可以對nm用法有更清醒的認識.
??? (3)在上述hello.c, bye.c中, 調用的是printf(), 而nm輸出中顯示調用的是puts(), 說明最終程序實際調用的puts(), 如果令hello.c或bye.c中的printf()使用格式化輸出,則nm顯示調用printf(). ( 如: printf("%d", 1); )
???
??? 關于nm的參數(shù)選項,參考 on-line manual


objcopy

??? objcopy可以將一種格式的目標文件轉化為另外一種格式的目標文件. 它使用GNU BFD庫進行讀/寫目標文件.使用BFD, objcopy就能將原格式的目標文件轉化為不同格式的目標文件.
??? 以我們在nm中使用的hello.o目標文件和hello可執(zhí)行為例:

$ file hello.o hello
??
??? file命令用來判別文件類型, 輸出如下:
???
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
hello:? ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), not stripped
???
??? 現(xiàn)在運行objcopy來改變hello的文件類型: 原先它是ELF格式的可執(zhí)行程序, 現(xiàn)將它轉換為srec格式. srec格式文件是Motolora S-Record格式的文件, 主要用來在主機和目標機之間傳輸數(shù)據.
???
$ objcopy -O srec hello hello_srec
$ file hello.o hello

??? file命令結果: hello_srec: Motorola S-Record; binary data in text format

??? 注意objcopy的格式, "-O"指定輸出文件類型; 輸入文件名和輸出文件名位于命令末尾. 關于objcopy命令的詳細選項, 參考 on-line manual


objdump

??? objdump用來顯示目標文件的信息. 可以通過選項控制顯示那些特定信息. objdump一個最大的用處恐怕就是將C代碼反匯編了. 在嵌入式軟件開發(fā)過程中, 也可以用它查看執(zhí)行文件或庫文件的信息.
??? 下面我們用上文提到的hello可執(zhí)行文件和hello_srec可執(zhí)行文件為例, 介紹objdump的簡單用法:
???

$ objdump -f hello hello_srec

輸出如下:
hello:???? file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482c0

hello_srec:???? file format srec
architecture: UNKNOWN!, flags 0x00000000:
start address 0x00000000080482c0
???
-f : 顯示目標文件的頭文件概要信息.

生成反匯編代碼:
???

$ objdump -d hello.o

顯示如下:
hello.o:???? file format elf32-i386

Disassembly of section .text:

00000000 <hello>:
?? 0:?? 55????????????????????? push?? %ebp
?? 1:?? 89 e5?????????????????? mov??? %esp,%ebp
?? 3:?? 83 ec 08??????????????? sub??? $0x8,%esp
?? 6:?? 83 ec 0c??????????????? sub??? $0xc,%esp
?? 9:?? 68 00 00 00 00????????? push?? $0x0
?? e:?? e8 fc ff ff ff????????? call?? f <hello+0xf>
? 13:?? 83 c4 10??????????????? add??? $0x10,%esp
? 16:?? c9????????????????????? leave
? 17:?? c3????????????????????? ret

??? -d : 顯示目標文件中機器指令使用的匯編語言. 只反匯編那些應該含有指令機器碼的節(jié)(顯示.text段); 如果用-D, 則反匯編所有節(jié)的內容.
??? 關于objcopy命令的詳細選項, 參考 on-line manual


readelf

??? readelf用來顯示ELF格式目標文件的信息.可通過參數(shù)選項來控制顯示哪些特定信息.(注意: readelf不支持顯示archive文檔, 也不支持64位的ELF文件).
??? 下面利用先前的hello可執(zhí)行文件演示readelf的簡單用法:
???

$ readelf -h hello

ELF Header:
? Magic:?? 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
? Class:??????????????????????????? ? ? ? ? ? ? ?? ELF32
? Data:??????????????????????????????????????????? 2's complement, little endian
? Version:??????????????????????????????????????? 1 (current)
? OS/ABI:????????????????????????? ? ? ? ? ? ? ? ? UNIX - System V
? ABI Version:???????????????????? ? ? ? ? ? ? ? 0
? Type:????????????????????????????????????????????? EXEC (Executable file)
? Machine:??????????????????????????????????????? Intel 80386
? Version:????????????????????????????????????????? 0x1
? Entry point address:?????????????????????? 0x80482c0
? Start of program headers:???????? ? ?? 52 (bytes into file)
? Start of section headers:???????? ? ? ? ? 3848 (bytes into file)
? Flags:?????????????????????????? ? ? ? ? ? ? ? ? ? ? 0x0
? Size of this header:????????????????????????? 52 (bytes)
? Size of program headers:???????? ? ? ?? 32 (bytes)
? Number of program headers:?????? ?? 7
? Size of section headers:????????? ? ? ? ? 40 (bytes)
? Number of section headers:??????????? 34
? Section header string table index:?? 31

注意: readelf只能用于ELF格式目標文件, 且選項中至少要指定一個(除V, H外)的選項!


gprof

??? gprof被用來測量程序的性能. 它記錄每個函數(shù)被調用的次數(shù)以及相應的執(zhí)行時間. 這樣就能鎖定程序執(zhí)行時花費時間最多的部分, 對程序的優(yōu)化就可集中于對它們的優(yōu)化.
???
??? 用一個簡單的數(shù)值計算程序來掩飾gprof的用法:

collatz.c:

#include <stdio.h>
/* Computes the length of Collatz sequences */
unsigned int step (unsigned int x)
{
???? if (x % 2 == 0)
???? {
??? ? return (x / 2);
???? }
???? else
???? {
??? ? return (3 * x + 1);
???? }
}

unsigned int nseq (unsigned int x0)
{
???? unsigned int i = 1, x;
???? if (x0 == 1 || x0 == 0)
??? ? return i;
???? x = step (x0);
???? while (x != 1 && x != 0)
???? {
??? ? x = step (x);
??? ? i++;
???? }
???? return i;
}

int main (void)
{
???? unsigned int i, m = 0, im = 0;
???? for (i = 1; i < 500000; i++)
???? {
??? ? unsigned int k = nseq (i);
??? ? if (k > m)
??? ? {
??? ?????? m = k;
??? ?????? im = i;
??? ?????? printf ("sequence length = %u for %u/n", m, im);
??? ? }
???? }
???? return 0;
}

??? 先將collatz.c編譯成目標文件collatz.o, gcc通過 -pg選項來打開gprof支持:
???
$ gcc -Wall -c -pg collatz.c

$ gcc -Wall -pg -o collatz collatz.o

??? 注意:兩條命令都要加 "-pg"選項。前一條命令生成collatz.o目標文件。后一條命令生成可執(zhí)行文件,該可執(zhí)行文件中包含了記錄函數(shù)執(zhí)行時間的指令。
??? 生成collatz可執(zhí)行文件后,現(xiàn)執(zhí)行它,結果與一般程序的執(zhí)行無疑。但此時在PWD目錄生成一個名為"gmon.out"的文件,gprof通過它來分析程序的執(zhí)行。
??? 如果不現(xiàn)執(zhí)行程序,而直接用gprof來分析它,會提示“gmon.out: No such file or directory”。
??? gprof用法:
???
$ gprof ./collatz

關于gprof更多的描述,參考gprof的 on-line manual

3、可執(zhí)行文件格式

目標文件格式與類型

GNU C compiler根據源文件的后綴名來對文件進行預處理、匯編或編譯操作。在編譯鏈接時,生成的目標文件都是ELF格式的(可執(zhí)行鏈接格式,Executable and Linking Format)。Object文件格式有三種類型:

(1)可重定位(relocatable)文件:用來和其他的object文件一起鏈接為一個可執(zhí)行文件(executable)或一個共享文件(.so文件,shared object)。

(2)可執(zhí)行(executable)文件;

(3)共享目標文件(shared object file):用于被下面的兩個鏈接器鏈接。一是鏈接編輯器(ld),可以和其他的relocatable或shared object file來創(chuàng)建其他的目標文件,例如.so共享庫(可用file命令查看其屬性);二是動態(tài)鏈接器,聯(lián)合一個可執(zhí)行文件和其他的shared object file來創(chuàng)建一個進程映像

?

首先看看ELF文件的總體布局:

ELF header(ELF頭部)
Program header table(程序頭表)
Segment1(段1)
Segment2(段2)
………
Sengmentn(段n)
Setion header table(節(jié)頭表,可選)

段由若干個節(jié)(Section)構成,節(jié)頭表對每一個節(jié)的信息有相關描述。對可執(zhí)行程序而言,節(jié)頭表是可選的。 參考資料 1中作者談到把節(jié)頭表的所有數(shù)據全部設置為0,程序也能正確運行!ELF頭部是一個關于本文件的路線圖(road map),從總體上描述文件的結構。下面是ELF頭部的數(shù)據結構:

typedef struct {unsigned char e_ident[EI_NIDENT]; /* 魔數(shù)和相關信息 */Elf32_Half e_type; /* 目標文件類型 */Elf32_Half e_machine; /* 硬件體系 */Elf32_Word e_version; /* 目標文件版本 */Elf32_Addr e_entry; /* 程序進入點 */Elf32_Off e_phoff; /* 程序頭部偏移量 */Elf32_Off e_shoff; /* 節(jié)頭部偏移量 */Elf32_Word e_flags; /* 處理器特定標志 */Elf32_Half e_ehsize; /* ELF頭部長度 */Elf32_Half e_phentsize; /* 程序頭部中一個條目的長度 */Elf32_Half e_phnum; /* 程序頭部條目個數(shù) */Elf32_Half e_shentsize; /* 節(jié)頭部中一個條目的長度 */Elf32_Half e_shnum; /* 節(jié)頭部條目個數(shù) */Elf32_Half e_shstrndx; /* 節(jié)頭部字符表索引 */ } Elf32_Ehdr;

下面我們對ELF頭表中一些重要的字段作出相關說明,完整的ELF定義請參閱 參考資料6參考資料 7

e_ident[0]-e_ident[3]包含了ELF文件的魔數(shù),依次是0x7f、'E'、'L'、'F'。注意,任何一個ELF文件必須包含此魔數(shù)。 參考資料 3中討論了利用程序、工具、/Proc文件系統(tǒng)等多種查看ELF魔數(shù)的方法。e_ident[4]表示硬件系統(tǒng)的位數(shù),1代表32位,2代表64位。e_ident[5]表示數(shù)據編碼方式,1代表小印第安排序(最大有意義的字節(jié)占有最低的地址),2代表大印第安排序(最大有意義的字節(jié)占有最高的地址)。e_ident[6]指定ELF頭部的版本,當前必須為1。e_ident[7]到e_ident[14]是填充符,通常是0。ELF格式規(guī)范中定義這幾個字節(jié)是被忽略的,但實際上是這幾個字節(jié)完全可以可被利用。如病毒Lin/Glaurung.676/666(參考資料 1)設置e_ident[7]為0x21,表示本文件已被感染;或者存放可執(zhí)行代碼(參考資料 2)。ELF頭部中大多數(shù)字段都是對子頭部數(shù)據的描述,其意義相對比較簡單。值得注意的是某些病毒可能修改字段e_entry(程序進入點)的值,以指向病毒代碼,例如上面提到的病毒Lin/Glaurung.676/666。

一個實際可執(zhí)行文件的文件頭部形式如下:(利用命令readelf)

ELF Header:Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Intel 80386Version: 0x1Entry point address: 0x80483ccStart of program headers: 52 (bytes into file)Start of section headers: 14936 (bytes into file)Flags: 0x0Size of this header: 52 (bytes)Size of program headers: 32 (bytes)Number of program headers: 6Size of section headers: 40 (bytes)Number of section headers: 34Section header string table index: 31

緊接ELF頭部的是程序頭表,它是一個結構數(shù)組,包含了ELF頭表中字段e_phnum定義的條目,結構描述一個段或其他系統(tǒng)準備執(zhí)行該程序所需要的信息。

typedef struct {Elf32_Word p_type; /* 段類型 */Elf32_Off p_offset; /* 段位置相對于文件開始處的偏移量 */Elf32_Addr p_vaddr; /* 段在內存中的地址 */Elf32_Addr p_paddr; /* 段的物理地址 */Elf32_Word p_filesz; /* 段在文件中的長度 */Elf32_Word p_memsz; /* 段在內存中的長度 */Elf32_Word p_flags; /* 段的標記 */Elf32_Word p_align; /* 段在內存中對齊標記 */} Elf32_Phdr;

在詳細討論可執(zhí)行文件程序頭表之前,首先查看一個實際文件的輸出:

Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4 INTERP 0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R 0x1[Requesting program interpreter: /lib/ld-linux.so.2]LOAD 0x000000 0x08048000 0x08048000 0x00684 0x00684 R E 0x1000LOAD 0x000684 0x08049684 0x08049684 0x00118 0x00130 RW 0x1000DYNAMIC 0x000690 0x08049690 0x08049690 0x000c8 0x000c8 RW 0x4NOTE 0x000108 0x08048108 0x08048108 0x00020 0x00020 R 0x4Section to Segment mapping:Segment Sections...00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 03 .data .dynamic .ctors .dtors .jcr .got .bss 04 .dynamic 05 .note.ABI-tag Section Headers:[Nr] Name Type Addr Off Size ES Flg Lk Inf Al[ 0] NULL 00000000 000000 000000 00 0 0 0[ 1] .interp PROGBITS 080480f4 0000f4 000013 00 A 0 0 1[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4[ 3] .hash HASH 08048128 000128 000040 04 A 4 0 4[ 4] .dynsym DYNSYM 08048168 000168 0000b0 10 A 5 1 4[ 5] .dynstr STRTAB 08048218 000218 00007b 00 A 0 0 1[ 6] .gnu.version VERSYM 08048294 000294 000016 02 A 4 0 2[ 7] .gnu.version_r VERNEED 080482ac 0002ac 000030 00 A 5 1 4[ 8] .rel.dyn REL 080482dc 0002dc 000008 08 A 4 0 4[ 9] .rel.plt REL 080482e4 0002e4 000040 08 A 4 b 4[10] .init PROGBITS 08048324 000324 000017 00 AX 0 0 4[11] .plt PROGBITS 0804833c 00033c 000090 04 AX 0 0 4[12] .text PROGBITS 080483cc 0003cc 0001f8 00 AX 0 0 4[13] .fini PROGBITS 080485c4 0005c4 00001b 00 AX 0 0 4[14] .rodata PROGBITS 080485e0 0005e0 00009f 00 A 0 0 32[15] .eh_frame PROGBITS 08048680 000680 000004 00 A 0 0 4[16] .data PROGBITS 08049684 000684 00000c 00 WA 0 0 4[17] .dynamic DYNAMIC 08049690 000690 0000c8 08 WA 5 0 4[18] .ctors PROGBITS 08049758 000758 000008 00 WA 0 0 4[19] .dtors PROGBITS 08049760 000760 000008 00 WA 0 0 4[20] .jcr PROGBITS 08049768 000768 000004 00 WA 0 0 4[21] .got PROGBITS 0804976c 00076c 000030 04 WA 0 0 4[22] .bss NOBITS 0804979c 00079c 000018 00 WA 0 0 4[23] .comment PROGBITS 00000000 00079c 000132 00 0 0 1[24] .debug_aranges PROGBITS 00000000 0008d0 000098 00 0 0 8[25] .debug_pubnames PROGBITS 00000000 000968 000040 00 0 0 1[26] .debug_info PROGBITS 00000000 0009a8 001cc6 00 0 0 1[27] .debug_abbrev PROGBITS 00000000 00266e 0002cc 00 0 0 1[28] .debug_line PROGBITS 00000000 00293a 0003dc 00 0 0 1[29] .debug_frame PROGBITS 00000000 002d18 000048 00 0 0 4[30] .debug_str PROGBITS 00000000 002d60 000bcd 01 MS 0 0 1[31] .shstrtab STRTAB 00000000 00392d 00012b 00 0 0 1[32] .symtab SYMTAB 00000000 003fa8 000740 10 33 56 4[33] .strtab STRTAB 00000000 0046e8 000467 00 0 0 1

對一個ELF可執(zhí)行程序而言,一個基本的段是標記p_type為PT_INTERP的段,它表明了運行此程序所需要的程序解釋器(/lib/ld-linux.so.2),實際上也就是動態(tài)連接器(dynamic linker)。最重要的段是標記p_type為PT_LOAD的段,它表明了為運行程序而需要加載到內存的數(shù)據。查看上面實際輸入,可以看見有兩個可LOAD段,第一個為只讀可執(zhí)行(FLg為R E),第二個為可讀可寫(Flg為RW)。段1包含了文本節(jié).text,注意到ELF文件頭部中程序進入點的值為0x80483cc,正好是指向節(jié).text在內存中的地址。段二包含了數(shù)據節(jié).data,此數(shù)據節(jié)中數(shù)據是可讀可寫的,相對的只讀數(shù)據節(jié).rodata包含在段1中。ELF格式可以比COFF格式包含更多的調試信息,如上面所列出的形式為.debug_xxx的節(jié)。在I386平臺LINUX系統(tǒng)下,用命令file查看一個ELF可執(zhí)行程序的可能輸出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped。

ELF文件中包含了動態(tài)連接器的全路徑,內核定位"正確"的動態(tài)連接器在內存中的地址是"正確"運行可執(zhí)行文件的保證, 參考資料 13討論了如何通過查找動態(tài)連接器在內存中的地址以達到顛覆(Subversiver)動態(tài)連接機制的方法。

最后我們討論ELF文件的動態(tài)連接機制。每一個外部定義的符號在全局偏移表(Global Offset Table GOT)中有相應的條目,如果符號是函數(shù)則在過程連接表(Procedure Linkage Table PLT)中也有相應的條目,且一個PLT條目對應一個GOT條目。對外部定義函數(shù)解析可能是整個ELF文件規(guī)范中最復雜的,下面是函數(shù)符號解析過程的一個描述。

1:代碼中調用外部函數(shù)func,語句形式為call 0xaabbccdd,地址0xaabbccdd實際上就是符號func在PLT表中對應的條目地址(假設地址為標號.PLT2)。

2:PLT表的形式如下

.PLT0: pushl 4(%ebx) /* GOT表的地址保存在寄存器ebx中 */ jmp *8(%ebx)nop; nopnop; nop.PLT1: jmp *name1@GOT(%ebx)pushl $offsetjmp .PLT0@PC.PLT2: jmp *func@GOT(%ebx)pushl $offsetjmp .PLT0@PC

3:查看標號.PLT2的語句,實際上是跳轉到符號func在GOT表中對應的條目。

4:在符號沒有重定位前,GOT表中此符號對應的地址為標號.PLT2的下一條語句,即是pushl $offset,其中$offset是符號func的重定位偏移量。注意到這是一個二次跳轉。

5:在符號func的重定位偏移量壓棧后,控制跳到PLT表的第一條目,把GOT[1]的內容壓棧,并跳轉到GOT[2]對應的地址。

6:GOT[2]對應的實際上是動態(tài)符號解析函數(shù)的代碼,在對符號func的地址解析后,會把func在內存中的地址設置到GOT表中此符號對應的條目中。

7:當?shù)诙握{用此符號時,GOT表中對應的條目已經包含了此符號的地址,就可直接調用而不需要利用PLT表進行跳轉。

動態(tài)連接是比較復雜的,但為了獲得靈活性的代價通常就是復雜性。其最終目的是把GOT表中條目的值修改為符號的真實地址,這也可解釋節(jié).got包含在可讀可寫段中。

動態(tài)連接是一個非常重要的進步,這意味著庫文件可以被升級、移動到其他目錄等等而不需要重新編譯程序(當然,這不意味庫可以任意修改,如函數(shù)入參的個數(shù)、數(shù)據類型應保持兼容性)。從很大程度上說,動態(tài)連接機制是ELF格式代替a.out格式的決定性原因。如果說面對對象的編程本質是面對接口(interface)的編程,那么動態(tài)連接機制則是這種思想的地一個非常典型的應用,具體的講,動態(tài)連接機制與設計模式中的橋接(BRIDGE)方法比較類似,而它的LAZY特性則與代理(PROXY)方法非常相似。動態(tài)連接操作的細節(jié)描述請參閱參考資料 8,9,10,11。通過閱讀命令readelf、objdump 的源代碼以及參考資料 14中所提及的相關軟件源代碼,可以對ELF文件的格式有更徹底的了解。

?

4. 鏈接與鏈接腳本

鏈接器ld把object文件中的每個section都作為一個整體,為其分配運行的地址(memory layout),這個過程就是重定位(relocation);最后把所有目標文件合并為一個目標文件。

?


鏈接通過一個linker script來控制,這個腳本描述了輸入文件的sections到輸出文件的映射,以及輸出文件的memory layout。

因此,linker總會使用一個linker script,如果不特別指定,則使用默認的script;可以使用‘-T’命令行選項來指定一個linker script。

?

*映像文件的輸入段與輸出段

linker把多個輸入文件合并為一個輸出文件。輸出文件和輸入文件都是目標文件(object file),輸出文件通常被稱為可執(zhí)行文件(executable)。

每個目標文件都有一系列section,輸入文件的section稱為input section,輸出文件的section則稱為output section。

一個section可以是loadable的,即輸出文件運行時需要將這樣的section加載到memory(類似于RO&RW段);也可以是allocatable的,這樣的section沒有任何內容,某些時候用0對相應的memory區(qū)域進行初始化(類似于ZI段);如果一個section既非loadable也非allocatable,則它通常包含的是調試信息。

每個loadable或allocatable的output section都有兩個地址,一是VMA(virtual memory address),是該section的運行時域地址;二是LMA(load memory address),是該section的加載時域地址。

可以通過objdump工具附加'-h'選項來查看目標文件中的sections

*簡單的Linker script

(1) SECTIONS命令:

The SECTIONS command tells the linker how to map input sections into output sections, and how to place the output sections in memory.

命令格式如下:

SECTIONS

{

sections-command

sections-command

......

}

其中sections-command可以是ENTRY命令,符號賦值,輸出段描述,也可以是overlay描述。

?

(2) 地址計數(shù)器‘.’(location counter):

該符號只能用于SECTIONS命令內部,初始值為‘0’,可以對該符號進行賦值,也可以使用該符號進行計算或賦值給其他符號。它會自動根據SECTIONS命令內部所描述的輸出段的大小來計算當前的地址。

(3) 輸出段描述(output section description):

前面提到在SECTIONS命令中可以作輸出段描述,描述的格式如下:

section [address] [(type)] : [AT(lma)]

{

output-section-command

output-section-command

...

} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

很多附加選項是用不到的。其中的output-section-command又可以是符號賦值,輸入段描述,要直接包含的數(shù)據值,或者某一特定的輸出段關鍵字

?

*linker script 實例

==============================

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS {

??? . = 0xa3f00000;

??? __boot_start = .;

??? .start ALIGN(4) : {

??????? *(.text.start)

??? }


???

setup ALIGN(4) : {

??????? setup_block = .;

??????? *(.setup)

??????? setup_block_end = .;

??? }


??? .text ALIGN(4) : {

??????? *(.text)

??? }


??? .rodata ALIGN(4) : {

??????? *(.rodata)

??? }

??? .data ALIGN(4) : {

??????? *(.data)

??? }


??? .got ALIGN(4) : {

??????? *(.got)

??? }

??? __boot_end = .;


??? .bss ALIGN(16) : {

??????? bss_start = .;

??????? *(.bss)

??????? *(COMMON)

??????? bss_end = .;

??? }


??? .comment ALIGN(16) : {

??????? *(.comment)

??? }

??? stack_point = __boot_start + 0x00100000;

??? loader_size = __boot_end - __boot_start;

??? setup_size = setup_block_end - setup_block;

}

?

=============================??

在SECTIONS命令中的類似于下面的描述結構就是輸出段描述:

.start ALIGN(4) : {

??? *(.text.start)

}

.start為output section name,ALIGN(4)返回一個基于location counter(.)的4字節(jié)對齊的地址值。*(.text.start)是輸入段描述,*為通配符,意思是把所有被鏈接的object文件中的.text.start段都鏈接進這個名為.start的輸出段。

源文件中所標識的section及其屬性實際上就是對輸入段的描述,例如.text.start輸入段在源文件start.S中的代碼如下:

.section .text.start

.global _start

_start :

??? b start


--------------------------------------------------------------------------------
[推薦閱讀]
1.ARM學習報告002--GNU tool開發(fā)ARM程序及生成映象文件機理
2.Using ld, the GNU Linker

?

5、再議論 鏈接

?

1. 概論


每一個鏈接過程都由鏈接腳本(linker script, 一般以lds作為文件的后綴名)控制. 鏈接腳本主要用于規(guī)定如何把輸入文件內的section放入輸出文件內, 并控制輸出文件內各部分在程序地址空間內的布局. 但你也可以用連接命令做一些其他事情.

連接器有個默認的內置連接腳本, 可用ld --verbose查看. 連接選項-r和-N可以影響默認的連接腳本(如何影響?).

-T選項用以指定自己的鏈接腳本, 它將代替默認的連接腳本。你也可以使用<暗含的連接腳本>以增加自定義的鏈接命令.

以下沒有特殊說明,連接器指的是靜態(tài)連接器.


2. 基本概念


鏈接器把一個或多個輸入文件合成一個輸出文件.

輸入文件: 目標文件或鏈接腳本文件.
輸出文件: 目標文件或可執(zhí)行文件.

目標文件(包括可執(zhí)行文件)具有固定的格式, 在UNIX或GNU/Linux平臺下, 一般為ELF格式. 若想了解更多, 可參考 UNIX/Linux平臺可執(zhí)行文件格式分析

有時把輸入文件內的section稱為輸入section(input section), 把輸出文件內的section稱為輸出section(output sectin).

目標文件的每個section至少包含兩個信息: 名字和大小. 大部分section還包含與它相關聯(lián)的一塊數(shù)據, 稱為section contents(section內容). 一個section可被標記為“l(fā)oadable(可加載的)”或“allocatable(可分配的)”.

loadable section: 在輸出文件運行時, 相應的section內容將被載入進程地址空間中.

allocatable section: 內容為空的section可被標記為“可分配的”. 在輸出文件運行時, 在進程地址空間中空出大小同section指定大小的部分. 某些情況下, 這塊內存必須被置零.

如果一個section不是“可加載的”或“可分配的”, 那么該section通常包含了調試信息. 可用objdump -h命令查看相關信息.

每個“可加載的”或“可分配的”輸出section通常包含兩個地址: VMA(virtual memory address虛擬內存地址或程序地址空間地址)和LMA(load memory address加載內存地址或進程地址空間地址). 通常VMA和LMA是相同的.

在目標文件中, loadable或allocatable的輸出section有兩種地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是執(zhí)行輸出文件時section所在的地址, 而LMA是加載輸出文件時section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系統(tǒng)中, 經常存在加載地址和執(zhí)行地址不同的情況: 比如將輸出文件加載到開發(fā)板的flash中(由LMA指定), 而在運行時將位于flash中的輸出文件復制到SDRAM中(由VMA指定).

可這樣來理解VMA和LMA, 假設:
(1) .data section對應的VMA地址是0x08050000, 該section內包含了3個32位全局變量, i、j和k, 分別為1,2,3.
(2) .text section內包含由"printf( "j=%d ", j );"程序片段產生的代碼.

連接時指定.data section的VMA為0x08050000, 產生的printf指令是將地址為0x08050004處的4字節(jié)內容作為一個整數(shù)打印出來。

如果.data section的LMA為0x08050000,顯然結果是j=2
如果.data section的LMA為0x08050004,顯然結果是j=1

還可這樣理解LMA:
.text section內容的開始處包含如下兩條指令(intel i386指令是10字節(jié),每行對應5字節(jié)):

jmp 0x08048285
movl $0x1,%eax

如果.text section的LMA為0x08048280, 那么在進程地址空間內0x08048280處為“jmp 0x08048285”指令, 0x08048285處為movl $0x1,%eax指令. 假設某指令跳轉到地址0x08048280, 顯然它的執(zhí)行將導致%eax寄存器被賦值為1.

如果.text section的LMA為0x08048285, 那么在進程地址空間內0x08048285處為“jmp 0x08048285”指令, 0x0804828a處為movl $0x1,%eax指令. 假設某指令跳轉到地址0x08048285, 顯然它的執(zhí)行又跳轉到進程地址空間內0x08048285處, 造成死循環(huán).

符號(symbol): 每個目標文件都有符號表(SYMBOL TABLE), 包含已定義的符號(對應全局變量和static變量和定義的函數(shù)的名字)和未定義符號(未定義的函數(shù)的名字和引用但沒定義的符號)信息.

符號值: 每個符號對應一個地址, 即符號值(這與c程序內變量的值不一樣, 某種情況下可以把它看成變量的地址). 可用nm命令查看它們. (nm的使用方法可參考本blog的 GNU binutils筆記)


3. 腳本格式

鏈接腳本由一系列命令組成, 每個命令由一個關鍵字(一般在其后緊跟相關參數(shù))或一條對符號的賦值語句組成. 命令由分號‘;’分隔開.

文件名或格式名內如果包含分號';'或其他分隔符, 則要用引號‘"’將名字全稱引用起來. 無法處理含引號的文件名.
/* */之間的是注釋。


4. 簡單例子


在介紹鏈接描述文件的命令之前, 先看看下述的簡單例子:

以下腳本將輸出文件的text section定位在0x10000, data section定位在0x8000000:

SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}

解釋一下上述的例子:
. = 0x10000 : 把定位器符號置為0x10000 (若不指定, 則該符號的初始值為0).

.text : { *(.text) } : 將所有(*符號代表任意輸入文件)輸入文件的.text section合并成一個.text section, 該section的地址由定位器符號的值指定, 即0x10000.

. = 0x8000000 :把定位器符號置為0x8000000
.data : { *(.data) } : 將所有輸入文件的.text section合并成一個.data section, 該section的地址被置為0x8000000.

.bss : { *(.bss) } : 將所有輸入文件的.bss section合并成一個.bss section,該section的地址被置為0x8000000+.data section的大小.

連接器每讀完一個section描述后, 將定位器符號的值*增加*該section的大小. 注意: 此處沒有考慮對齊約束.


5. 簡單腳本命令


- 1 -

ENTRY(SYMBOL): 將符號SYMBOL的值設置成入口地址。

入口地址(entry point): 進程執(zhí)行的第一條用戶空間的指令在進程地址空間的地址)

ld有多種方法設置進程入口地址, 按一下順序: (編號越前, 優(yōu)先級越高)
1, ld命令行的-e選項
2, 連接腳本的ENTRY(SYMBOL)命令
3, 如果定義了start符號, 使用start符號值
4, 如果存在.text section, 使用.text section的第一字節(jié)的位置值
5, 使用值0

- 2 -
INCLUDE filename : 包含其他名為filename的鏈接腳本

相當于c程序內的的#include指令, 用以包含另一個鏈接腳本.

腳本搜索路徑由-L選項指定. INCLUDE指令可以嵌套使用, 最大深度為10. 即: 文件1內INCLUDE文件2, 文件2內INCLUDE文件3... , 文件10內INCLUDE文件11. 那么文件11內不能再出現(xiàn) INCLUDE指令了.

- 3 -
INPUT(files): 將括號內的文件做為鏈接過程的輸入文件

ld首先在當前目錄下尋找該文件, 如果沒找到, 則在由-L指定的搜索路徑下搜索. file可以為 -lfile形式,就象命令行的-l選項一樣. 如果該命令出現(xiàn)在暗含的腳本內, 則該命令內的file在鏈接過程中的順序由該暗含的腳本在命令行內的順序決定.

- 4 -
GROUP(files) : 指定需要重復搜索符號定義的多個輸入文件

file必須是庫文件, 且file文件作為一組被ld重復掃描,直到不在有新的未定義的引用出現(xiàn)。

- 5 -
OUTPUT(FILENAME) : 定義輸出文件的名字

同ld的-o選項, 不過-o選項的優(yōu)先級更高. 所以它可以用來定義默認的輸出文件名. 如a.out

- 6 -
SEARCH_DIR(PATH) :定義搜索路徑,

同ld的-L選項, 不過由-L指定的路徑要比它定義的優(yōu)先被搜索。

- 7 -
STARTUP(filename) : 指定filename為第一個輸入文件

在鏈接過程中, 每個輸入文件是有順序的. 此命令設置文件filename為第一個輸入文件。

- 8 -
OUTPUT_FORMAT(BFDNAME) : 設置輸出文件使用的BFD格式

同ld選項-o format BFDNAME, 不過ld選項優(yōu)先級更高.

- 9 -
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定義三種輸出文件的格式(大小端)

若有命令行選項-EB, 則使用第2個BFD格式; 若有命令行選項-EL,則使用第3個BFD格式.否則默認選第一個BFD格式.

TARGET(BFDNAME):設置輸入文件的BFD格式

同ld選項-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 則最用一個TARGET命令設置的BFD格式將被作為輸出文件的BFD格式.

另外還有一些:
ASSERT(EXP, MESSAGE):如果EXP不為真,終止連接過程

EXTERN(SYMBOL SYMBOL ...):在輸出文件中增加未定義的符號,如同連接器選項-u

FORCE_COMMON_ALLOCATION:為common symbol(通用符號)分配空間,即使用了-r連接選項也為其分配

NOCROSSREFS(SECTION SECTION ...):檢查列出的輸出section,如果發(fā)現(xiàn)他們之間有相互引用,則報錯。對于某些系統(tǒng),特別是內存較緊張的嵌入式系統(tǒng),某些section是不能同時存在內存中的,所以他們之間不能相互引用。

OUTPUT_ARCH(BFDARCH):設置輸出文件的machine architecture(體系結構),BFDARCH為被BFD庫使用的名字之一。可以用命令objdump -f查看。

可通過 man -S 1 ld查看ld的聯(lián)機幫助, 里面也包括了對這些命令的介紹.


6. 對符號的賦值

在目標文件內定義的符號可以在鏈接腳本內被賦值. (注意和C語言中賦值的不同!) 此時該符號被定義為全局的. 每個符號都對應了一個地址, 此處的賦值是更改這個符號對應的地址.

e.g. 通過下面的程序查看變量a的地址:
/* a.c */
#include <stdio.h>
int a = 100;
int main(void)
{
??? printf( "&a=0x%p ", &a );
??? return 0;
}

/* a.lds */
a = 3;

$ gcc -Wall -o a-without-lds a.c
&a = 0x8049598

$ gcc -Wall -o a-with-lds a.c a.lds
&a = 0x3

注意: 對符號的賦值只對全局變量起作用!

一些簡單的賦值語句
能使用任何c語言內的賦值操作:

SYMBOL = EXPRESSION ;
SYMBOL += EXPRESSION ;
SYMBOL -= EXPRESSION ;
SYMBOL *= EXPRESSION ;
SYMBOL /= EXPRESSION ;
SYMBOL <<= EXPRESSION ;
SYMBOL >>= EXPRESSION ;
SYMBOL &= EXPRESSION ;
SYMBOL |= EXPRESSION ;

除了第一類表達式外, 使用其他表達式需要SYMBOL被定義于某目標文件。
. 是一個特殊的符號,它是定位器,一個位置指針,指向程序地址空間內的某位置(或某section內的偏移,如果它在SECTIONS命令內的某section描述內),該符號只能在SECTIONS命令內使用。
注意:賦值語句包含4個語法元素:符號名、操作符、表達式、分號;一個也不能少。
被賦值后,符號所屬的section被設值為表達式EXPRESSION所屬的SECTION(參看11. 腳本內的表達式)
賦值語句可以出現(xiàn)在連接腳本的三處地方:SECTIONS命令內,SECTIONS命令內的section描述內和全局位置;如下,
floating_point = 0; /* 全局位置 */
SECTIONS
{
.text :
{
*(.text)
_etext = .; /* section描述內 */
}
_bdata = (. + 3) & ~ 4; /* SECTIONS命令內 */
.data : { *(.data) }
}

PROVIDE關鍵字
該關鍵字用于定義這類符號:在目標文件內被引用,但沒有在任何目標文件內被定義的符號。
例子:
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}
當目標文件內引用了etext符號,確沒有定義它時,etext符號對應的地址被定義為.text section之后的第一個字節(jié)的地址。


7. SECTIONS命令

SECTIONS命令告訴ld如何把輸入文件的sections映射到輸出文件的各個section: 如何將輸入section合為輸出section; 如何把輸出section放入程序地址空間(VMA)和進程地址空間(LMA).該命令格式如下:

SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
...
}

SECTION-COMMAND有四種:
(1) ENTRY命令
(2) 符號賦值語句
(3) 一個輸出section的描述(output section description)
(4) 一個section疊加描述(overlay description)

如果整個連接腳本內沒有SECTIONS命令, 那么ld將所有同名輸入section合成為一個輸出section內, 各輸入section的順序為它們被連接器發(fā)現(xiàn)的順序.

如果某輸入section沒有在SECTIONS命令中提到, 那么該section將被直接拷貝成輸出section。

輸出section描述
輸出section描述具有如下格式:

SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]

[ ]內的內容為可選選項, 一般不需要.
SECTION:section名字
SECTION左右的空白、圓括號、冒號是必須的,換行符和其他空格是可選的。
每個OUTPUT-SECTION-COMMAND為以下四種之一,
符號賦值語句
一個輸入section描述
直接包含的數(shù)據值
一個特殊的輸出section關鍵字

輸出section名字(SECTION):
輸出section名字必須符合輸出文件格式要求,比如:a.out格式的文件只允許存在.text、.data和.bss section名。而有的格式只允許存在數(shù)字名字,那么此時應該用引號將所有名字內的數(shù)字組合在一起;另外,還有一些格式允許任何序列的字符存在于 section名字內,此時如果名字內包含特殊字符(比如空格、逗號等),那么需要用引號將其組合在一起。

輸出section地址(ADDRESS):
ADDRESS是一個表達式,它的值用于設置VMA。如果沒有該選項且有REGION選項,那么連接器將根據REGION設置VMA;如果也沒有 REGION選項,那么連接器將根據定位符號‘.’的值設置該section的VMA,將定位符號的值調整到滿足輸出section對齊要求后的值,輸出 section的對齊要求為:該輸出section描述內用到的所有輸入section的對齊要求中最嚴格的。
例子:
.text . : { *(.text) }

.text : { *(.text) }
這兩個描述是截然不同的,第一個將.text section的VMA設置為定位符號的值,而第二個則是設置成定位符號的修調值,滿足對齊要求后的。
ADDRESS可以是一個任意表達式,比如ALIGN(0x10)這將把該section的VMA設置成定位符號的修調值,滿足16字節(jié)對齊后的。
注意:設置ADDRESS值,將更改定位符號的值。

輸入section描述:
最常見的輸出section描述命令是輸入section描述。
輸入section描述是最基本的連接腳本描述。
輸入section描述基礎:
基本語法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)
FILENAME文件名,可以是一個特定的文件的名字,也可以是一個字符串模式。
SECTION名字,可以是一個特定的section名字,也可以是一個字符串模式
例子是最能說明問題的,
*(.text) :表示所有輸入文件的.text section
(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有輸入文件的.ctors section。
data.o(.data) :表示data.o文件的.data section
data.o :表示data.o文件的所有section
*(.text .data) :表示所有文件的.text section和.data section,順序是:第一個文件的.text section,第一個文件的.data section,第二個文件的.text section,第二個文件的.data section,...
*(.text) *(.data) :表示所有文件的.text section和.data section,順序是:第一個文件的.text section,第二個文件的.text section,...,最后一個文件的.text section,第一個文件的.data section,第二個文件的.data section,...,最后一個文件的.data section
下面看連接器是如何找到對應的文件的。
當FILENAME是一個特定的文件名時,連接器會查看它是否在連接命令行內出現(xiàn)或在INPUT命令中出現(xiàn)。
當FILENAME是一個字符串模式時,連接器僅僅只查看它是否在連接命令行內出現(xiàn)。
注意:如果連接器發(fā)現(xiàn)某文件在INPUT命令內出現(xiàn),那么它會在-L指定的路徑內搜尋該文件。

字符串模式內可存在以下通配符:
* :表示任意多個字符
? :表示任意一個字符
[CHARS] :表示任意一個CHARS內的字符,可用-號表示范圍,如:a-z
:表示引用下一個緊跟的字符

在文件名內,通配符不匹配文件夾分隔符/,但當字符串模式僅包含通配符*時除外。
任何一個文件的任意section只能在SECTIONS命令內出現(xiàn)一次。看如下例子,
SECTIONS {
.data : { *(.data) }
.data1 : { data.o(.data) }
}
data.o文件的.data section在第一個OUTPUT-SECTION-COMMAND命令內被使用了,那么在第二個OUTPUT-SECTION-COMMAND命令內將不會再被使用,也就是說即使連接器不報錯,輸出文件的.data1 section的內容也是空的。
再次強調:連接器依次掃描每個OUTPUT-SECTION-COMMAND命令內的文件名,任何一個文件的任何一個section都只能使用一次。
讀者可以用-M連接命令選項來產生一個map文件,它包含了所有輸入section到輸出section的組合信息。
再看個例子,
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
這個例子中說明,所有文件的輸入.text section組成輸出.text section;所有以大寫字母開頭的文件的.data section組成輸出.DATA section,其他文件的.data section組成輸出.data section;所有文件的輸入.bss section組成輸出.bss section。
可以用SORT()關鍵字對滿足字符串模式的所有名字進行遞增排序,如SORT(.text*)。
通用符號(common symbol)的輸入section:
在許多目標文件格式中,通用符號并沒有占用一個section。連接器認為:輸入文件的所有通用符號在名為COMMON的section內。
例子,
.bss { *(.bss) *(COMMON) }
這個例子中將所有輸入文件的所有通用符號放入輸出.bss section內。可以看到COMMOM section的使用方法跟其他section的使用方法是一樣的。
有些目標文件格式把通用符號分成幾類。例如,在MIPS elf目標文件格式中,把通用符號分成standard common symbols(標準通用符號)和small common symbols(微通用符號,不知道這么譯對不對?),此時連接器認為所有standard common symbols在COMMON section內,而small common symbols在.scommon section內。
在一些以前的連接腳本內可以看見[COMMON],相當于*(COMMON),不建議繼續(xù)使用這種陳舊的方式。
輸入section和垃圾回收:
在連接命令行內使用了選項--gc-sections后,連接器可能將某些它認為沒用的section過濾掉,此時就有必要強制連接器保留一些特定的 section,可用KEEP()關鍵字達此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))
最后看個簡單的輸入section相關例子:
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
本例中,將all.o文件的所有section和foo.o文件的所有(一個文件內可以有多個同名section).input1 section依次放入輸出outputa section內,該section的VMA是0x10000;將foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入輸出outputb section內,該section的VMA是當前定位器符號的修調值(對齊后);將其他文件(非all.o、foo.o、foo1.o)文件的. input1 section和.input2 section放入輸出outputc section內。

在輸出section存放數(shù)據命令:
能夠顯示地在輸出section內填入你想要填入的信息(這樣是不是可以自己通過連接腳本寫程序?當然是簡單的程序)。
BYTE(EXPRESSION) 1 字節(jié)
SHORT(EXPRESSION) 2 字節(jié)
LOGN(EXPRESSION) 4 字節(jié)
QUAD(EXPRESSION) 8 字節(jié)
SQUAD(EXPRESSION) 64位處理器的代碼時,8 字節(jié)
輸出文件的字節(jié)順序big endianness 或little endianness,可以由輸出目標文件的格式決定;如果輸出目標文件的格式不能決定字節(jié)順序,那么字節(jié)順序與第一個輸入文件的字節(jié)順序相同。
如:BYTE(1)、LANG(addr)。
注意,這些命令只能放在輸出section描述內,其他地方不行。
錯誤:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
正確:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
在當前輸出section內可能存在未描述的存儲區(qū)域(比如由于對齊造成的空隙),可以用FILL(EXPRESSION)命令決定這些存儲區(qū)域的內容, EXPRESSION的前兩字節(jié)有效,這兩字節(jié)在必要時可以重復被使用以填充這類存儲區(qū)域。如FILE(0x9090)。在輸出section描述中可以有=FILEEXP屬性,它的作用如同F(xiàn)ILE()命令,但是FILE命令只作用于該FILE指令之后的section區(qū)域,而=FILEEXP屬性作用于整個輸出section區(qū)域,且FILE命令的優(yōu)先級更高!!!

輸出section內命令的關鍵字:
CREATE_OBJECT_SYMBOLS :為每個輸入文件建立一個符號,符號名為輸入文件的名字。每個符號所在的section是出現(xiàn)該關鍵字的section。
CONSTRUCTORS :與c++內的(全局對象的)構造函數(shù)和(全局對像的)析構函數(shù)相關,下面將它們簡稱為全局構造和全局析構。
對于a.out目標文件格式,連接器用一些不尋常的方法實現(xiàn)c++的全局構造和全局析構。當連接器生成的目標文件格式不支持任意section名字時,比如說ECOFF、XCOFF格式,連接器將通過名字來識別全局構造和全局析構,對于這些文件格式,連接器把與全局構造和全局析構的相關信息放入出現(xiàn) CONSTRUCTORS關鍵字的輸出section內。
符號__CTORS_LIST__表示全局構造信息的的開始處,__CTORS_END__表示全局構造信息的結束處。
符號__DTORS_LIST__表示全局構造信息的的開始處,__DTORS_END__表示全局構造信息的結束處。
這兩塊信息的開始處是一字長的信息,表示該塊信息有多少項數(shù)據,然后以值為零的一字長數(shù)據結束。
一般來說,GNU C++在函數(shù)__main內安排全局構造代碼的運行,而__main函數(shù)被初始化代碼(在main函數(shù)調用之前執(zhí)行)調用。是不是對于某些目標文件格式才這樣???
對于支持任意section名的目標文件格式,比如COFF、ELF格式,GNU C++將全局構造和全局析構信息分別放入.ctors section和.dtors section內,然后在連接腳本內加入如下,
__CTOR_LIST__ = .;
LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
*(.ctors)
LONG(0)
__CTOR_END__ = .;
__DTOR_LIST__ = .;
LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
*(.dtors)
LONG(0)
__DTOR_END__ = .;
如果使用GNU C++提供的初始化優(yōu)先級支持(它能控制每個全局構造函數(shù)調用的先后順序),那么請在連接腳本內把CONSTRUCTORS替換成SORT (CONSTRUCTS),把*(.ctors)換成*(SORT(.ctors)),把*(.dtors)換成*(SORT(.dtors))。一般來說,默認的連接腳本已作好的這些工作。

輸出section的丟棄:
例子,.foo { *(.foo) },如果沒有任何一個輸入文件包含.foo section,那么連接器將不會創(chuàng)建.foo輸出section。但是如果在這些輸出section描述內包含了非輸入section描述命令(如符號賦值語句),那么連接器將總是創(chuàng)建該輸出section。
有一個特殊的輸出section,名為/DISCARD/,被該section引用的任何輸入section將不會出現(xiàn)在輸出文件內,這就是DISCARD的意思吧。如果/DISCARD/ section被它自己引用呢?想想看。

輸出section屬性:
終于講到這里了,呵呵。
我們再回顧以下輸出section描述的文法:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
前面我們?yōu)g覽了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相關信息,下面我們將瀏覽其他屬性。

TYPE :每個輸出section都有一個類型,如果沒有指定TYPE類型,那么連接器根據輸出section引用的輸入section的類型設置該輸出section的類型。它可以為以下五種值,
NOLOAD :該section在程序運行時,不被載入內存。
DSECT,COPY,INFO,OVERLAY :這些類型很少被使用,為了向后兼容才被保留下來。這種類型的section必須被標記為“不可加載的”,以便在程序運行不為它們分配內存。

輸出section的LMA :默認情況下,LMA等于VMA,但可以通過關鍵字AT()指定LMA。
用關鍵字AT()指定,括號內包含表達式,表達式的值用于設置LMA。如果不用AT()關鍵字,那么可用AT>LMA_REGION表達式設置指定該section加載地址的范圍。
這個屬性主要用于構件ROM境象。
例子,
SECTIONS
{
.text 0x1000 : { *(.text) _etext = . ; }
.mdata 0x2000 :
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
程序如下,
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;

/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
*dst++ = *src++;
}

/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0;

此程序將處于ROM內的已初始化數(shù)據拷貝到該數(shù)據應在的位置(VMA地址),并將為初始化數(shù)據置零。
讀者應該認真的自己分析以上連接腳本和程序的作用。

輸出section區(qū)域:可以將輸出section放入預先定義的內存區(qū)域內,例子,
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }

輸出section所在的程序段:可以將輸出section放入預先定義的程序段(program segment)內。如果某個輸出section設置了它所在的一個或多個程序段,那么接下來定義的輸出section的默認程序段與該輸出 section的相同。除非再次顯示地指定。例子,
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
可以通過:NONE指定連接器不把該section放入任何程序段內。詳情請查看PHDRS命令

輸出section的填充模版:這個在前面提到過,任何輸出section描述內的未指定的內存區(qū)域,連接器用該模版填充該區(qū)域。用法:=FILEEXP,前兩字節(jié)有效,當區(qū)域大于兩字節(jié)時,重復使用這兩字節(jié)以將其填滿。例子,
SECTIONS { .text : { *(.text) } =0x9090 }

覆蓋圖(overlay)描述:
覆蓋圖描述使兩個或多個不同的section占用同一塊程序地址空間。覆蓋圖管理代碼負責將section的拷入和拷出。考慮這種情況,當某存儲塊的訪問速度比其他存儲塊要快時,那么如果將section拷到該存儲塊來執(zhí)行或訪問,那么速度將會有所提高,覆蓋圖描述就很適合這種情形。文法如下,
SECTIONS {
...

OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]
{
SECNAME1
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [:PHDR...] [=FILL]
SECNAME2
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
...
} [:PHDR...] [=FILL]
...
} [>REGION] [:PHDR...] [=FILL]

...
}
由以上文法可以看出,同一覆蓋圖內的section具有相同的VMA。SECNAME2的LMA為SECTNAME1的LMA加上SECNAME1的大小,同理計算SECNAME2,3,4...的LMA。SECNAME1的LMA由LDADDR決定,如果它沒有被指定,那么由START決定,如果它也沒有被指定,那么由當前定位符號的值決定。
NOCROSSREFS關鍵字指定各section之間不能交叉引用,否則報錯。
對于OVERLAY描述的每個section,連接器將定義兩個符號__load_start_SECNAME和__load_stop_SECNAME,這兩個符號的值分別代表SECNAME section的LMA地址的開始和結束。
連接器處理完OVERLAY描述語句后,將定位符號的值加上所有覆蓋圖內section大小的最大值。
看個例子吧,
SECTIONS{
...

OVERLAY 0x1000 : AT (0x4000)
{
.text0 { o1/*.o(.text) }
.text1 { o2/*.o(.text) }
}
...
}
.text0 section和.text1 section的VMA地址是0x1000,.text0 section加載于地址0x4000,.text1 section緊跟在其后。
程序代碼,拷貝.text1 section代碼,
extern char __load_start_text1, __load_stop_text1;
memcpy ((char *) 0x1000, &__load_start_text1,
&__load_stop_text1 - &__load_start_text1);


8. 內存區(qū)域命令
---------------

注意:以下存儲區(qū)域指的是在程序地址空間內的。
在默認情形下,連接器可以為section分配任意位置的存儲區(qū)域。你也可以用MEMORY命令定義存儲區(qū)域,并通過輸出section描述的> REGION屬性顯示地將該輸出section限定于某塊存儲區(qū)域,當存儲區(qū)域大小不能滿足要求時,連接器會報告該錯誤。
MEMORY命令的文法如下,
MEMORY {
NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
...
}
NAME :存儲區(qū)域的名字,這個名字可以與符號名、文件名、section名重復,因為它處于一個獨立的名字空間。
ATTR :定義該存儲區(qū)域的屬性,在講述SECTIONS命令時提到,當某輸入section沒有在SECTIONS命令內引用時,連接器會把該輸入 section直接拷貝成輸出section,然后將該輸出section放入內存區(qū)域內。如果設置了內存區(qū)域設置了ATTR屬性,那么該區(qū)域只接受滿足該屬性的section(怎么判斷該section是否滿足?輸出section描述內好象沒有記錄該section的讀寫執(zhí)行屬性)。ATTR屬性內可以出現(xiàn)以下7個字符,
R 只讀section
W 讀/寫section
X 可執(zhí)行section
A ‘可分配的’section
I 初始化了的section
L 同I
! 不滿足該字符之后的任何一個屬性的section
ORIGIN :關鍵字,區(qū)域的開始地址,可簡寫成org或o
LENGTH :關鍵字,區(qū)域的大小,可簡寫成len或l

例子,
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K
ram (!rx) : org = 0x40000000, l = 4M
}
此例中,把在SECTIONS命令內*未*引用的且具有讀屬性或寫屬性的輸入section放入rom區(qū)域內,把其他未引用的輸入section放入 ram。如果某輸出section要被放入某內存區(qū)域內,而該輸出section又沒有指明ADDRESS屬性,那么連接器將該輸出section放在該區(qū)域內下一個能使用位置。


9. PHDRS命令
------------

該命令僅在產生ELF目標文件時有效。
ELF目標文件格式用program headers程序頭(程序頭內包含一個或多個segment程序段描述)來描述程序如何被載入內存。可以用objdump -p命令查看。
當在本地ELF系統(tǒng)運行ELF目標文件格式的程序時,系統(tǒng)加載器通過讀取程序頭信息以知道如何將程序加載到內存。要了解系統(tǒng)加載器如何解析程序頭,請參考ELF ABI文檔。
在連接腳本內不指定PHDRS命令時,連接器能夠很好的創(chuàng)建程序頭,但是有時需要更精確的描述程序頭,那么PAHDRS命令就派上用場了。
注意:一旦在連接腳本內使用了PHDRS命令,那么連接器**僅會**創(chuàng)建PHDRS命令指定的信息,所以使用時須謹慎。
PHDRS命令文法如下,
PHDRS
{
NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
[ FLAGS ( FLAGS ) ] ;
}
其中FILEHDR、PHDRS、AT、FLAGS為關鍵字。
NAME :為程序段名,此名字可以與符號名、section名、文件名重復,因為它在一個獨立的名字空間內。此名字只能在SECTIONS命令內使用。
一個程序段可以由多個‘可加載’的section組成。通過輸出section描述的屬性:PHDRS可以將輸出section加入一個程序段,: PHDRS中的PHDRS為程序段名。在一個輸出section描述內可以多次使用:PHDRS命令,也即可以將一個section加入多個程序段。
如果在一個輸出section描述內指定了:PHDRS屬性,那么其后的輸出section描述將默認使用該屬性,除非它也定義了:PHDRS屬性。顯然當多個輸出section屬于同一程序段時可簡化書寫。
在TYPE屬性后存在FILEHDR關鍵字,表示該段包含ELF文件頭信息;存在PHDRS關鍵字,表示該段包含ELF程序頭信息。
TYPE可以是以下八種形式,
PT_NULL 0
表示未被使用的程序段
PT_LOAD 1
表示該程序段在程序運行時應該被加載
PT_DYNAMIC 2
表示該程序段包含動態(tài)連接信息
PT_INTERP 3
表示該程序段內包含程序加載器的名字,在linux下常見的程序加載器是ld-linux.so.2
PT_NOTE 4
表示該程序段內包含程序的說明信息
PT_SHLIB 5
一個保留的程序頭類型,沒有在ELF ABI文檔內定義
PT_PHDR 6
表示該程序段包含程序頭信息。
EXPRESSION 表達式值
以上每個類型都對應一個數(shù)字,該表達式定義一個用戶自定的程序頭。
AT(ADDRESS)屬性定義該程序段的加載位置(LMA),該屬性將**覆蓋**該程序段內的section的AT()屬性。
默認情況下,連接器會根據該程序段包含的section的屬性(什么屬性?好象在輸出section描述內沒有看到)設置FLAGS標志,該標志用于設置程序段描述的p_flags域。
下面看一個典型的PHDRS設置,
PHDRS
{
headers PT_PHDR PHDRS ;
interp PT_INTERP ;
text PT_LOAD FILEHDR PHDRS ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
}
SECTIONS
{
. = SIZEOF_HEADERS;
.interp : { *(.interp) } :text :interp
.text : { *(.text) } :text
.rodata : { *(.rodata) } /* defaults to :text */
...
. = . + 0x1000; /* move to a new page in memory */
.data : { *(.data) } :data
.dynamic : { *(.dynamic) } :data :dynamic
...
}


10. 版本號命令
--------------

當使用ELF目標文件格式時,連接器支持帶版本號的符號。
讀者可以發(fā)現(xiàn)僅僅在共享庫中,符號的版本號屬性才有意義。
動態(tài)加載器使用符號的版本號為應用程序選擇共享庫內的一個函數(shù)的特定實現(xiàn)版本。
可以在連接腳本內直接使用版本號命令,也可以將版本號命令實現(xiàn)于一個特定版本號描述文件(用連接選項--version-script指定該文件)。
該命令的文法如下,
VERSION { version-script-commands }
以下內容直接拷貝于以前的文檔,
===================== 開始 ==================================
內容簡介
---------
0 前提
1 帶版本號的符號的定義
2 連接到帶版本的符號
3 GNU擴充
4 我的疑問
5 英文搜索關鍵字
6 我的參考


0. 前提

-- 只限于ELF文件格式
-- 以下討論用gcc

1. 帶版本號的符號的定義(共享庫內)

文件b.c內容如下,
int old_true()
{
return 1;
}

int new_true()
{
return 2;
}

寫連接器的版本控制腳本,本例中為b.lds,內容如下
VER1.0{
new_true;
};
VER2.0{
};

$gcc -c b.c
$gcc -shared -Wl,--version-script=b.lds -o libb.so b.o

可以在{}內填入要綁定的符號,本例中new_true符號就與VER1.0綁定了。
那么如果有一個應用程序連接到該庫的new_true符號,那么它連接的就是VER1.0版本的new_true符號

如果把b.lds更改為,
VER1.0{
};
VER2.0{
new_true;
};

然后在生成libb.so文件,在運行那個連接到VER1.0版本的new_true符號的應用程序,可以發(fā)現(xiàn)該應用程序不能運行了,
因為庫內沒有VER1.0版本的new_true,只有VER2.0版本的new_true。


2. 連接到帶版本的符號
寫一個簡單的應用(名為app)連接到libb.so,應用符號new_true
假設libb.so的版本控制文件為,
VER1.0{
};
VER2.0{
new_true;
};

$ nm app | grep new_true
U new_true@@VER1.0
$
用nm命令發(fā)現(xiàn)app連接到VER1.0版本的new_true

3. GNU的擴充
它允許在程序文件內綁定 *符號* 到 *帶版本號的別名符號*

文件b.c內容如下,
int old_true()
{
return 1;
}

int new_true()
{
return 2;
}
__asm__( ".symver old_true,true@VER1.0" );
__asm__( ".symver new_true,true@@VER2.0" );


其中,帶版本號的別名符號是true,其默認的版本號為VER2.0

供連接器用的版本控制腳本b.lds內容如下,
VER1.0{
};
VER2.0{
};

版本控制文件內必須包含版本VER1.0和版本VER2.0的定義,因為在b.c文件內有對他們的引用

****** 假定libb.so與app.c在同一目錄下 ********

以下應用程序app.c連接到該庫,
int true();
int main()
{
printf( "%d ", true );
}

$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
2
$ nm app | grep true
U true@@VER2.0
$

很明顯,程序app使用的是VER2.0版本的別名符號true,如果在b.c內沒有指明別名符號true的默認版本,
那么gcc app.c libb.so將出現(xiàn)連接錯誤,提示true沒有定義。

也可以在程序內指定特定版本的別名符號true,程序如下,
__asm__( ".symver true,true@VER1.0" );
int true();
int main()
{
printf( "%d ", true );
}

$ gcc app.c libb.so
$ LD_LIBRARY_PATH=. ./app
1
$ nm app | grep true
U true@VER1.0
$

顯然,連接到了版本號為VER1.0的別名符號true。其中只有一個@表示,該版本不是默認的版本




我的疑問:
版本控制腳本文件中,各版本號節(jié)點之間的依賴關系


英文搜索關鍵字:
.symver
versioned symbol
version a shared library

參考:
info ld, Scripts node
===================== 結束 ==================================


11. 表達式
----------

表達式的文法與C語言的表達式文法一致,表達式的值都是整型,如果ld的運行主機和生成文件的目標機都是32位,則表達式是32位數(shù)據,否則是64位數(shù)據。
能夠在表達式內使用符號的值,設置符號的值。
下面看六項表達式相關內容,

常表達式:
_fourk_1 = 4K; /* K、M單位 */
_fourk_2 = 4096; /* 整數(shù) */
_fourk_3 = 0x1000; /* 16 進位 */
_fourk_4 = 01000; /* 8 進位 */
1K=1024 1M=1024*1024
符號名:
沒有被引號""包圍的符號,以字母、下劃線或'.'開頭,可包含字母、下劃線、'.'和'-'。當符號名被引號包圍時,符號名可以與關鍵字相同。如,
"SECTION"=9
"with a space" = "also with a space" + 10;
定位符號'.':
只在SECTIONS命令內有效,代表一個程序地址空間內的地址。
注意:當定位符用在SECTIONS命令的輸出section描述內時,它代表的是該section的當前**偏移**,而不是程序地址空間的絕對地址。
先看個例子,
SECTIONS
{
output :
{
file1(.text)
. = . + 1000;
file2(.text)
. += 1000;
file3(.text)
} = 0x1234;
}
其中由于對定位符的賦值而產生的空隙由0x1234填充。其他的內容應該容易理解吧。
再看個例子,
SECTIONS
{
. = 0x100
.text: {
*(.text)
. = 0x200
}
. = 0x500
.data: {
*(.data)
. += 0x600
}
} .text section在程序地址空間的開始位置是0x
表達式的操作符:
與C語言一致。
優(yōu)先級 結合順序 操作符
1 left ! - ~ (1)
2 left * / %
3 left + -
4 left >> <<
5 left == != > < <= >=
6 left &
7 left |
8 left &&
9 left ||
10 right ? :
11 right &= += -= *= /= (2)
(1)表示前綴符,(2)表示賦值符。
表達式的計算:
連接器延遲計算大部分表達式的值。
但是,對待與連接過程緊密相關的表達式,連接器會立即計算表達式,如果不能計算則報錯。比如,對于section的VMA地址、內存區(qū)域塊的開始地址和大小,與其相關的表達式應該立即被計算。
例子,
SECTIONS
{
.text 9+this_isnt_constant :
{ *(.text) }
}
這個例子中,9+this_isnt_constant表達式的值用于設置.text section的VMA地址,因此需要立即運算,但是由于this_isnt_constant變量的值不確定,所以此時連接器無法確立表達式的值,此時連接器會報錯。
相對值與絕對值:
在輸出section描述內的表達式,連接器取其相對值,相對與該section的開始位置的偏移
在SECTIONS命令內且非輸出section描述內的表達式,連接器取其絕對值
通過ABSOLUTE關鍵字可以將相對值轉化成絕對值,即在原來值的基礎上加上表達式所在section的VMA值。
例子,
SECTIONS
{
.data : { *(.data) _edata = ABSOLUTE(.); }
}
該例子中,_edata符號的值是.data section的末尾位置(絕對值,在程序地址空間內)。
內建函數(shù):
ABSOLUTE(EXP) :轉換成絕對值
ADDR(SECTION) :返回某section的VMA值。
ALIGN(EXP) :返回定位符'.'的修調值,對齊后的值,(. + EXP - 1) & ~(EXP - 1)
BLOCK(EXP) :如同ALIGN(EXP),為了向前兼容。
DEFINED(SYMBOL) :如果符號SYMBOL在全局符號表內,且被定義了,那么返回1,否則返回0。例子,
SECTIONS { ...
.text : {
begin = DEFINED(begin) ? begin : . ;
...
}
...
}
LOADADDR(SECTION) :返回三SECTION的LMA
MAX(EXP1,EXP2) :返回大者
MIN(EXP1,EXP2) :返回小者
NEXT(EXP) :返回下一個能被使用的地址,該地址是EXP的倍數(shù),類似于ALIGN(EXP)。除非使用了MEMORY命令定義了一些非連續(xù)的內存塊,否則NEXT(EXP)與ALIGH(EXP)一定相同。
SIZEOF(SECTION) :返回SECTION的大小。當SECTION沒有被分配時,即此時SECTION的大小還不能確定時,連接器會報錯。
SIZEOF_HEADERS :
sizeof_headers :返回輸出文件的文件頭大小(還是程序頭大小),用以確定第一個section的開始地址(在文件內)。???


12. 暗含的連接腳本


輸入文件可以是目標文件,也可以是連接腳本,此時的連接腳本被稱為 暗含的連接腳本
如果連接器不認識某個輸入文件,那么該文件被當作連接腳本被解析。更進一步,如果發(fā)現(xiàn)它的格式又不是連接腳本的格式,那么連接器報錯。
一個暗含的連接腳本不會替換默認的連接腳本,僅僅是增加新的連接而已。
一般來說,暗含的連接腳本符號分配命令,或INPUT、GROUP、VERSION命令。
在連接命令行中,每個輸入文件的順序都被固定好了,暗含的連接腳本在連接命令行內占住一個位置,這個位置決定了由該連接腳本指定的輸入文件在連接過程中的順序。
典型的暗含的連接腳本是libc.so文件,在GNU/linux內一般存在/usr/lib目錄下。


References


1, gnu ld在線手冊

2, 程序的鏈接和裝入及Linux下動態(tài)鏈接的實現(xiàn)

3, UNIX/Linux平臺可執(zhí)行文件格式分析

4, John R. Levine.《Linkers & Loaders》

總結

以上是生活随笔為你收集整理的linux 可执行文件的分析(gcc GUN BUILEIN)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

亚洲精品乱码白浆高清久久久久久 | 久久69精品 | 超碰在线观看97 | 午夜体验区 | 开心激情久久 | 亚洲一区二区高潮无套美女 | 伊人午夜| 中文字幕在线观看第二页 | 国产精品久久久久久久久久免费 | 日韩免费视频在线观看 | 久久草av| 午夜电影久久 | www视频免费在线观看 | 欧美中文字幕久久 | 日本黄色a级大片 | 欧美 高跟鞋交 xxxxhd | 精品美女国产在线 | 国内揄拍国产精品 | 成人9ⅰ免费影视网站 | 免费看的黄色录像 | 久射网| 日韩精品一区二 | 日韩在线短视频 | 超级碰碰碰免费视频 | 成年人在线播放视频 | 久久久久免费电影 | 国产美女免费 | 四虎影视精品 | 色.com| 亚洲精品中文字幕在线观看 | 狠狠干成人综合网 | 天天插综合网 | 在线激情影院一区 | 国产成人a亚洲精品v | www.超碰97.com | www.久久免费视频 | 国产经典 欧美精品 | 久久草草热国产精品直播 | 中文字幕色在线 | 波多野结衣精品 | 高清av免费看 | 精品国产亚洲一区二区麻豆 | 午夜av电影院 | 四虎影视成人 | 欧美美女视频在线观看 | 中文理论片 | 91亚洲精品乱码久久久久久蜜桃 | 免费电影一区二区三区 | 日本资源中文字幕在线 | 久久久精品视频网站 | 色噜噜在线观看视频 | 亚洲高清视频在线播放 | 91九色综合| 黄色三级免费片 | 欧美成人精品三级在线观看播放 | 久久综合五月 | 96视频免费在线观看 | 欧美一级片免费在线观看 | 久久爱www. | 色多多污污在线观看 | 国产精品18久久久久vr手机版特色 | 91人人干| 国产精品1区2区3区在线观看 | 激情五月婷婷丁香 | 国产高清不卡一区二区三区 | 天天色天天艹 | 深夜男人影院 | 日日干视频 | 久久av免费 | 日韩精品免费一区二区在线观看 | 久久久国产一区二区三区 | 国产精品毛片久久久久久 | 在线观看mv的中文字幕网站 | 黄色网www| 在线视频1卡二卡三卡 | 伊人伊成久久人综合网站 | 一本色道久久精品 | 亚洲精品男女 | 国产精品电影一区 | 在线免费看黄网站 | 97人人模人人爽人人喊中文字 | 日本久久91| 91精品国产92久久久久 | 亚洲闷骚少妇在线观看网站 | 色噜噜狠狠狠狠色综合久不 | 婷婷激情综合五月天 | 免费男女羞羞的视频网站中文字幕 | 人人澡超碰碰97碰碰碰软件 | 国产四虎影院 | 99色网站 | 国产男女爽爽爽免费视频 | 99色免费视频 | 国产精品短视频 | 国产午夜精品福利视频 | 久久亚洲综合国产精品99麻豆的功能介绍 | 免费男女网站 | 人人超碰人人 | 在线你懂的视频 | 91理论电影 | 亚洲中字幕 | 成人午夜影视 | 蜜臀久久99精品久久久无需会员 | 国产区 在线 | 亚洲最大av | 五月天色网站 | 日本韩国在线不卡 | 国产视频 亚洲精品 | 亚洲一级黄色av | 91精品国产电影 | 久久久久国产精品免费 | 精品国产免费看 | 有码中文字幕在线观看 | 99精品一级欧美片免费播放 | 成人啊 v | 国产久草在线观看 | 日韩av播放在线 | www..com黄色片| 一级片黄色片网站 | 久久综合九色综合97婷婷女人 | 天堂在线成人 | 日韩欧美高清免费 | 97在线观看免费视频 | 在线日韩一区 | 免费黄色网址网站 | a一片一级 | 久久字幕网 | 日韩中文字幕免费在线观看 | 精品国产乱码久久久久久三级人 | 在线播放国产精品 | 成人精品视频久久久久 | 欧美少妇18p | 亚洲午夜精品福利 | 久艹视频在线观看 | 97狠狠操 | 在线观看一级片 | 在线看国产视频 | 国产精品入口久久 | 久久99爱视频 | 一区二区三区高清在线 | 色伊人网 | 免费观看的黄色 | 一区二区三区在线免费播放 | 啪啪免费观看网站 | 六月久久婷婷 | 美女视频黄网站 | 天天干.com| 国产精品婷婷 | 欧美疯狂性受xxxxx另类 | 亚洲精品美女在线观看播放 | 国产不卡片 | 中文字幕 在线看 | 亚洲一区二区三区毛片 | 亚洲影视九九影院在线观看 | 欧美91精品国产自产 | 国产成人精品一区二 | 国产精品男女视频 | 日韩精品一区二区在线观看 | 日韩av免费观看网站 | 国产精品久久久视频 | 国产亚洲精品久久久久5区 成人h电影在线观看 | 久久久久久97三级 | 91人人爱 | 国产小视频福利在线 | 亚洲精品中文在线观看 | 色小说av| 日本精品免费看 | 97人人爽| 久久婷婷开心 | 久久精品亚洲一区二区三区观看模式 | 99热亚洲精品| 一级免费片 | 亚洲婷久久 | 久草视频在线看 | 又黄又刺激的网站 | 国产亚洲在线 | 欧美一级电影 | 天天操天天舔天天爽 | 精品夜夜嗨av一区二区三区 | 伊人五月天 | 亚洲精品国产第一综合99久久 | 国产一区欧美二区 | 久久99热这里只有精品国产 | 中文区中文字幕免费看 | 欧美精品亚州精品 | 在线国产视频观看 | 精品久久久久久国产91 | av福利第一导航 | 香蕉影院在线 | 国产精品视频资源 | 国产99久久九九精品免费 | 狠狠插狠狠操 | 99综合视频 | 日韩一级黄色av | 日本视频高清 | 欧美日韩国产成人 | av免费看在线 | 午夜视频在线观看网站 | 欧美专区国产专区 | 国产精品第2页 | 日本中出在线观看 | 久久国产午夜精品理论片最新版本 | 久久视频在线视频 | 久草在线观看资源 | 99久精品视频 | 婷婷丁香花五月天 | 黄色国产精品 | 首页中文字幕 | 久久久久久久久久久久久影院 | 久久精品爱爱视频 | 国产精品mv在线观看 | 精品中文字幕在线 | 亚洲激情网站免费观看 | 亚洲国产精品影院 | 久久在线免费 | www.福利视频 | 欧美一级性生活视频 | 中文字幕在线观看免费观看 | 久久精品电影 | 亚洲片在线| 日韩精品久久久 | 国语自产偷拍精品视频偷 | 亚洲资源片 | 中文字幕中文字幕在线中文字幕三区 | 91九色在线播放 | 久久伊人爱 | 九九爱免费视频在线观看 | 久久精品国产亚洲aⅴ | 国内亚洲精品 | 婷婷五天天在线视频 | 精品9999| av一级久久 | 国产 精品 资源 | 国产黄色av影视 | 久久综合九色综合欧美就去吻 | 91视频国产高清 | 国产高清一级 | 91中文视频 | 国产一卡久久电影永久 | 手机版av在线 | 99久久www免费 | 毛片精品免费在线观看 | 九九九九九九精品任你躁 | 成人永久免费 | 亚洲国产午夜精品 | 精品免费久久久久 | 美女黄视频免费 | 五月天中文字幕mv在线 | 国产九九九九九 | 精品国产亚洲一区二区麻豆 | 国产精品一区二区中文字幕 | 成人一级片免费看 | 欧美日韩精品网站 | 亚洲精品国产精品国自产观看浪潮 | 一区二区三区电影在线播 | 成人在线观看免费 | 丁香5月婷婷久久 | 欧美日韩国内在线 | 五月天色婷婷丁香 | 国产精品一区专区欧美日韩 | 精品五月天 | 操操日日 | 人人插人人舔 | 欧美视频18 | 天天躁日日躁狠狠躁av麻豆 | 国产精品视频 | 天天操人 | 久久视精品 | 97国产精品亚洲精品 | 日韩av一区二区在线影视 | 天天干夜夜爱 | 日韩一级电影网站 | 久久色在线播放 | 国产精品日韩久久久久 | 亚洲精品高清在线观看 | 日韩欧美区 | 免费在线播放av电影 | 久久精品91视频 | 日韩一区二区免费在线观看 | 成年人电影免费在线观看 | 99视频精品视频高清免费 | 三三级黄色片之日韩 | 日韩在线视频精品 | 最近中文字幕久久 | 中文字幕一区2区3区 | 国内小视频在线观看 | 区一区二在线 | 天天做日日做天天爽视频免费 | 精品视频97| 99久久久国产精品免费观看 | 亚洲 欧美 国产 va在线影院 | 久久神马影院 | 亚洲精品国产品国语在线 | 亚洲国产三级在线观看 | 久久久久久久久爱 | 正在播放 国产精品 | 黄色大全在线观看 | 久久婷婷精品视频 | 黄网在线免费观看 | 久久国产精品久久w女人spa | 久热色超碰 | 色av婷婷 | 99视频久 | 免费黄色在线网址 | www.com.日本一级 | 久久久国产精品视频 | 天天色棕合合合合合合 | 色综合五月天 | 国产成视频在线观看 | 亚洲一区二区精品 | 日韩亚洲在线视频 | 99热国产在线观看 | 日韩视频中文字幕在线观看 | 久久久久久久久久久久av | 免费视频黄色 | 久久久精品电影 | 91精品爽啪蜜夜国产在线播放 | 欧美性护士 | 国产精品一区二区中文字幕 | 亚洲第一久久久 | 国产一级片免费视频 | 在线观看成人福利 | 国产 视频 久久 | 婷婷久月| 激情五月婷婷综合网 | 欧美精品一级视频 | 久久69精品久久久久久久电影好 | 狠狠成人| 色久天 | 天天干人人插 | 亚av在线| 91精品色 | 国产在线观看你懂得 | 最近字幕在线观看第一季 | 中日韩三级视频 | 国产精品久久精品国产 | 五月天堂网 | 成人影片在线免费观看 | 日韩午夜在线播放 | 久久久精品国产一区二区电影四季 | 在线a视频免费观看 | av大片免费看 | 日韩免费一二三区 | 天天操天天色天天射 | 久久精品中文字幕免费mv | 久久免费看片 | 亚洲va欧美va人人爽春色影视 | 日韩在线观看高清 | 夜夜干夜夜 | 免费午夜视频在线观看 | 国产精品手机在线 | 色婷婷六月天 | 国产亚洲在线观看 | 国产精品亚州 | 婷婷伊人综合亚洲综合网 | 91精品国产电影 | 丁香高清视频在线看看 | 国产成人久久77777精品 | 国产精品福利在线观看 | 色婷婷福利 | 国产亚洲精品久久久久久久久久久久 | 亚洲成a人片77777kkkk1在线观看 | 九九热精 | 国产在线久久久 | 日韩欧美在线视频一区二区三区 | 免费情趣视频 | 黄色网在线播放 | 亚洲人片在线观看 | 久久66热这里只有精品 | 日韩av免费在线看 | 日本久久综合视频 | 91成品视频| 91麻豆精品国产91久久久更新时间 | 国产精品不卡一区 | 国产一区国产二区在线观看 | 中文字幕亚洲精品日韩 | 韩国三级一区 | 波多野结衣综合网 | 亚洲精品日韩一区二区电影 | 亚洲欧美va | 一区二区三区精品久久久 | 成年人免费观看在线视频 | 亚洲激情一区二区三区 | 国产精品理论片在线播放 | 欧美亚洲xxx | 91中文在线视频 | 久久这里只有精品23 | 免费网站在线观看人 | 午夜视频不卡 | 久草在线资源免费 | 亚洲欧洲国产视频 | 欧美国产在线看 | 日韩超碰在线 | 中文字幕电影高清在线观看 | 五月天中文在线 | 久久夜夜操 | 久久字幕精品一区 | 国产精品毛片一区视频 | 中文字幕亚洲欧美日韩2019 | 久久免费视频8 | 国产亚洲在线视频 | 久久丁香网 | 色婷婷精品大在线视频 | 91高清免费观看 | 中文字幕在线观看第一区 | 高清av在线| 日日干精品 | 欧美一级免费黄色片 | free. 性欧美.com | 国产97在线播放 | 久久久久成| 精品在线看 | 在线观看免费日韩 | 成人国产精品免费观看 | 激情婷婷综合网 | 日韩久久一区二区 | 日韩av视屏| 欧美精品三级 | 午夜美女福利 | 狠狠插狠狠操 | 欧美成人精品欧美一级乱黄 | 伊人首页 | 91在线看| 成人动漫精品一区二区 | 天天操天天吃 | 18国产精品福利片久久婷 | 精品国产乱码一区二区三区在线 | 国产中文在线视频 | 欧美国产日韩激情 | 久草在线免费资源 | 国产一级片播放 | 国产一二三四在线视频 | 十八岁以下禁止观看的1000个网站 | 在线亚洲激情 | 精品一区二区在线观看 | 日韩久久精品一区二区三区下载 | 久久久精品高清 | av在线免费观看不卡 | 高清不卡一区二区在线 | 国产永久网站 | 91网址在线观看 | 久久歪歪 | 91精品免费看 | 综合网色 | 国产精品中文字幕在线播放 | 欧美大片mv免费 | 久久天堂网站 | 日韩r级电影在线观看 | 久久久久国产精品免费免费搜索 | 亚洲激情网站免费观看 | 九九色综合 | 在线天堂中文在线资源网 | 波多野结衣电影一区二区三区 | 欧美精品久久久久久久亚洲调教 | 人人添人人| av中文字幕不卡 | 97国产大学生情侣白嫩酒店 | 最近2019好看的中文字幕免费 | 国产精品12 | 色之综合网 | 精品欧美在线视频 | 成人毛片在线观看 | 99热高清| 91毛片在线 | 国产精品国产精品 | 黄色在线观看污 | 久久久国产一区 | 国产 日韩 中文字幕 | 亚洲观看黄色网 | 人人射人人射 | 最新超碰在线 | 欧美在线观看视频一区二区 | 色婷婷成人网 | 激情网站免费观看 | 中国一级片免费看 | 在线观看国产福利片 | 日韩不卡高清 | 九九久久久久99精品 | aav在线| 亚洲精品视频观看 | 国产精品色视频 | 亚洲精品在线播放视频 | 97电影在线看视频 | 国产九九九精品视频 | 五月婷色 | 日韩av影视在线 | 国产高潮久久 | 精品乱码一区二区三四区 | 日韩理论电影在线 | 在线观看的av | 久久国产精品区 | 国内精品视频免费 | 日韩xxxx视频 | 黄色成人91| 日韩在线电影一区二区 | 五月天激情开心 | 国产精品24小时在线观看 | 久久精品国产精品亚洲精品 | 欧美美女一级片 | 成人avav| 国产精品国产亚洲精品看不卡 | 精品嫩模福利一区二区蜜臀 | 亚洲人精品午夜 | 免费99精品国产自在在线 | 美女在线免费观看视频 | 日韩久久精品一区二区三区下载 | 亚洲精品国偷自产在线99热 | 国产老妇av| 亚洲清纯国产 | 精品一区二区免费 | 人成午夜视频 | 久久综合九色综合欧美就去吻 | 奇米影视8888在线观看大全免费 | 国产精品乱看 | 97在线视频免费 | 在线观看日韩一区 | 超碰国产在线 | 日韩伦理片hd | 天天射天天干天天插 | 亚洲精品乱码久久久一二三 | 少妇按摩av | 九九久久久久99精品 | 久影院 | 婷婷av在线 | 伊人黄色网 | 精品国产片 | 久久久久欠精品国产毛片国产毛生 | 少妇bbw搡bbbb搡bbbb | 亚洲国产播放 | 涩涩资源网 | 在线免费观看麻豆 | 成人免费中文字幕 | 一区二区三区久久 | 欧美精品免费一区二区 | 日韩视频免费 | 天天射,天天干 | 人人爽人人爽人人爽人人爽 | 国产视频高清 | 成年人精品 | 天天人人综合 | av一区二区三区在线观看 | 久一久久| 国产99一区二区 | 国产精品国产三级国产aⅴ入口 | 日韩在线三级 | 欧美午夜视频在线 | 欧美日韩国产网站 | 天天操网 | 深夜精品福利 | 欧美激情奇米色 | 亚洲精品中文在线 | 中文字幕视频观看 | 又黄又爽又无遮挡的视频 | 免费观看国产精品视频 | 蜜臀久久99静品久久久久久 | 亚洲电影久久久 | 国产伦精品一区二区三区四区视频 | 欧美一级久久久久 | 在线观看视频你懂的 | 久久综合久久综合久久综合 | 九九欧美视频 | 成年人在线播放视频 | 天天操 夜夜操 | 国产专区视频在线观看 | 人人干干人人 | 在线观看色网 | 亚州欧美精品 | 黄色录像av | 精品久久久久久久久久久久久久久久久久 | 精品福利视频在线 | 狠狠干夜夜操 | 成年人免费看片网站 | 亚洲 欧美 日韩 综合 | 奇米影视777四色米奇影院 | 98超碰在线观看 | 黄色av影院 | 久久免费精品一区二区三区 | 日韩在线大片 | 国产欧美精品一区二区三区四区 | 精品视频9999 | 免费看av片网站 | 天天草综合 | 亚洲欧美视频网站 | 97精品国自产拍在线观看 | 色婷婷激情电影 | 久久的色 | 91成年视频| av在线网站观看 | 国产成视频在线观看 | 国产视频精品免费播放 | 国产黄网站在线观看 | 欧洲性视频 | 天天干一干 | 激情自拍av | 天天色天天操天天爽 | 亚洲另类人人澡 | 色橹橹欧美在线观看视频高清 | 国内免费的中文字幕 | 久久激情日本aⅴ | 免费看三级黄色片 | 欧美日韩一区二区视频在线观看 | 日本视频不卡 | 中文字幕日韩国产 | 国产一二三在线视频 | 精品一区二区综合 | 成人国产网址 | 天天色天天上天天操 | 欧美日韩有码 | 一区电影 | 欧美日韩免费在线视频 | 日韩在线观看三区 | 国产精品成人在线观看 | 91精品国产综合久久婷婷香蕉 | 黄色毛片网站在线观看 | 精品国产免费av | 亚洲乱亚洲乱亚洲 | 国产欧美在线一区二区三区 | www天天干com| 日韩在线视频一区 | 最近日本韩国中文字幕 | 精品视频不卡 | 国产剧情一区在线 | 91av在线免费播放 | 免费成视频 | 亚洲涩涩网站 | 爱情影院aqdy鲁丝片二区 | 久久精品99国产精品酒店日本 | 麻花传媒mv免费观看 | 国产精品美女在线观看 | 亚洲欧美日韩精品一区二区 | 日韩精品免费在线 | 女女av在线 | 国产999视频 | 久久久久国产精品一区 | 亚洲综合色播 | 最近免费中文字幕大全高清10 | 日本99久久 | 久久观看 | 免费a级观看 | 国产日韩精品一区二区三区 | 99精品国产99久久久久久福利 | 99九九99九九九视频精品 | 国产精品高清在线 | 在线看小早川怜子av | 91看毛片 | 欧美在线视频一区二区三区 | 久草在线视频精品 | 六月色婷婷 | 九九精品在线观看 | 久久影视中文字幕 | 亚洲第一中文字幕 | 最新极品jizzhd欧美 | av黄色在线 | 91麻豆精品国产91久久久无需广告 | 激情五月***国产精品 | 日本爱爱免费 | 9色在线视频 | 国产视频 亚洲精品 | 97在线观看免费 | 综合色中色 | 91最新在线观看 | 久久精品久久精品 | 日韩高清在线一区二区三区 | 视频在线国产 | 在线观看视频一区二区三区 | 五月婷婷黄色网 | www.午夜色.com| 日本中文在线观看 | 国产成人免费观看久久久 | 免费黄色av电影 | av网站免费在线 | 91完整版观看 | 97精品免费视频 | 国产群p视频 | 91精彩视频在线观看 | 色窝资源| 欧美日韩亚洲在线观看 | 免费看的黄色录像 | 五月天国产精品 | 国产精品欧美久久 | 欧美专区国产专区 | 国产精品国产亚洲精品看不卡15 | 国产美女免费观看 | 免费在线激情电影 | 日韩黄色免费在线观看 | 少妇bbb搡bbbb搡bbbb | 欧美激情va永久在线播放 | 天天爱天天射天天干天天 | 欧美日韩调教 | 日韩精品综合在线 | 久久夜色精品国产欧美乱极品 | 免费a现在观看 | 黄色视屏免费在线观看 | 91精品亚洲影视在线观看 | 成人午夜网址 | 午夜视频黄 | 亚洲激情精品 | 亚洲视频99| 午夜精品区 | 久久久久久国产精品 | 美女免费视频一区二区 | 久久国产午夜精品理论片最新版本 | 国产精品尤物 | 国产99久久久精品 | 久久999精品| 美女网站在线 | 天天爽人人爽 | 91探花国产综合在线精品 | 亚洲免费永久精品国产 | 国产免费区 | 午夜电影 电影 | 中文字幕网站视频在线 | 天天操操操操操 | 亚洲激情影院 | a成人v| 久99热| 天天av天天 | 国产高清免费在线播放 | 国产精品99久久99久久久二8 | 午夜美女视频 | 国产精品自在线拍国产 | 夜夜视频| 亚洲一区欧美激情 | 日本xxxxav| 国产精品专区一 | 日本中文一级片 | 久久不射电影院 | 日日婷婷夜日日天干 | av韩国在线 | 精品一区二区免费视频 | 免费精品人在线二线三线 | 欧美成年网站 | 亚洲国产综合在线 | 亚洲视频久久久久 | 国产成人久久精品77777 | 91精品国产91p65 | 天天射,天天干 | 国产视频在线播放 | 国产色 在线 | 久久久99国产精品免费 | 欧美一区二区三区激情视频 | 狠狠操狠狠操 | 欧美男男tv网站 | 婷婷丁香色 | 午夜国产一区 | 国产九九九精品视频 | 国产精品99久久久久人中文网介绍 | 欧美一区免费观看 | 日韩在线不卡av | 久久久久久蜜桃一区二区 | 色婷婷亚洲婷婷 | 中文在线免费一区三区 | 国产香蕉97碰碰碰视频在线观看 | 久久一区精品 | 成全免费观看视频 | 91成人在线看 | 久久66热这里只有精品 | 一区二精品 | 97在线视频网站 | 免费看一级黄色大全 | 婷婷丁香狠狠爱 | 在线观看免费日韩 | 一级黄色免费网站 | 成人在线播放视频 | 永久精品视频 | 欧美精品国产综合久久 | av动态图片 | 精品国产精品久久一区免费式 | 免费国产在线视频 | 日韩大片在线播放 | 国产日韩欧美在线影视 | av成人在线看 | 国产精品 美女 | 久久视频在线视频 | 一级免费观看 | 手机看片1042 | 不卡中文字幕在线 | 中文字幕在线一区二区三区 | 日韩视频中文字幕 | 欧美视频xxx | 国产成年人av | 日韩精品视频第一页 | 中文字幕亚洲综合久久五月天色无吗'' | 天天操月月操 | 亚洲精品视频免费在线观看 | 久久亚洲视频 | 天天操天天射天天添 | 毛片网在线 | 亚洲人成在线电影 | 日韩午夜大片 | 91一区二区在线 | 99久久精品免费看国产四区 | 欧美午夜精品久久久久 | 超碰在线成人 | 国产在线色 | 成人网中文字幕 | 成人黄视频 | 91高清不卡| 久久久久激情视频 | 欧美一区二区日韩一区二区 | 国产糖心vlog在线观看 | 黄色的片子 | 久久精品精品电影网 | 国内精品美女在线观看 | 中文字幕文字幕一区二区 | 中文字幕丝袜 | 欧美色综合久久 | 国产精品视频 | 日本黄色免费播放 | 国产精品精品国产色婷婷 | 亚洲精品成人免费 | 国产亚洲精品久久久久久电影 | 五月天六月婷 | 欧美日韩国语 | 日韩在线色| 黄色片网站大全 | 一二区精品| 一区二区三区视频在线 | 亚洲激情中文 | 久久香蕉国产精品麻豆粉嫩av | 欧美91精品| 亚洲美女精品区人人人人 | 成人网在线免费视频 | 欧美一级性视频 | 久久麻豆视频 | 能在线观看的日韩av | av免费在线网站 | 操老逼免费视频 | 精品一区二三区 | av三级av| 综合网伊人 | 狠狠狠狠狠狠操 | 日韩一区二区三区免费电影 | 亚洲激情网站免费观看 | 国产亚洲观看 | 福利电影久久 | 热久久99这里有精品 | 操操操干干干 | 中文理论片 | 精品自拍sae8—视频 | 9在线观看免费高清完整 | av千婊在线免费观看 | 一区二区三区日韩在线 | 久久久久国产成人免费精品免费 | 久久久久久久久久久免费av | 久久资源在线 | 中文视频在线播放 | 久久夜夜夜 | 久久综合操 | 午夜精品一区二区三区在线 | 国产精品久久久久久久久久久久 | 色婷婷激情综合 | 午夜精品三区 | 成人免费一区二区三区在线观看 | 国产精品一区二区三区在线免费观看 | 97品白浆高清久久久久久 | 国产黑丝袜在线 | 久久国产经典 | 在线亚洲天堂网 | 美女久久视频 | 国产精品久久久久久久久久久免费看 | 久久久免费观看 | 亚洲色五月 | 久久男人中文字幕资源站 | 成人在线播放视频 | 成人黄色片在线播放 | 久久久综合香蕉尹人综合网 | 69亚洲精品 | 美女一区网站 | 99视频久| 一区二区视频在线看 | 91最新网址在线观看 | 天天射网 | 激情五月网站 | 九九色视频 | 亚洲九九| 国产在线观看不卡 | www99精品| 久久99精品久久久久蜜臀 | 亚洲综合在线视频 | 乱子伦av | 亚洲成av人电影 | av888.com| 亚洲一区不卡视频 | 黄色片网站 | 日韩av伦理片 | 97超碰在线久草超碰在线观看 | 欧美激情精品久久久久久变态 | 亚洲综合精品在线 | 欧美精品成人在线 | 婷婷色在线播放 | 久久国产免费看 | 国产色视频一区二区三区qq号 | 久草精品视频在线看网站免费 | 欧美一级片免费 | 久久伊人精品一区二区三区 | 久久久久久免费毛片精品 | 国产天天爽 | 亚洲一区二区天堂 | 久久久综合九色合综国产精品 | 久草网站在线观看 | 久久网站最新地址 | 国产福利专区 | 欧美色图30p | 五月天婷婷综合 | 深爱婷婷 | 精品免费久久久久 | 免费av在 | 国产亚洲精品久久久久久无几年桃 | 国产96视频 | 六月丁香在线视频 | 国产精品亚州 | 欧美日韩在线精品 | 久久久91精品国产一区二区三区 | www久久精品| 久久久精品高清 | 一区视频在线 | 在线观看一区二区视频 | 日本精品一区二区在线观看 | 国产护士hd高朝护士1 | 天天天操操操 | 免费黄色在线 | 国产精品成人一区二区 | 欧美一二三视频 | 91观看视频| 亚洲精品在线观看免费 | 丁香五月网久久综合 | 欧美美女激情18p | 国产精品尤物 | 日本高清中文字幕有码在线 | 安徽妇搡bbbb搡bbbb | 成人xxxx | 国产不卡在线观看视频 | 91人人澡 | 国产特黄色片 | 久久高清免费观看 | 免费高清影视 | 国产亚洲在 | 色天天综合网 | 亚洲精品乱码久久久久v最新版 | 日韩久久一区 | 开心婷婷色 | 亚洲综合色视频在线观看 | 亚洲成a人片在线观看网站口工 | 天操夜夜操 | free,性欧美| 午夜精品久久久久久久99 | 黄色www | 久久99久久99精品免费看小说 | 日本女人在线观看 | av线上看| 精品一区二区在线观看 | 狠狠色婷婷丁香六月 | 久久久久看片 | 五月综合激情网 | 日韩av区| 黄色av电影一级片 | 国产一区二区在线免费播放 | 中文字幕在线中文 | 91精品国产高清 | 久久人人爽人人爽人人片av软件 | 天天爽综合网 | 亚洲一区二区高潮无套美女 | 亚洲狠狠婷婷综合久久久 | 日韩高清免费在线 | 91最新地址永久入口 | 久久丁香网 | 中文字幕免费高清在线 | 91丨九色丨91啦蝌蚪老版 | 在线观看你懂的网站 | 国产伦精品一区二区三区照片91 | 日本aa在线 | 国产日韩欧美在线观看视频 | 久久在线电影 | 亚洲码国产日韩欧美高潮在线播放 | 亚洲人天堂 | 字幕网资源站中文字幕 | 国产在线精品国自产拍影院 | 视频99爱| 国产欧美久久久精品影院 | 伊人国产视频 | 伊人首页 | 天天操天天干天天操天天干 | 中文字幕在线有码 | 久久另类小说 | 黄av免费 | 国产精品日韩在线播放 | 国产精品免费不卡 | 91亚洲永久精品 | av片中文字幕 | 热久久免费视频 | 国产一区二区免费看 | 国产精品永久免费 | 国产高清精品在线观看 | 国产色视频123区 | www视频在线播放 | 日韩在线电影观看 | 日韩在线免费观看视频 | 六月色丁香 |