目录

簡介

開發

# git clone git://sourceware.org/git/binutils-gdb.git
$ wget http://ftp.gnu.org/gnu/binutils/binutils-2.21.1.tar.bz2; tar xvf binutils-2.21.tar.bz2
# 只編譯 gas
$ mkdir build; cd build
$ ../binutils-2.21.1/configure --enable-as --prefix=$INSTALL
$ make; make install

主要目錄:

以 xtensa 為例 (xtensa 需要處理 VLIW。也可以參考 Add support for Andes NDS32):

開發流程:

使用

# -d 只反匯編包含指令的部分,-D 對所有的段進行反匯編,-s 把所有段的內容以 16 進制的方式印出。
$ objdump -d hello
# 只列出特定 function 的組語。
$ objdump -d hello | grep -A20 main.:
# 編譯時加上 -g,顯示源碼。
$ objdump -S hello
# -h 顯示段表,-p 顯示 program header
$ objdump -h hello
# -t 顯示 symbol table,-T 顯示 dynamic symbol table
$ objdump -t hello
# 顯示 ELF header 詳細訊息
$ readelf -h hello
$ readelf -h hello | less
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:                           ARM
  Version:                           0x1
  Entry point address:               0x8309
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4516 (bytes into file)
  Flags:                             0x5000002, has entry point, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27

ELF 格式

ELF 格式最主要精神在於用 header 描述 ELF 文件相關資訊,主要有三個 header (ELF文件):

目標文件需要鏈接器做進一步處理,所以一定有 section header table;可執行文件需要加載運行,所以一定有 program header table;而共享庫既要加載運行,又要在加載時做動態鏈接,所以既有 section header table 又有 program header table。

有幾個特殊段:

段頭

符號表

字符串表

重定位

除錯資訊

匯編器

匯編器 (Assembler ),讀入匯編代碼,輸出目的檔 (object file) 和清單檔 (listing file)。處理匯編指令、巨集和匯編指示符。

原理

可以從第二階段,第一階段至第零階段依次實現匯編,匯編指示符和巨集功能。

問題

實現

術語

鏈結器

鏈結器 (Linker) 負責將輸入目的檔中的各個段 (section) 合併,輸出至最終執行檔。鏈結器在各個輸出段之間,會依照對齊要求,插入填充値。另外,鏈結器在合併各輸入段時,也會依照對齊要求,插入填充値。

原理

鏈結腳本

  1. 顯示 ld 預設 linker script 內容。
    $ ld -verbose
  2. 底下是鏈結器預設腳本概述。參考底下文件:
    • 对.lds连接脚本文件的分析
      ENTRY(_start) // 指定入口函式
      
      SECTIONS // 其中的 SECTIONS 命令指示 linker 如何合併目標文件的 section
      {
        // 定義在目標文件被引用,但未被所有目標文件定義的符號。
        // SEGMENT_START 是內建函式,返回指定 segment 的基底位址。如果命令行參數沒有指定該 segment 的基底位址,返回預設值。
        // . 這個特殊符號代表目前位址計數器的值。
        PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
        
        // 將所有輸入目標文件的 .interp 合併成一個 .interp 至輸出目標文件。
        .interp         : { *(.interp) }
      
        // .init 是在進入 main 函式之前必須要執行的初始化,與之對應的是 .fini。
        // 如果命令行參數有 --gc-sections,ld 會去掉輸入目標文件中被視為無用的 section。
        // KEEP 代表保留該 section。
        // [=FILLEXP] 代表該段空隙處以 FILEEXP 填滿。 
        .init           :
        {
          KEEP (*(.init))
        } =0x90909090
      
        // 在 C++ 中,全局建構子必須在 main 函式之前被呼叫,全局解構子必須在 main 返回後被呼叫。
        // 透過 crtbegin.o 和 crtend.o,以及 .ctors 和 .dtors 可達成以上目的。  
        .ctors          :
        {
          KEEP (*crtbegin.o(.ctors))
          KEEP (*crtbegin?.o(.ctors))
          // 在對 .ctors 排序之前,不包含 crtend 的 .ctors。
           // crtend 的 .ctors 包含 ctors 結尾的標記,必須擺在最後。
           KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
          KEEP (*(SORT(.ctors.*)))
          KEEP (*(.ctors))
        }
        // .bss 預留空間給未初始化的全局變量和局部靜態變量。
        .bss            :
        {
         *(.dynbss)
         *(.bss .bss.* .gnu.linkonce.b.*)
         *(COMMON)
         // ALIGN(exp,align) 
         . = ALIGN(. != 0 ? 64 / 8 : 1); 
        }
        // ALIGN(align)
        . = ALIGN(64 / 8);
        _end = .; PROVIDE (end = .);
      }

