青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

興海北路

---男兒仗劍自橫行
<2025年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

統計

  • 隨筆 - 85
  • 文章 - 0
  • 評論 - 17
  • 引用 - 0

常用鏈接

留言簿(6)

隨筆分類

隨筆檔案

收藏夾

全是知識啊

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

GCC編譯背后(第二部分:匯編和鏈接)
(上接“GCC編譯的背后(第一部分:預處理和編譯)”)

3、匯編

    開篇:這里實際上還是翻譯過程,只不過把作為中間結果的匯編代碼翻譯成了機器代碼,即目標代碼,不過它還不可以運行。如果要產生這一中間結果,可用gcc的-c選項,當然,也可通過as命令_匯編_匯編語言源文件來產生。

    匯編是把匯編語言翻譯成目標代碼的過程,在學習匯編語言開發時,大家應該比較熟悉nasm匯編工具(支持Intel格式的匯編語言)了,不過這里主要用 as匯編工具來匯編AT&T格式的匯編語言,因為gcc產生的中間代碼就是AT&T格式的。下面來演示分別通過gcc的-c選項和as來 產生 目標代碼。

Quote:

$ file hello.s
hello.s: ASCII assembler program text
$ gcc -c hello.s        #用gcc把匯編語言編譯成目標代碼
$ file hello.o            #file命令可以用來查看文件的類型,這個目標代碼是可重定位的(relocatable),需                   #要通過ld進行進一步的鏈接成可執行程序(executable)和共享庫(shared)
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ as -o hello.o hello.s        #用as把匯編語言編譯成目標代碼
$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped



    gcc和as默認產生的目標代碼都是ELF格式[6]的,因此這里主要討論ELF格式的目標代碼(如果 有時間再回顧一下a.out和coff格式,當然你也可以參考資料[15],自己先了解一下,并結合objcopy來轉換它們,比較異同)。

    目標代碼不再是普通的文本格式,無法直接通過文本編輯器瀏覽,需要一些專門的工具。如果想了解更多目標代碼的細節,區分relocatable(可重定 位)、executable(可執行)、shared libarary(共享庫)的不同,我們得設法了解目標代碼的組織方式和相關的閱讀和分析工具。下面我們主要介紹這部分內容。
    "BFD is a package which allows applications to use the same routines to operate on object files whatever the object file format. A new object file format can be supported simply by creating a new BFD back end and adding it to the library."[24][25]。
    binutils(GNU Binary Utilities)的很多工具都采用這個庫來操作目標文件,這類工具有objdump,objcopy,nm,strip等(當然,你也可以利用它。如 果你深入了解ELF格式,那么通過它來分析和編寫Virus程序將會更加方便),不過另外一款非常優秀的分析工具readelf并不是 基于這個庫,所以你也應該可以直接用elf.h頭文件中定義的相關結構來操作ELF文件。

    下面將通過這些輔助工具(主要是readelf和objdump,可參考本節最后列出的資料[4]),結合ELF手冊[6](建議看第三篇中文版)來分析它們。

    下面大概介紹ELF文件的結構和三種不同類型ELF文件的區別。

ELF文件的結構:

ELF Header(ELF文件頭)
Porgram Headers Table(程序頭表,實際上叫段表好一些,用于描述可執行文件和可共享庫)
Section 1
Section 2   
Section 3
...
Section Headers Table(節區頭部表,用于鏈接可重定位文件成可執行文件或共享庫)

    對于可重定位文件,程序頭是可選的,而對于可執行文件和共享庫文件(動態連接庫),節區表則是可選的。這里的可選是指沒有也可以??梢苑謩e通過 readelf文件的-h,-l和-S參數查看ELF文件頭(ELF Header)、程序頭部表(Program Headers Table,段表)和節區表(Section Headers Table)。

    文件頭說明了文件的類型,大小,運行平臺,節區數目等。先來通過文件頭看看不同ELF的類型。為了說明問題,先來幾段代碼吧。



Code:

[Ctrl+A Select All]





Code:

[Ctrl+A Select All]





Code:

[Ctrl+A Select All]



    下面通過這幾段代碼來演示通過readelf -h參數查看ELF的不同類型。期間將演示如何創建動態連接庫(即可共享文件)、靜態連接庫,并比較它們的異同。
Quote:

$ gcc -c myprintf.c test.c        #編譯產生兩個目標文件myprintf.o和test.o,它們都是可重定位文件(REL)
$ readelf -h test.o | grep Type   
  Type:                              REL (Relocatable file)
$ readelf -h myprintf.o | grep Type
  Type:                              REL (Relocatable file)
$ gcc -o test myprintf.o test.o    #根據目標代碼連接產生可執行文件,這里的文件類型是可執行的(EXEC)
$ readelf -h test | grep Type
  Type:                              EXEC (Executable file)
$ ar rcsv libmyprintf.a myprintf.o    #用ar命令創建一個靜態連接庫,靜態連接庫也是可重定位文件(REL)
$ readelf -h libmyprintf.a | grep Type    #因此,使用靜態連接庫和可重定位文件一樣,它們之間唯一不
                                        #同是前者可以是多個可重定位文件的“集合”。
  Type:                              REL (Relocatable file)
$ gcc -o test test.o -llib -L./        #可以直接連接進去,也可以使用-l參數,-L指定庫的搜索路徑
$ gcc -Wall myprintf.o -shared -Wl,-soname,libmyprintf.so.0 -o libmyprintf.so.0.0
                                    #編譯產生動態鏈接庫,并支持major和minor版本號,動態鏈接庫類型為DYN
$ ln -sf libmyprintf.so.0.0 libmyprintf.so.0
$ ln -sf libmyprintf.so.0 libmyprintf.so
$ readelf -h libmyprintf.so | grep Type
  Type:                              DYN (Shared object file)
$ gcc -o test test.o -llib -L./        #編譯時和靜態連接庫類似,但是執行時需要指定動態連接庫的搜索路徑
$ LD_LIBRARY_PATH=./ ./test            #LD_LIBRARY_PATH為動態鏈接庫的搜索路徑
$ gcc -static -o test test.o -llib -L./    #在不指定static時會優先使用動態鏈接庫,指定時則阻止使用動態連接庫
                                        #這個時候會把所有靜態連接庫文件加入到可執行文件中,使得執行文件很大
                                        #而且加載到內存以后會浪費內存空間,因此不建議這么做



    經過上面的演示基本可以看出它們之間的不同。可重定位文件本身不可以運行,僅僅是作為可執行文件、靜態連接庫(也是可重定位文件)、動態連接庫的 “組件”。靜態連接庫和動態連接庫本身也不可以執行,作為可執行文件的“組件”,它們兩者也不同,前者也是可重定位文件(只不過可能是多個可重定位文件的 集合),并且在連接時加入到可執行文件中去;而動態連接庫在連接時,庫文件本身并沒有添加到可執行文件中,只是在可執行文件中加入了該庫的名字等信息,以 便在可執行文件運行過程中引用庫中的函數時由動態連接器去查找相關函數的地址,并調用它們。從這個意義上說,動態連接庫本身也具有可重定位的特征,含有可 重定位的信息。對于什么是重定位?如何進行靜態符號和動態符號的重定位,我們將在鏈接部分和《動態符號鏈接的細節》一節介紹。

    下面來看看ELF文件的主體內容,節區(Section)。ELF文件具有很大的靈活性,它通過文件頭組織整個文件的總體結構,通過節區表 (Section Headers Table)和程序頭(Program Headers Table或者叫段表)來分別描述可重定位文件和可執行文件。但不管是哪種類型,它們都需要它們的主體,即各種節區。在可重定位文件中,節區 表描述的就是各種節區本身;而在可執行文件中,程序頭描述的是由各個節區組成的段(Segment),以便程序運行時動態裝載器知道如何對它們進行內存映像,從而方便程序加載和運行。
    下面先來看看 一些常見的節區,而關于這些節區(section)如何通過重定位構成成不同的段(Segments),以及有哪些常規的段,我們將在鏈接部分進一步介紹。

    可以通過readelf的-S參數查看ELF的節區。(建議一邊操作一邊看文檔,以便加深對ELF文件結構的理解)先來看看可重定位文件的節區信息,通過節區表來查看:

Quote:

$ gcc -c myprintf.c            #默認編譯好myprintf.c,將產生一個可重定位的文件myprintf.o
$ readelf -S myprintf.o        #通過查看myprintf.o的節區表查看節區信息
There are 11 section headers, starting at offset 0xc0:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000018 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 000334 000010 08      9   1  4
  [ 3] .data             PROGBITS        00000000 00004c 000000 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00004c 000000 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 00004c 00000e 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 00005a 000012 00      0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 00006c 000000 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 00006c 000051 00      0   0  1
  [ 9] .symtab           SYMTAB          00000000 000278 0000a0 10     10   8  4
  [10] .strtab           STRTAB          00000000 000318 00001a 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
$ objdump -d -j .text   myprintf.o      #這里是程序指令部分,用objdump的-d選項可以看到反編譯的結果,
                                                                        #-j指定需要查看的節區
myprintf.o:     file format elf32-i386

Disassembly of section .text:

00000000 <myprintf>:
   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 <myprintf+0xf>
  13:   83 c4 10                add    $0x10,%esp
  16:   c9                      leave
  17:   c3                      ret
$ readelf -r myprintf.o                         #用-r選項可以看到有關重定位的信息,這里有兩部分需要重定位

Relocation section '.rel.text' at offset 0x334 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0000000a  00000501 R_386_32          00000000   .rodata
0000000f  00000902 R_386_PC32        00000000   puts
$ readelf -x .rodata myprintf.o         #.rodata節區包含只讀數據,即我們要打印的hello, world!.

Hex dump of section '.rodata':
  0x00000000 68656c6c 6f2c2077 6f726c64 2100     hello, world!.

$ readelf -x .data myprintf.o           #沒有這個節區,.data應該包含一些初始化的數據

Section '.data' has no data to dump.
$ readelf -x .bss       mmyprintf.o             #也沒有這個節區,.bss應該包含一些未初始化的數據,程序默認初始為0

Section '.bss' has no data to dump.
$ readelf -x .comment myprintf.o        #是一些注釋,可以看到是是GCC的版本信息

Hex dump of section '.comment':
  0x00000000 00474343 3a202847 4e552920 342e312e .GCC: (GNU) 4.1.
  0x00000010 3200                                2.
$ readelf -x .note.GNU-stack myprintf.o #這個也沒有內容

Section '.note.GNU-stack' has no data to dump.
$ readelf -x .shstrtab myprintf.o       #包括所有節區的名字