VMA 和 LMA

對於 section 來說,有虚拟地址 (Virtual Memory Address) 或加載地址 (Load Memory Address) 之分。虛擬地址代表該 section 被執行時所在的位址; 加載地址代表該 section 被加載時所在的位址。一般情況下,虚拟地址與加載地址具有相同的值。但對於嵌入式系統來說,可能會出現加載該 section 至 ROM (flash),等到執行時再把該 section 複製到 RAM (內存) 再開始執行。

嵌入式系統的鏈結腳本範例。

// MEMORY 描述目標平台內存區塊起始位址和長度,也可以指定區塊的存取權限。
// ld 預設可以使用內存任意位址。MEMORY 可以指定 ld 使用那些區塊。
MEMORY
{
   // 定義 rom 和 ram 兩個內存區塊,並指定其起始位址和長度。
   rom : ORIGIN = 0x0,    LENGTH = 32K
   ram : ORIGIN = 0x8000, LENGTH = 32K
}

SECTIONS
{
   .loader :
   {
      *(.loader)
   } > rom // .loader 段放在 rom 區塊。

   .text ram_start :
   {
      text_start = . ;
      *(.text)
      text_end = . ;
   } AT > rom // 指定该 section 加载地址的范围, 
}

對齊和填充

位址無關代碼

位址無關代碼 (Position Independent Code (PIC))。

鏈結與裝載

外部連結

其它

# 將映像檔移至 4G 虛擬位址以上。-fPIE 是編譯器選項,-pie 是鏈結器選項。
$ gcc -Wl,-Ttext-segment=0x100000000 -fPIE -pie hello.c -o hello
$ ./hello
$ cat /proc/`pidof hello`/maps

Before adding a PIE mode the program's executable can't be placed at randomly address in memory, only PIC dynamic libraries can be relocated to random. http://stackoverflow.com/questions/2463150/fpie-position-independent-executable-option-gcc-ld

映射文件

       -M
       --print-map
           Print a link map to the standard output.  A link map provides information about the link, including the following:

           ·  Where object files are mapped into memory.

           ·  How common symbols are allocated.

           ·  All archive members included in the link, with a mention of the symbol which caused the archive member to be brought in.

           ·  The values assigned to symbols.

               Note - symbols whose values are computed by an expression which involves a reference to a previous value of the same
               symbol may not have correct result displayed in the link map.  This is because the linker discards intermediate results
               and only retains the final value of an expression.  Under such circumstances the linker will display the final value
               enclosed by square brackets.  Thus for example a linker script containing:

                          foo = 1
                          foo = foo * 4
                          foo = foo + 8

               will produce the following output in the link map if the -M option is used:

                          0x00000001                foo = 0x1
                          [0x0000000c]                foo = (foo * 0x4)
                          [0x0000000c]                foo = (foo + 0x8)

               See Expressions for more information about expressions in linker scripts.
; section                vma       size                     lma
  .ovly0          0x00100100      0x1f4 load address 0x00108000

轉成二進制檔

覆蓋

計算內存使用量

鏈結順序

二進制檔嵌入

弱符號和弱參考

語言相關段

鏈結器相關環境變數

錯誤訊息

外部連結