Hex dump of section '.shstrtab':
  0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
  0x00000010 002e7368 73747274 6162002e 72656c2e ..shstrtab..rel.
  0x00000020 74657874 002e6461 7461002e 62737300 text..data..bss.
  0x00000030 2e726f64 61746100 2e636f6d 6d656e74 .rodata..comment
  0x00000040 002e6e6f 74652e47 4e552d73 7461636b ..note.GNU-stack
  0x00000050 00                                  .

$ readelf -symtab myprintf.o    #符號表,包括所有用到的相關符號信息,如函數名、變量名

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS myprintf.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000000     0 SECTION LOCAL  DEFAULT    7
     7: 00000000     0 SECTION LOCAL  DEFAULT    6
     8: 00000000    24 FUNC    GLOBAL DEFAULT    1 myprintf
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND puts
$ readelf -x .strtab myprintf.o #字符串表,用到的字符串,包括文件名、函數名、變量名等。

Hex dump of section '.strtab':
  0x00000000 006d7970 72696e74 662e6300 6d797072 .myprintf.c.mypr
  0x00000010 696e7466 00707574 7300              intf.puts.



    從上表可以看出,對于可重定位文件,會包含這些基本節區.text, .rel.text, .data, .bss, .rodata, .comment, .note.GNU-stack, .shstrtab, .symtab和.strtab。為了進一步理解這些節區和源代碼的關系,這里來看一看myprintf.c產生的匯編代碼。

Quote:

$ gcc -S myprintf.c
$ cat myprintf.s
        .file   "myprintf.c"
        .section        .rodata
.LC0:
        .string "hello, world!"
        .text
.globl myprintf
        .type   myprintf, @function
myprintf:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        subl    $12, %esp
        pushl   $.LC0
        call    puts
        addl    $16, %esp
        leave
        ret
        .size   myprintf, .-myprintf
        .ident  "GCC: (GNU) 4.1.2"
        .section        .note.GNU-stack,"",@progbits



    是不是可以從中看出可重定位文件中的那些節區和匯編語言代碼之間的關系?在上面的可重定位文件,可以看到有一個可重定位的節區,即. rel.text,它標記了兩個需要重定位的項,.rodata和puts。這個節區將告訴編譯器這兩個信息在鏈接或者動態鏈接的過程中需要重定位, 具體如何重定位?將根據重定位項的類型,比如上面的R_386_32和R_386_PC32(關于這些類型的更多細節,請查看ELF手冊[6])。

    到這里,對可重定位文件應該有了一個基本的了解,下面將介紹什么是可重定位,可重定位文件到底是如何被鏈接生成可執行文件和動態連接庫的,這個過程除了進行了一些符號的重定位外,還進行了哪些工作呢?

本節參考資料:

[1] 了解編譯程序的過程
http://9iyou.com/Program_Data/linuxunix-3125.html
http://www.host01.com/article/server/00070002/0621409075078127.htm
[2] C track: compiling C programs.
http://www.cs.caltech.edu/courses/cs11/material/c/mike/misc/compiling_c.html
[3] Dissecting shared libraries
http://www.ibm.com/developerworks/linux/library/l-shlibs.html

4、鏈接

    開篇:重定位是將符號引用與符號定義進行鏈接的過程。因此鏈接是處理可重定位文件,把它們的各種符號引用和符號定義轉換為可執行文件中的合適信息(一般是虛擬內存地址)的過程。鏈接又 分為靜態鏈接和動態鏈接,前者是程序開發階段程序員用ld(gcc實際上在后臺調用了ld)靜態鏈接器手動鏈接的過程,而動態鏈接則是程序運行期間系 統調用動態鏈接器(ld-linux.so)自動鏈接的過程。比如,如果鏈接到可執行文件中的是靜態連接庫libmyprintf.a,那么. rodata節區在鏈接后需要被重定位到一個絕對的虛擬內存地址,以便程序運行時能夠正確訪問該節區中的字符串信息。而對于puts,因為它是動態連接庫libc.so中定義的函數,所 以會在程序運行時通過動態符號鏈接找出puts函數在內存中的地址,以便程序調用該函數。在這里主要討論靜態鏈接過程,動態鏈接過程見《動態符號鏈接的細節》。

    靜態鏈接過程主要是把可重定位文件依次讀入,分析各個文件的文件頭,進而依次讀入各個文件的節區,并計算各個節區的虛擬內存位置,對一些需要重定位的符號 進 行處理,設定它們的虛擬內存地址等,并最終產生一個可執行文件或者是動態鏈接庫。這個鏈接過程是通過ld來完成的,ld在鏈接時使用了一個鏈接腳本 (linker script), 該鏈接腳本處理鏈接的具體細節。由于靜態符號鏈接過程非常復雜,特別是計算符號地址的過程,考慮到時間關系,相關細節請參考ELF手冊[6]。這里主要介 紹可重定位文件中的節區(節區表描述的)和可執行文件中段(程序頭描述的)的對應關系以及gcc編譯時采用的一些默認鏈接選項。

    下面先來看看可執行文件的節區信息,通過程序頭(段表)來查看:

Quote:

$ readelf -S test.o                        #為了比較,先把test.o的節區表也列出
There are 10 section headers, starting at offset 0xb4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000024 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 0002ec 000008 08      8   1  4
  [ 3] .data             PROGBITS        00000000 000058 000000 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 000058 000000 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 000058 000012 00      0   0  1
  [ 6] .note.GNU-stack   PROGBITS        00000000 00006a 000000 00      0   0  1
  [ 7] .shstrtab         STRTAB          00000000 00006a 000049 00      0   0  1
  [ 8] .symtab           SYMTAB          00000000 000244 000090 10      9   7  4
  [ 9] .strtab           STRTAB          00000000 0002d4 000016 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
$ gcc -o test test.o libmyprintf.o
$ readelf -l test        #我們發現,test和test.o,libmyprintf.o相比,多了很多節區,如.interp和.init等

Elf file type is EXEC (Executable file)
Entry point 0x80482b0
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x0047c 0x0047c R E 0x1000
  LOAD           0x00047c 0x0804947c 0x0804947c 0x00104 0x00108 RW  0x1000
  DYNAMIC        0x000490 0x08049490 0x08049490 0x000c8 0x000c8 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section 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     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag
   06    



    上表給出了可執行文件的如下幾個段(segment),

PHDR: 給出了程序表自身的大小和位置,不能出現一次以上。
INTERP: 因為程序中調用了puts(在動態鏈接庫中定義),使用了動態連接庫,因此需要動態裝載器/鏈接器(ld-linux.so)
LOAD: 包括程序的指令,.text等節區都映射在該段,只讀(R)
LOAD: 包括程序的數據,.data, .bss等節區都映射在該段,可讀寫(RW)
DYNAMIC: 動態鏈接相關的信息,比如包含有引用的動態連接庫名字等信息
NOTE: 給出一些附加信息的位置和大小
GNU_STACK: 這里為空,應該是和GNU相關的一些信息

    這里的段可能包括之前的一個或者多個節區,也就是說經過鏈接之后原來的節區被重排了,并映射到了不同的段,這些段將告訴系統應該如何把它加載到內存中。

    從上表中,通過比較可執行文件(test)中擁有的節區和可重定位文件(test.o和myprintf.o)中擁有的節區后發現,鏈接之后多了一些之前沒有的節區,這些新的節區來自哪里?它們的作用是什么呢?先來通過gcc的-v參數看看它的后臺鏈接過程。

Quote:

$ gcc -v -o test test.o myprintf.o    #把可重定位文件鏈接成可執行文件
Reading specs from /usr/lib/gcc/i486-slackware-linux/4.1.2/specs
Target: i486-slackware-linux
Configured with: ../gcc-4.1.2/configure --prefix=/usr --enable-shared --enable-languages=ada,c,c++,fortran,java,objc --enable-threads=posix --enable-__cxa_atexit --disable-checking --with-gnu-ld --verbose --with-arch=i486 --target=i486-slackware-linux --host=i486-slackware-linux
Thread model: posix
gcc version 4.1.2
 /usr/libexec/gcc/i486-slackware-linux/4.1.2/collect2 --eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crt1.o /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/lib/gcc/i486-slackware-linux/4.1.2/../../../../i486-slackware-linux/lib -L/usr/lib/gcc/i486-slackware-linux/4.1.2/../../.. test.o myprintf.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crtn.o



    從上邊的演示看出,gcc在連接了我們自己的目標文件test.o和myprintf.o之外,還連接了crt1.o,crtbegin.o等額外的目標文件,難道那些新的節區就來自這些文件?
    另外gcc在進行了相關配置(./configure)后,調用了collect2,卻并沒有調用ld,通過查找gcc文檔中和collect2相關的部 分發現collect2在后臺實際上還是去尋找ld命令的。為了理解gcc默認連接的后臺細節,這里直接把collect2替換成ld,并把一些路徑換成 絕對路徑或者簡化,得到如下的ld命令以及執行的效果。

Quote:

$ ld --eh-frame-hdr \
-m elf_i386 \
-dynamic-linker /lib/ld-linux.so.2 \
-o test \
/usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o \
test.o myprintf.o \
-L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/i486-slackware-linux/lib -L/usr/lib/ -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed \
/usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/crtn.o
$ ./test
hello, world!



不出我們所料,它完美的運行了。下面通過ld的手冊(man ld)來分析一下這幾個參數。

--eh-frame-hdr

要求創建一個.eh_frame_hdr節區(貌似目標文件test中并沒有這個節區,所以不關心它)。

  • -m elf_i386

    這 里指定不同平臺上的鏈接腳本,可以通過--verbose命令查看腳本的具體內容,如ld -m elf_i386 --verbose,它實際上被存放在一個文件中(/usr/lib/ldscripts目錄下),你可以去修改這個腳本,具體如何做?請參考ld的手冊。 在后面我們將簡要提到鏈接腳本中是如何預定義變量的,以及這些預定義變量如何在我們的程序中使用。需要提到的是,如果不是交叉編譯,那么無須指定該選項。

  • -dynamic-linker /lib/ld-linux.so.2

    指定動態裝載器/鏈接器,即程序中的INTERP段中的內容。動態裝載器/連接器負責連接有可共享庫的可執行文件的裝載和動態符號連接。

  • -o test

    指定輸出文件,即可執行文件名的名字

  • /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o

    鏈 接到test文件開頭的一些內容,這里實際上就包含了.init等節區。.init節區包含一些可執行代碼,在main函數之前被調用,以便進行一些初始化操 作,在C++中完成構造函數功能,更多細節請參考資料[9]

  • test.o myprintf.o

    鏈接我們自己的可重定位文件

  • -L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/i486-slackware-linux/lib -L/usr/lib/ -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed

    鏈接libgcc庫和libc庫,后者定義有我們需要的puts函數

  • /usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/crtn.o

    鏈接到test文件末尾的一些內容,這里實際上包含了.fini等節區。.fini節區包含了一些可執行代碼,在程序退出時被執行,作一些清理工作,在C++中完成析構造函數功能。我們往往可以通過atexit來注冊那些需要在程序退出時才執行的函數。

        對于crtbegin.o和crtend.o這兩個文件,貌似完全是用來支持C++的構造和析構工作的[9],所以可以不鏈接到我們的可執行文件中,鏈接時把它們去掉看看,

    Quote:

    $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o test.o myprintf.o -L/usr/lib -lc /usr/lib/crtn.o    #后面發現不用鏈接libgcc,也不用--eh-frame-hdr參數
    $ readelf -l test

    Elf file type is EXEC (Executable file)
    Entry point 0x80482b0
    There are 7 program headers, starting at offset 52

    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
      INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /lib/ld-linux.so.2]
      LOAD           0x000000 0x08048000 0x08048000 0x003ea 0x003ea R E 0x1000
      LOAD           0x0003ec 0x080493ec 0x080493ec 0x000e8 0x000e8 RW  0x1000
      DYNAMIC        0x0003ec 0x080493ec 0x080493ec 0x000c8 0x000c8 RW  0x4
      NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

     Section 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
       03     .dynamic .got .got.plt .data
       04     .dynamic
       05     .note.ABI-tag
       06    
    $ ./test
    hello, world!



        完全可以工作,而且發現.ctors(保存著程序中全局構造函數的指針數組), .dtors(保存著程序中全局析構函數的指針數組),.jcr(未知),.eh_frame節區都沒有了,所以crtbegin.o和crtend.o應該包含了這些節區。
        而對于另外兩個文件crti.o和crtn.o,通過readelf -S查看后發現它們都有.init和.fini節區,如果我們不需要讓程序進行一些初始化和清理工作呢?是不是就可以不 鏈接這個兩個文件?試試看。

    Quote:

    $ ld  -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o test.o myprintf.o -L/usr/lib/ -lc
    /usr/lib/libc_nonshared.a(elf-init.oS): In function `__libc_csu_init':
    (.text+0x25): undefined reference to `_init'



    貌似不行,竟然有人調用了__libc_csu_init函數,而這個函數引用了_init。這兩個符號都在哪里呢?

    Quote:

    $ readelf -s /usr/lib/crt1.o | grep __libc_csu_init
        18: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND __libc_csu_init
    $ readelf -s /usr/lib/crti.o | grep _init
        17: 00000000     0 FUNC    GLOBAL DEFAULT    5 _init



        竟然是crt1.o調用了__libc_csu_init函數,而該函數卻引用了我們沒有鏈接的crti.o文件中定義的_init符號。這樣的話不鏈接 crti.o和crtn.o文件就不成了羅?不對吧,要不干脆不用crt1.o算了,看看gcc額外連接進去的最后一個文件crt1.o到底干了個啥子?

    Quote:

    $ ld  -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib/ -lc
    ld: warning: cannot find entry symbol _start; defaulting to 00000000080481a4



        這樣卻說沒有找到入口符號_start,難道crt1.o中定義了這個符號?不過它給默認設置了一個地址,只是個警告,說明test已經生成,不管怎樣先運行看看再說。

    Quote:

    $ ./test
    hello, world!
    Segmentation fault



        貌似程序運行完了,不過結束時冒出個段錯誤?可能是程序結束時有問題,用gdb調試看看:

    Quote:

    $ gcc -g -c test.c myprintf.c    #產生目標代碼, 非交叉編譯,不指定-m也可以鏈接成功,所以下面可以去掉-m參數
    $ ld -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib -lc
    ld: warning: cannot find entry symbol _start; defaulting to 00000000080481d8   
    $ ./test
    hello, world!
    Segmentation fault
    $ gdb ./test
    ...
    (gdb) l
    1       #include "test.h"
    2
    3       int main()
    4       {
    5               myprintf();
    6               return 0;
    7       }
    (gdb) break 7            #在程序的末尾設置一個斷點
    Breakpoint 1 at 0x80481bf: file test.c, line 7.
    (gdb) r                    #程序都快結束了都沒問題,怎么會到最后出個問題呢?
    Starting program: /mnt/hda8/Temp/c/program/test
    hello, world!

    Breakpoint 1, main () at test.c:7
    7       }
    (gdb) n                    #單步執行看看,怎么下面一條指令是0x00000001,肯定是程序退出以后出了問題
    0x00000001 in ?? ()
    (gdb) n                    #誒,當然找不到邊了,都跑到0x00000001了
    Cannot find bounds of current function
    (gdb) c                    #原來是這么回事,估計是return 0返回之后出問題了,看看它的匯編去。
    Continuing.

    Program received signal SIGSEGV, Segmentation fault.
    0x00000001 in ?? ()
    $ gcc -S test.c #產生匯編代碼
    $ cat test.s    #后面就這么幾條指令,難不成ret返回有問題,不讓它ret返回,把return改成_exit直接進入內核退出
    ...
            call    myprintf
            movl    $0, %eax
            addl    $4, %esp
            popl    %ecx
            popl    %ebp
            leal    -4(%ecx), %esp
            ret
    ...
    $ vim test.c
    $ cat test.c    #就把return語句修改成_exit了。
    #include "test.h"
    #include <unistd.h> /* _exit */

    int main()
    {
            myprintf();
            _exit(0);
    }
    $ gcc -g -c test.c myprintf.c
    $  ld -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib -lc
    ld: warning: cannot find entry symbol _start; defaulting to 00000000080481d8
    $ ./test    #竟然好了,再看看匯編有什么不同
    hello, world!
    $ gcc -S test.c
    $ cat test.s    #貌似就把ret指令替換成了_exit函數調用,直接進入內核,然內核讓處理了,那為什么ret有問題呢?
    ...
            call    myprintf
            subl    $12, %esp
            pushl   $0
            call    _exit
    ...
    $ gdb ./test    #把代碼改回去(改成return 0;),再調試看看調用main函數返回時的下一條指令地址eip
    ...
    (gdb) l
    warning: Source file is more recent than executable.
    1       #include "test.h"
    2
    3       int main()
    4       {
    5               myprintf();
    6               return 0;
    7       }
    (gdb) break 5
    Breakpoint 1 at 0x80481b5: file test.c, line 5.
    (gdb) break 7
    Breakpoint 2 at 0x80481bc: file test.c, line 7.
    (gdb) r
    Starting program: /mnt/hda8/Temp/c/program/test

    Breakpoint 1, main () at test.c:5
    5               myprintf();
    (gdb) x/8x $esp    #發現0x00000001剛好是之前我們調試時看到的程序返回后的位置,即eip,說明程序在初始化的時候
                    #這個eip就是錯誤的。為什么呢?因為我們根本沒有鏈接進來初始化的代碼,而是在編譯器自己給我們
                    #初始化了一個程序入口即00000000080481d8,也就是說,沒有任何人調用main,main不知道返回哪里去
                    #所以,我們直接讓main結束時進入內核調用_exit而退出則不會有問題
    0xbf929510:     0xbf92953c      0x080481a4      0x00000000      0xb7eea84f
    0xbf929520:     0xbf92953c      0xbf929534      0x00000000      0x00000001 



        通過上面的演示和解釋發現只要把return語句修改為_exit語句,程序即使不鏈接任何額外的目標代碼都可以正常運行(原因是不連接那些額外的文件時 相當于沒有進行初始化操作,如果在程序的最后執行ret匯編指令,程序將無法獲得正確的eip,從而無法進行后續的動作)。但是為什么會有“找不到 _start符號”的警告呢?通過readelf -s查看crt1.o發現里頭有這個符號,并且crt1.o引用了main這個符號,是不是意味著會從_start進入main呢?是不是程序入口是 _start,而并非main呢?

        先來看看剛才提到的鏈接器的默認鏈接腳本(ld -m elf_386 --verbose),它告訴我們程序的入口(entry)是_start,而一個可執行文件必須有一個入口地址才能運行,所以這就是說明了為什么ld一 定要提示我們“_start找不到”,找不到以后就給默認設置了一個地址。

    Quote:

    $ ld --verbose  | grep ^ENTRY    #非交叉編譯,可不用-m參數;ld默認找_start入口,并不是main哦!
    ENTRY(_start)



        原來是這樣,程序的入口(entry)竟然不是main函數,而是_start。那干脆把匯編里頭的main給改掉算了,看行不行?

    Quote:

    $ cat test.c
    #include "test.h"
    #include <unistd.h>     /* _exit */

    int main()
    {
            myprintf();
            _exit(0);
    }
    $ gcc -S test.c
    $ sed -i -e "s#main#_start#g" test.s    #把匯編中的main全部修改為_start,即修改程序入口為_start
    $ gcc -c test.s myprintf.c
    $ ld -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib/ -lc    #果然沒問題了 :-)
    $ ./test
    hello, world!



        _start竟然是真正的程序入口,那在有main的情況下呢?為什么在_start之后能夠找到main呢?這個看看alert7大叔的"Before main分析"[5]吧,這里不再深入介紹。總之呢,通過修改程序的return語句為_exit(0)和修改程序的入口為_start,我們的代碼不鏈接gcc默認鏈 接的那些額外的文件同樣可以工作得很好。并且打破了一個學習C語言以來的常識:main函數作為程序的主函數,是程序的入口,實際上則不然。

        再補充一點內容,在ld的鏈接腳本中,有一個特別的關鍵字PROVIDE,由這個關鍵字定義的符號是ld的預定義字符,我們可以在C語言函數中擴展它們后直接使用。這些特別的符號可以通過下面的方法獲取,

    Quote:

    $ ld --verbose | grep PROVIDE | grep -v HIDDEN
      PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
      PROVIDE (__etext = .);
      PROVIDE (_etext = .);
      PROVIDE (etext = .);
      _edata = .; PROVIDE (edata = .);
      _end = .; PROVIDE (end = .);


        這里面有幾個我們比較關心的,第一個是程序的入口地址__executable_start,另外三個是etext,edata,end,分別對應程序的 代碼段(text)、初始化數據(data)和未初始化的數據(bss)(可以參考資料[6]和man etext),如何引用這些變量呢?看看這個例子。


    Code:

    [Ctrl+A Select All]



        到這里,程序鏈接過程的一些細節都介紹得差不多了。在《動態符號鏈接的細節》中將主要介紹ELF文件的動態符號鏈接過程。

    本節參考資料

    [1] An beginners guide to compiling programs under Linux.
    http://www.luv.asn.au/overheads/compile.html
    [2] gcc manual
    http://gcc.gnu.org/onlinedocs/gcc-4.2.2/gcc/
    [3] A Quick Tour of Compiling, Linking, Loading, and Handling Libraries on Unix
    http://efrw01.frascati.enea.it/Software/Unix/IstrFTU/cern-cnl-2001-003-25-link.html
    [4] Unix 目標文件初探
    http://www.ibm.com/developerworks/cn/aix/library/au-unixtools.html
    [5] Before main()分析
    http://www.xfocus.net/articles/200109/269.html
    [6] A Process Viewing Its Own /proc/<PID>/map Information
    http://www.linuxforums.org/forum/linux-kernel/51790-process-viewing-its-own-proc-pid-map-information.html
  • posted on 2008-03-14 15:24 隨意門 閱讀(7544) 評論(3)  編輯 收藏 引用

    評論

    # re: GCC編譯背后(第二部分:匯編和鏈接) 2008-03-14 15:25 隨意門

    # 參考資料

    [1] UNIX環境高級編程
    [2] Linux Kernel Primer
    [3] Linux Kernel Interface(2.4)
    http://www.faqs.org/docs/kernel_2_4/lki.html
    [4] Understanding ELF using readelf and objdump
    http://www.linuxforums.org/misc/understanding_elf_using_readelf_and_objdump.html
    [5] Study of ELF loading and relocs
    http://netwinder.osuosl.org/users/p/patb/public_html/elf_relocs.html
    [6] ELF file format and ABI
    http://www.x86.org/ftp/manuals/tools/elf.pdf
    http://www.muppetlabs.com/~breadbox/software/ELF.txt
    (北大OS實驗室)http://162.105.203.48/web/gaikuang/submission/TN05.ELF.Format.Summary.pdf
    (alert7 大牛翻譯)http://www.xfocus.net/articles/200105/174.html
    [7] 關于GCC方面的論文,請查看歷年的會議論文集
    http://www.gccsummit.org/2005/2005-GCC-Summit-Proceedings.pdf
    http://www.gccsummit.org/2006/2006-GCC-Summit-Proceedings.pdf
    [8] The Linux GCC HOW TO
    http://www.faqs.org/docs/Linux-HOWTO/GCC-HOWTO.html
    [9] ELF: From The Programmer's Perspective
    http://linux.jinr.ru/usoft/WWW/www_debian.org/Documentation/elf/elf.html
    [10] C/C++程序編譯步驟詳解
    http://www.xxlinux.com/linux/article/development/soft/20070424/8267.html
    [11] GNU binutils小結
    http://hi.baidu.com/skxzz/blog/item/d2599f003fdd1c12738b6561.html
    [12] C語言常見問題集
    http://c-faq-chn.sourceforge.net/ccfaq/index.html
    [13] 使用BFD操作ELF
    http://elfhack.whitecell.org/mydocs/use_bfd.txt
    [14] bfd document
    http://sourceware.org/binutils/docs/bfd/index.html
    [15] UNIX/LINUX 平臺可執行文件格式分析
    http://blog.chinaunix.net/u/19881/showart_215242.html
      回復  更多評論    

    # re: GCC編譯背后(第二部分:匯編和鏈接) 2010-03-28 11:05 HERNANDEZChristi33

    The <a href="http://lowest-rate-loans.com/topics/credit-loans">credit loans</a> suppose to be useful for guys, which want to start their business. By the way, this is comfortable to receive a college loan.
      回復  更多評論    

    # re: GCC編譯背后(第二部分:匯編和鏈接) 2010-05-28 20:48 freelance writer

    If I were you, I would look for very good freelance writing to get a lot of the best notes close to this topic!
      回復  更多評論    

    # re: GCC編譯背后(第二部分:匯編和鏈接) 2011-06-26 03:42 essays online

    Study system seems to be influenced by educational law. Nevertheless, students still need to complete essay papers. I opine that students ought not to be confused! The custom writing service can offer supreme quality of custom writing! I solve my academic papers writing troubles in such way as well.
      回復  更多評論    
    青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲卡通欧美制服中文| 亚洲欧美日韩精品在线| 欧美福利视频在线| 免费亚洲一区| 日韩亚洲欧美中文三级| 一级成人国产| 国产色爱av资源综合区| 久久九九国产| 男人的天堂亚洲| 在线天堂一区av电影| 这里只有精品电影| 黄色在线一区| 亚洲欧洲综合另类在线| 欧美三级网址| 久久综合久久综合久久| 欧美黄色成人网| 欧美中文字幕视频在线观看| 久久精品国产亚洲精品| 亚洲精品影视在线观看| 亚洲激情视频在线| 久久国产主播| 久久美女性网| 亚洲一区二区三区在线看| 午夜精品视频在线观看| 最新国产成人av网站网址麻豆 | 国内久久视频| 亚洲精品乱码久久久久久按摩观| 国产精品久久久久久久久久直播| 女人香蕉久久**毛片精品| 欧美日韩高清在线| 男同欧美伦乱| 国产欧美一区二区三区视频 | 久久久久欧美| 国产精品yjizz| 亚洲高清网站| 激情亚洲网站| 亚洲自拍电影| 亚洲视频狠狠| 欧美激情按摩在线| 另类av导航| 国产欧美精品日韩区二区麻豆天美| 亚洲国产成人不卡| 亚洲成色www久久网站| 中文欧美日韩| 亚洲日本欧美在线| 欧美77777| 卡通动漫国产精品| 国产午夜精品久久久久久久| 亚洲最新合集| 9l视频自拍蝌蚪9l视频成人| 久久久久国产精品厨房| 欧美一区亚洲二区| 国产精品久久久久久久7电影| 亚洲欧洲一区二区在线播放| 亚洲动漫精品| 女同性一区二区三区人了人一| 久久久国产精品一区二区中文 | 欧美激情精品久久久久久蜜臀| 久热这里只精品99re8久| 国产亚洲欧洲一区高清在线观看 | 老鸭窝毛片一区二区三区| 国产主播一区二区| 久久黄色影院| 麻豆国产va免费精品高清在线| 国产一区二区欧美| 久久精品女人| 男女激情视频一区| 亚洲伦理在线| 欧美日韩国产综合一区二区| 99在线精品观看| 亚洲一区中文| 国产日韩欧美不卡在线| 久久黄色级2电影| 欧美成年人网| 亚洲最新视频在线| 欧美日韩一区二区三区在线看| 亚洲精品国精品久久99热一| 一区二区三区精品| 国产精品一区二区三区免费观看| 亚洲欧美影院| 麻豆成人av| 欧美日本韩国一区二区三区| 亚洲自拍16p| 国产日韩欧美综合在线| 久久视频一区二区| 亚洲日本成人| 亚洲欧美日韩一区二区三区在线观看| 国产精品私房写真福利视频 | 日韩视频在线观看国产| 亚洲欧美在线x视频| 国模 一区 二区 三区| 美女脱光内衣内裤视频久久网站| 亚洲精品乱码视频| 久久黄色网页| 一区二区三区.www| 国产亚洲欧美一级| 欧美日韩福利| 久久久91精品国产一区二区精品| 亚洲第一区在线观看| 亚洲视频免费看| 亚洲大黄网站| 国产精品美女www爽爽爽| 狂野欧美性猛交xxxx巴西| 一本色道久久综合亚洲二区三区| 欧美在线综合| 99在线精品免费视频九九视| 国产亚洲欧美一区在线观看| 欧美日韩国产丝袜另类| 久久久久久久尹人综合网亚洲| 夜夜嗨一区二区| 欧美激情aⅴ一区二区三区| 亚洲欧美日韩国产| 一本久道久久久| 激情综合网激情| 国产精品入口夜色视频大尺度| 玖玖综合伊人| 欧美专区中文字幕| 亚洲永久字幕| 亚洲网站啪啪| 日韩视频在线观看国产| 亚洲第一色在线| 欧美jjzz| 久久亚洲综合色一区二区三区| 亚洲一区在线看| 一区二区不卡在线视频 午夜欧美不卡'| 国产一区二区三区久久精品| 国产精品免费aⅴ片在线观看| 欧美精品日韩一区| 欧美高清在线视频| 欧美成人午夜激情| 老司机67194精品线观看| 午夜精品久久| 小黄鸭精品aⅴ导航网站入口| 一区二区三区 在线观看视| 亚洲欧洲日产国产综合网| 欧美激情精品久久久久久变态| 久久综合电影一区| 美国三级日本三级久久99| 久久久久久国产精品mv| 久久精品三级| 久久综合精品一区| 鲁大师影院一区二区三区| 久久久夜夜夜| 欧美电影免费观看高清| 亚洲第一网站| 亚洲免费精彩视频| 一道本一区二区| 亚洲欧美日韩一区| 久久国产精品99精品国产| 久久成人精品一区二区三区| 久久成人免费网| 久久一区二区三区国产精品| 久久免费国产精品| 免费成人av资源网| 欧美视频在线不卡| 国产精品美女久久久久久久| 国产精品综合| 99热在这里有精品免费| 亚洲欧洲av一区二区三区久久| 日韩视频中午一区| 亚洲一级特黄| 久久久久久久网| 欧美成年人网| 国产精品久久久久999| 韩日视频一区| 日韩视频免费观看高清在线视频| 亚洲特色特黄| 久久久久青草大香线综合精品| 欧美高清在线播放| 99在线|亚洲一区二区| 欧美一区视频| 欧美日韩国语| 国模套图日韩精品一区二区| 亚洲精品护士| 久久成人这里只有精品| 噜噜噜91成人网| 亚洲视频在线观看| 老鸭窝毛片一区二区三区| 国产精品久久久久国产a级| 影音先锋久久久| 亚洲欧美国产高清va在线播| 另类图片国产| 亚洲午夜精品17c| 欧美成人首页| 激情综合自拍| 亚洲欧美日产图| 亚洲欧洲精品一区二区精品久久久| 亚洲欧美日韩成人高清在线一区| 卡一卡二国产精品| 国产亚洲精品高潮| 一本色道久久88亚洲综合88| 久久久久国产精品麻豆ai换脸| 亚洲欧洲综合另类在线| 久久久av水蜜桃| 国产精品一二一区| 亚洲一区二区三区乱码aⅴ蜜桃女 亚洲一区二区三区乱码aⅴ | 久久精品国产一区二区三区| 国产精品久久网| 夜夜精品视频| 欧美激情一区二区三区不卡|