ELF格式文件分析以及運用


基於本文的一個實踐《使用Python分析ELF文件優化Flash和Sram空間的案例》。

1.背景

ELF是Executable and Linkable Format縮寫,其官方規范在《Tools Interface Standard Executable and Linkable Format Specification version 1.2》分為三部分:Executable and Linking Format;Processor Specific(Intel Architecture);Operating System Specific(UNIX System V Release 4)。重點關注第一部分通用標准:Object Files和Program Loading and Dynamic Linking。前者可以說是靜態,后者是動態,程序加載和動態鏈接。

ELF文件是二進制格式並不能直接讀取,可以通過readelf工具來進行分析。所以在分析ELF文件的過程中會穿插使用readelf。

最后介紹可執行文件在運行時不同部分的加載狀態和動態鏈接過程。

 

ELF即Executable and Linkable Format,可執行鏈接格式,ELF格式的文件用於存儲Linux程序。

ELF文件(目標文件)格式主要三種:

  • 可重定向文件:文件保存着代碼和適當的數據,用來和其他的目標文件一起來創建一個可執行文件或者是一個共享目標文件。(目標文件或者靜態庫文件,即linux通常后綴為.a和.o的文件)
  • 可執行文件:文件保存着一個用來執行的程序。(例如bash,gcc等)
  • 共享目標文件:共享庫。文件保存着代碼和合適的數據,用來被下連接編輯器和動態鏈接器鏈接。(linux下后綴為.so的文件。)
    目標文件既要參與程序鏈接又要參與程序執行:

一般的 ELF 文件包括三個索引表:ELF header,Program header table,Section header table。

  • ELF header:在文件的開始,保存了路線圖,描述了該文件的組織情況。
  • Program header table:告訴系統如何創建進程映像。用來構造進程映像的目標文件必須具有程序頭部表,可重定位文件不需要這個表。
  • Section header table:包含了描述文件節區的信息,每個節區在表中都有一項,每一項給出諸如節區名稱、節區大小這類信息。用於鏈接的目標文件必須包含節區頭部表,其他目標文件可以有,也可以沒有這個表。

參考文檔:《linux第三次實踐:ELF文件格式分析

ELF文件相關的結構體定義在/usr/include/elf.h中,下面借助工具讀取信息,和結構體對比分析。

 

2. readelf使用介紹

使用readelf -h可以得到使用方法:

Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       Display the ELF file header
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -g --section-groups    Display the section groups
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S
  -s --syms              Display the symbol table
     --symbols           An alias for --syms
  --dyn-syms             Display the dynamic symbol table
  -n --notes             Display the core notes (if present)
  -r --relocs            Display the relocations (if present)
  -u --unwind            Display the unwind info (if present)
  -d --dynamic           Display the dynamic section (if present)
  -V --version-info      Display the version sections (if present)
  -A --arch-specific     Display architecture specific information (if any)
  -c --archive-index     Display the symbol/file index in an archive
  -D --use-dynamic       Use the dynamic section info when displaying symbols
  -x --hex-dump=<number|name>
                         Dump the contents of section <number|name> as bytes
  -p --string-dump=<number|name>
                         Dump the contents of section <number|name> as strings
  -R --relocated-dump=<number|name>
                         Dump the contents of section <number|name> as relocated bytes
  -w[lLiaprmfFsoRt] or
  --debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
               =frames-interp,=str,=loc,=Ranges,=pubtypes,
               =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
               =addr,=cu_index]
                         Display the contents of DWARF2 debug sections
  --dwarf-depth=N        Do not display DIEs at depth N or greater
  --dwarf-start=N        Display DIEs starting with N, at the same depth
                         or deeper
  -I --histogram         Display histogram of bucket list lengths
  -W --wide              Allow output width to exceed 80 characters
  @<file>                Read options from <file>
  -H --help              Display this information
  -v --version           Display the version number of readelf

  

3. ELF文件解釋(Linking View)

這里主要通過readelf工具靜態分析ELF文件,從(Figure 1-1. Object File Format)可知它的組成大概有如下部分。

總共有三種類型的ELF文件:可重定位文件、共享文件和可執行文件。

可重定位文件(Relocatable file):這是由匯編器匯編生成的 .o 文件。后面的鏈接器(link editor)拿一個或一些 Relocatable object files 作為輸入,經鏈接處理后,生成一個可執行的對象文件 (Executable file) 或者一個可被共享的對象文件(Shared object file)。我們可以使用 ar 工具將眾多的 .o Relocatable object files 歸檔(archive)成 .a 靜態庫文件。如何產生 Relocatable file,你應該很熟悉了,請參見我們相關的基本概念文章和JulWiki。另外,可以預先告訴大家的是我們的內核可加載模塊 .ko 文件也是 Relocatable object file。

共享文件(Shares Object file):這些就是所謂的動態庫文件,也即 .so 文件。如果拿前面的靜態庫來生成可執行程序,那每個生成的可執行程序中都會有一份庫代碼的拷貝。如果在磁盤中存儲這些可執行程序,那就會占用額外的磁盤空間;另外如果拿它們放到Linux系統上一起運行,也會浪費掉寶貴的物理內存。如果將靜態庫換成動態庫,那么這些問題都不會出現。動態庫在發揮作用的過程中,必須經過兩個步驟:

a) 鏈接編輯器(link editor)拿它和其他Relocatable object file以及其他shared object file作為輸入,經鏈接處理后,生存另外的 shared object file 或者 executable file

b) 在運行時,動態鏈接器(dynamic linker)拿它和一個Executable file以及另外一些 Shared object file 來一起處理,在Linux系統里面創建一個進程映像

可執行文件(Executable file):這我們見的多了。文本編輯器vi、調式用的工具gdb、播放mp3歌曲的軟件mplayer等等都是Executable object file。你應該已經知道,在我們的 Linux 系統里面,存在兩種可執行的東西。除了這里說的 Executable object file,另外一種就是可執行的腳本(如shell腳本)。注意這些腳本不是 Executable object file,它們只是文本文件,但是執行這些腳本所用的解釋器就是 Executable object file,比如 bash shell 程序。

image

使用readelf -a xxxx可以看個全貌,實際的顯示的順序和Linking View稍有不同。首先是ELF Header;然后是Section Headers和Program Headers,再然后是Section to Segment mapping的映射表;最后是一系列Section的詳細內容。

ELF Header:
   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
……

Section Headers:
……

Program Headers:
……

Section to Segment mapping:
……

Dynamic section at offset 0xe28 contains 24 entries:
……

Relocation section '.rela.dyn' at offset 0x3e8 contains 1 entries:
……

Relocation section '.rela.plt' at offset 0x400 contains 4 entries:
……

Symbol table '.dynsym' contains 6 entries:
……

Symbol table '.symtab' contains 81 entries:

……

ELF header放在文件開頭顯示了整個ELF文件的概況,Sections包含了一系列從Linking View角度來看的對象文件信息,包含指令、數據、符號表、重定位信息等等。

Program headers是提供給系統創建進程的依據,可執行文件必須包含Program headers用以創建進程。

3.1 ELF Header

ELF頭的結構體在include/uapi/linux/elf.h中定義,包含32位和64位兩種。結構體成員名一致,只是成員數據類型不盡相同。所以就取elf64_hdr。

typedef struct elf64_hdr {
   unsigned char    e_ident[EI_NIDENT];    /* ELF "magic number" */
   Elf64_Half e_type;
   Elf64_Half e_machine;
   Elf64_Word e_version;
   Elf64_Addr e_entry;        /* Entry point virtual address */
   Elf64_Off e_phoff;        /* Program header table file offset */
   Elf64_Off e_shoff;        /* Section header table file offset */
   Elf64_Word e_flags;
   Elf64_Half e_ehsize;
   Elf64_Half e_phentsize;
   Elf64_Half e_phnum;
   Elf64_Half e_shentsize;
   Elf64_Half e_shnum;
   Elf64_Half e_shstrndx;
} Elf64_Ehdr;

使用hexdump xxx –s 0 –n 64結果如下:

0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0002 003e 0001 0000 03e0 0040 0000 0000
0000020 0040 0000 0000 0000 1c50 0000 0000 0000
0000030 0000 0000 0040 0038 0009 0040 001f 001c
0000040

通過readelf –h xxx,所以ELF Header主要內容是定義了ELF Header大小,Program Headers偏移、數目和大小、Section Headers偏移、數目和大小。據此就可以分析整個ELF文件。

ELF Header:
   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 -----------------e_ident
   Class:                             ELF64
   Data:                              2's complement, little endian
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
   Type:                              EXEC (Executable file)----------------------------e_type
   Machine:                           Advanced Micro Devices X86-64----------------e_machine,機器類型。
   Version:                           0x1-------------------------------------------------e_version
   Entry point address:               0x4004e0--------------------------------------e_entry
   Start of program headers:          64 (bytes into file)--------------------------e_phoff,可知Program Headers緊接着ELF Header。
   Start of section headers:          8784 (bytes into file)-------------------------e_shoff,Section Headers的偏移。
   Flags:                             0x0---------------------------------------------------e_flags
   Size of this header:               64 (bytes)---------------------------------------e_ehsize,ELF Header的大小。
   Size of program headers:           56 (bytes)------------------------------------e_phentsize,Program Headers的大小。
   Number of program headers:         9--------------------------------------------e_phnum,9個Program Headers

.
   Size of section headers:           64 (bytes)--------------------------------------e_shentsize,一個Section Headers大小。
   Number of section headers:         31--------------------------------------------e_shnum,Section Headers個數。
   Section header string table index: 28-------------------------------------------e_shstrndx,StringTable在Section Headers中的index。

將結構體成員名和ELF Header對照一看,就能知道大概。就是e_ident需要再分解一下:

#define    EI_MAG0        0        /* e_ident[] indexes */
#define    EI_MAG1        1
#define    EI_MAG2        2
#define    EI_MAG3        3-------------------------------------------前四個字節為固定的ELF標識。
#define    EI_CLASS    4---------------------------------------------表示當前ELF文件類型,02為ELF64,01為ELF32。
#define    EI_DATA        5-------------------------------------------Endian類型,01為LSB,02為MSB。
#define    EI_VERSION    6------------------------------------------Version??好像沒多大意義。
#define    EI_OSABI    7---------------------------------------------這里不是所有的系統都有,ABI(Application Binary Interface)應用程序二進制接口。
#define    EI_PAD        8--------------------------------------------后面都是一些填充信息。

3.2 Section Headers

同樣的結合elf64_shdr和readelf讀取信息對照:

typedef struct elf64_shdr {
   Elf64_Word sh_name;        /* Section name, index in string tbl */
   Elf64_Word sh_type;        /* Type of section */
   Elf64_Xword sh_flags;        /* Miscellaneous section attributes */
   Elf64_Addr sh_addr;        /* Section virtual addr at execution */
   Elf64_Off sh_offset;        /* Section file offset */
   Elf64_Xword sh_size;        /* Size of section in bytes */
   Elf64_Word sh_link;        /* Index of another section */
   Elf64_Word sh_info;        /* Additional section information */
   Elf64_Xword sh_addralign;    /* Section alignment */
   Elf64_Xword sh_entsize;    /* Entry size if section holds table */
} Elf64_Shdr;

readelf –S xxx,下面每段的解釋可以參照《ELF-64 Object File Format》的Table12-Table13:

image

image

There are 31 section headers, starting at offset 0x2250:----------------------從ELF Header的e_shnum和e_shoff可知偏移為31和8784。

Section Headers:
   [Nr] Name              Type             Address           Offset
        Size              EntSize          Flags  Link  Info  Align
   [ 0]                   NULL             0000000000000000  00000000
        0000000000000000  0000000000000000           0     0     0
   [ 1] .interp           PROGBITS         0000000000400238  00000238--------------Program interpreter path name
        000000000000001c  0000000000000000   A       0     0     1
   [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
        0000000000000020  0000000000000000   A       0     0     4
   [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
        0000000000000024  0000000000000000   A       0     0     4
   [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
        000000000000001c  0000000000000000   A       5     0     8
   [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8-------------Symbol table for dynamic linking
        0000000000000090  0000000000000018   A       6     1     8
   [ 6] .dynstr           STRTAB           0000000000400348  00000348---------------String table for .dynamic section
        000000000000005f  0000000000000000   A       0     0     1
   [ 7] .gnu.version      VERSYM           00000000004003a8  000003a8
        000000000000000c  0000000000000002   A       5     0     2
   [ 8] .gnu.version_r    VERNEED          00000000004003b8  000003b8
        0000000000000030  0000000000000000   A       6     1     8
   [ 9] .rela.dyn         RELA             00000000004003e8  000003e8--------------------可重定位
        0000000000000018  0000000000000018   A       5     0     8
   [10] .rela.plt         RELA             0000000000400400  00000400--------------------可重定位
        0000000000000060  0000000000000018  AI       5    24     8
   [11] .init             PROGBITS         0000000000400460  00000460-------------------進程初始化代碼,編譯器自動添加
        000000000000001a  0000000000000000  AX       0     0     4
   [12] .plt              PROGBITS         0000000000400480  00000480-------------------Procedure linkage table
        0000000000000050  0000000000000010  AX       0     0     16
   [13] .plt.got          PROGBITS         00000000004004d0  000004d0
        0000000000000008  0000000000000000  AX       0     0     8
   [14] .text             PROGBITS         00000000004004e0  000004e0------------------Executable code,可執行程序代碼
        0000000000000292  0000000000000000  AX       0     0     16
   [15] .fini             PROGBITS         0000000000400774  00000774-------------------進程去初始化代碼,編譯器自動添加
        0000000000000009  0000000000000000  AX       0     0     4
   [16] .rodata           PROGBITS         0000000000400780  00000780-----------------Read-only data(constants and literals)
        0000000000000004  0000000000000004  AM       0     0     4
   [17] .eh_frame_hdr     PROGBITS         0000000000400784  00000784
        0000000000000034  0000000000000000   A       0     0     4
   [18] .eh_frame         PROGBITS         00000000004007b8  000007b8
        00000000000000f4  0000000000000000   A       0     0     8
   [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
        0000000000000008  0000000000000000  WA       0     0     8
   [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
        0000000000000008  0000000000000000  WA       0     0     8
   [21] .jcr              PROGBITS         0000000000600e20  00000e20
        0000000000000008  0000000000000000  WA       0     0     8
   [22] .dynamic          DYNAMIC          0000000000600e28  00000e28--------------Dynamic linking tables
        00000000000001d0  0000000000000010  WA       6     0     8
   [23] .got              PROGBITS         0000000000600ff8  00000ff8------------------Global offset table
        0000000000000008  0000000000000008  WA       0     0     8
   [24] .got.plt          PROGBITS         0000000000601000  00001000---------------Global offset table-Procedure linkage table
        0000000000000038  0000000000000008  WA       0     0     8
   [25] .data             PROGBITS         0000000000601040  00001040------------------Initialized data,初始化過的數據區域
        0000000000000620  0000000000000000  WA       0     0     32
   [26] .bss              NOBITS           0000000000601660  00001660--------------------Uninitialized data,未初始化數據區域
        0000000000000620  0000000000000000  WA       0     0     32
   [27] .comment          PROGBITS         0000000000000000  00001660---------------Version control information
        0000000000000034  0000000000000001  MS       0     0     1
   [28] .shstrtab         STRTAB           0000000000000000  00002141------------------Section name string table,Section Headers名稱常量
        000000000000010c  0000000000000000           0     0     1
   [29] .symtab           SYMTAB           0000000000000000  00001698-----------------Linker symbol table,程序的符號表,包含動態
        0000000000000798  0000000000000018          30    55     8
   [30] .strtab           STRTAB           0000000000000000  00001e30-------------------String table,字符串常量信息
        0000000000000311  0000000000000000           0     0     1
Key to Flags:
   W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
   I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
   O (extra OS processing required) o (OS specific), p (processor specific)

 

.interp
.note.ABI-tag
.note.gnu.build-i
.gnu.hash

.dynsym

通過readelf --dyn-syms test讀取。

動態符號表(.dynsym)用來保存與動態連接相關的導入導出符號,不包括模塊內部的符號

.dynstr

動態符號表(.dynsym)中所包含的符號的符號名保存在動態符號字符串表 .dynstr 中。

.gnu.version

 

.gnu.version_r

 

.rela.dyn

重定位的地方在.got段內。主要是針對外部數據變量符號。例如全局數據。重定位在程序運行時定位,一般是在.init段內。定位過程:獲得符號對應value后,根據rel.dyn表中對應的offset,修改.got表對應位置的value。另外,.rel.dyn 含義是指和dyn有關,一般是指在程序運行時候,動態加載。區別於rel.plt,rel.plt是指和plt相關,具體是指在某個函數被調用時候加載。我個人理解這個Section的作用是,在重定位過程中,動態鏈接器根據r_offset找到.got對應表項,來完成對.got表項值的修改。

rel.dyn和.rel.plt是動態定位輔助段。由連接器產生,存在於可執行文件或者動態庫文件內。借助這兩個輔助段可以動態修改對應.got和.got.plt段,從而實現運行時重定位。

.rela.plt

 

重定位的地方在.got.plt段內(注意也是.got內,具體區分而已)。 主要是針對外部函數符號。一般是函數首次被調用時候重定位。首次調用時會重定位函數地址,把最終函數地址放到.got內,以后讀取該.got就直接得到最終函數地址。我個人理解這個Section的作用是,在重定位過程中,動態鏈接器根據r_offset找到.got對應表項,來完成對.got表項值的修改。

.init

 

.plt

段(過程鏈接表):所有外部函數調用都是經過一個對應樁函數,這些樁函數都在.plt段內。具體調用外部函數過程是:

調用對應樁函數—>樁函數取出.got表表內地址—>然后跳轉到這個地址.如果是第一次,這個跳轉地址默認是樁函數本身跳轉處地址的下一個指令地址(目的是通過樁函數統一集中取地址和加載地址),后續接着把對應函數的真實地址加載進來放到.got表對應處,同時跳轉執行該地址指令.以后樁函數從.got取得地址都是真實函數地址了。

下圖是.plt某表項,它包含了取.got表地址和跳轉執行兩條指令。

 

.text

text section是可執行指令的集合,.data和.text都是屬於PROGBITS類型的section,這是將來要運行的程序與代碼。查詢段表可知.text section的位偏移為0x0000320,size為0x0000192。

 

.fini

 

.rodata

rodata section,ro代表read only。
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.jcr
.dynamic
.got
.got.plt
.data
.bss
.comment
.shstrtab

.symtab

通過readelf -s讀取。
symtab section存放所有section中定義的符號名字,比如“data_items”,“start_loop”。 .symtab section是屬於SYMTAB類型的section,它描述了.strtab中的符號在“內存”中對應的“內存地址”。

 

.strtab

strtab section是屬於STRTAB類型的section,可以在文件中看到,它存着字符串,儲存着符號的名字。

.dynsym和.symtab區別

需要先了解allocable/non-allocable ELF section, ELF文件包含一些sections(如code和data)是在運行時需要的, 這些sections被稱為allocable; 而其他一些sections僅僅是linker,debugger等工具需要, 在運行時並不需要, 這些sections被稱為non-allocable的. 當linker構建ELF文件時, 它把allocable的數據放到一個地方, 將non-allocable的數據放到其他地方. 當OS加載ELF文件時, 僅僅allocable的數據被映射到內存, non-allocable的數據仍靜靜地呆在文件里不被處理. strip就是用來移除某些non-allocable sections的.

動態符號表(.dynsym)用來保存與動態連接相關的導入導出符號,不包括模塊內部的符號。而.symtab則保存所有符號,包括.dynsym中的符號。

.symtab包含大量linker,debugger需要的數據, 但並不為runtime必需, 它是non-allocable的;

.dynsym包含.symtab的一個子集, 比如共享庫所需要在runtime加載的函數對應的symbols, 它是allocable的。

3.3 Program Headers

Program Headers在內核中對應的結構體為:

typedef struct elf64_phdr {
   Elf64_Word p_type;
   Elf64_Word p_flags;
   Elf64_Off p_offset;        /* Segment file offset */
   Elf64_Addr p_vaddr;        /* Segment virtual address */
   Elf64_Addr p_paddr;        /* Segment physical address */
   Elf64_Xword p_filesz;        /* Segment size in file */
   Elf64_Xword p_memsz;        /* Segment size in memory */
   Elf64_Xword p_align;        /* Segment alignment, file & memory */
} Elf64_Phdr;

readelf –l xxx結果如下:

Elf file type is EXEC (Executable file)
Entry point 0x4004e0
There are 9 program headers, starting at offset 64

Program Headers:
   Type           Offset             VirtAddr           PhysAddr
                  FileSiz            MemSiz              Flags  Align
   PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040---Program header table
                  0x00000000000001f8 0x00000000000001f8  R E    8
   INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238---Program Interpreter path name
                  0x000000000000001c 0x000000000000001c  R      1
       [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
   LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000---Loadable segment
                  0x00000000000008ac 0x00000000000008ac  R E    200000
   LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                  0x0000000000000850 0x0000000000000e70  RW     200000
   DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28---Dynamic linking tables
                  0x00000000000001d0 0x00000000000001d0  RW     8
   NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254---Note sections
                  0x0000000000000044 0x0000000000000044  R      4
   GNU_EH_FRAME   0x0000000000000784 0x0000000000400784 0x0000000000400784---
                  0x0000000000000034 0x0000000000000034  R      4
   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                  0x0000000000000000 0x0000000000000000  RW     10
   GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                  0x00000000000001f0 0x00000000000001f0  R      1

Section to Segment mapping:-------------------------------------------這里將Section映射到Segment
   Segment Sections...
    00     ---------------------------------------------------------------------PHDR
    01     .interp -------------------------------------------------------------INTERP
    02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame --------LOAD
    03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss -----LOAD
    04     .dynamic ------------------------------------------------------------DYNAMIC
    05     .note.ABI-tag .note.gnu.build-id -----------------------------------NOTE
    06     .eh_frame_hdr ------------------------------------------------------GNU_EH_FRAME
    07     -----------------------------------------------------------------------GNU_STACK
    08     .init_array .fini_array .jcr .dynamic .got --------------------------GNU_RELRO

 

3.4 Symbol Table(Dynamic Symbol Table)

Symbol Table包括Dynamic Symbol Table,在內核中定義如下:

typedef struct elf64_sym {
   Elf64_Word st_name;        /* Symbol name, index in string tbl */
   unsigned char    st_info;    /* Type and binding attributes */
   unsigned char    st_other;    /* No defined meaning, 0 */
   Elf64_Half st_shndx;        /* Associated section index */
   Elf64_Addr st_value;        /* Value of the symbol */
   Elf64_Xword st_size;        /* Associated symbol size */
} Elf64_Sym;

st_info包括Type和Bind兩部分:

Type:

#define STT_NOTYPE  0------No type specified
#define STT_OBJECT  1------Data object
#define STT_FUNC    2-------Function entry point
#define STT_SECTION 3-----Symbol is associcated with a section
#define STT_FILE    4--------Source file associated with the object file
#define STT_COMMON  5
#define STT_TLS     6

Bind:

#define STB_LOCAL  0------Not visible outside the object file
#define STB_GLOBAL 1-----Global symbol, visible to all object files
#define STB_WEAK   2------Global scope, but with lower precedence than global symbols

readelf –s xxx結果如下:

Symbol table '.dynsym' contains 6 entries:
    Num:    Value          Size Type    Bind   Vis      Ndx Name
      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
      1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (2)
      2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
      3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
      4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
      5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.2.5 (2)

Symbol table '.symtab' contains 81 entries:-----------------------------------可以看到.dynsym中有的符號在.symtab中都可以找到。
    Num:    Value          Size Type    Bind   Vis      Ndx Name
      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
      1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1
……
     27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27
     28: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     29: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   21 __JCR_LIST__
     30: 0000000000400510     0 FUNC    LOCAL  DEFAULT   14 deregister_tm_clones
     31: 0000000000400550     0 FUNC    LOCAL  DEFAULT   14 register_tm_clones
     32: 0000000000400590     0 FUNC    LOCAL  DEFAULT   14 __do_global_dtors_aux
     33: 0000000000601660     1 OBJECT  LOCAL  DEFAULT   26 completed.7585
     34: 0000000000600e18     0 OBJECT  LOCAL  DEFAULT   20 __do_global_dtors_aux_fin
     35: 00000000004005b0     0 FUNC    LOCAL  DEFAULT   14 frame_dummy
     36: 0000000000600e10     0 OBJECT  LOCAL  DEFAULT   19 __frame_dummy_init_array_
     37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS sample.c
     38: 0000000000601680   256 OBJECT  LOCAL  DEFAULT   26 buffer_g_s_u
     39: 0000000000601260   256 OBJECT  LOCAL  DEFAULT   25 buffer_g_s_i
     40: 0000000000601780   256 OBJECT  LOCAL  DEFAULT   26 buffer_g_s_u_unuse
     41: 0000000000601360   256 OBJECT  LOCAL  DEFAULT   25 buffer_g_s_i_unuse
     42: 0000000000601880   256 OBJECT  LOCAL  DEFAULT   26 buffer_l_s_u.2801
     43: 0000000000601460   256 OBJECT  LOCAL  DEFAULT   25 buffer_l_s_i.2802
     44: 0000000000601560   256 OBJECT  LOCAL  DEFAULT   25 buffer_l_s_i_unuse.2804
     45: 0000000000601980   256 OBJECT  LOCAL  DEFAULT   26 buffer_l_s_u_unuse.2803
     46: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     47: 00000000004008a8     0 OBJECT  LOCAL  DEFAULT   18 __FRAME_END__
     48: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   21 __JCR_END__
     49: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
     50: 0000000000600e18     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_end
     51: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   22 _DYNAMIC
     52: 0000000000600e10     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start
     53: 0000000000400784     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR
     54: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_
     55: 0000000000400770     2 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini
     56: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@@GLIBC_2.2.5
     57: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
     58: 0000000000601040     0 NOTYPE  WEAK   DEFAULT   25 data_start
     59: 0000000000601a80   256 OBJECT  GLOBAL DEFAULT   26 buffer_g_u
     60: 0000000000601660     0 NOTYPE  GLOBAL DEFAULT   25 _edata
     61: 0000000000400774     0 FUNC    GLOBAL DEFAULT   15 _fini
     62: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2
     63: 0000000000601060   256 OBJECT  GLOBAL DEFAULT   25 buffer_g_i
     64: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
     65: 0000000000601040     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
     66: 0000000000601160   256 OBJECT  GLOBAL DEFAULT   25 buffer_g_i_unuse
     67: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     68: 0000000000601048     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
     69: 0000000000400780     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used
     70: 0000000000400700   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init
     71: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@@GLIBC_2.2.5
     72: 0000000000601c80     0 NOTYPE  GLOBAL DEFAULT   26 _end
     73: 00000000004004e0    42 FUNC    GLOBAL DEFAULT   14 _start
     74: 0000000000601660     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
     75: 00000000004005d6   298 FUNC    GLOBAL DEFAULT   14 main
     76: 0000000000601b80   256 OBJECT  GLOBAL DEFAULT   26 buffer_g_u_unuse
     77: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     78: 0000000000601660     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__
     79: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
     80: 0000000000400460     0 FUNC    GLOBAL DEFAULT   11 _init

 

那么不同類型的變量,是否占用空間呢? 

變量類型 是否占用空間  
全局變量 不論是否使用,都占用空間。 因為全局變量作用域跨文件,所以即使此文件沒有使用,也不能被優化。
全局靜態變量

如果沒被使用,會被編譯器優化。

如果被使用,則占用空間。

全局靜態變量的作用域為文件,編譯器可以判定在此文件是否使用。沒有使用,則別處也不會使用。沒有存在意義。
局部變量 局部變量不占用空間。 局部變量只在函數內使用,分配在棧中。
局部靜態變量

如果沒被使用,會被編譯器優化。

如果被使用,則占用空間。

局部靜態的作用域是函數,雖然存在靜態存儲區,但是如果函數內沒有使用。在別處再不會被使用,所以可以優化掉。

存在靜態存儲區。

malloc/free  堆中分配和釋放,所以是動態的。  

 

4. 通過readelf分析符號表用於空間優化

通過readelf -s xxx獲取elf文件的符號信息,然后解析每個符號的大小、地址、類型和名稱。根據解析的數據列出所有符號大小降序排列,和FUNC/OBJECT的降序排列。

4.1 解析elf符號信息

 所有符號信息都保存到elf_lists中:

elf_file = 'iot_ap.elf'
elf_summary = elf_file.split('.')[0]
elf_lists = []
top_counts = 20

if not os.path.exists(elf_summary):
    os.mkdir(elf_summary)

#elf_summary_object = open(elf_summary, 'wb')

tmp = os.popen('readelf -s %s' % elf_file).readlines()
elf_symbol_fmt = ' *(?P<num>[0-9]*): (?P<value>[0-9abcdef]*) *(?P<size>[0-9]*).*'
for line in tmp:
    m = re.match(elf_symbol_fmt, line)
    if not m:
        continue
    #num = m.group('num')
    elf_line_list = re.split(r'\s+', line)

    if elf_line_list[3][0:2] == '0x':
        elf_line_list[3] = int(elf_line_list[3][2:], 16)
    #                     size             address            tyoe             name
    elf_lists.append([elf_line_list[3], elf_line_list[2], elf_line_list[4], elf_line_list[8]])
#elf_summary_object.writelines(tmp)
#elf_summary_object.close()

 

 

4.2 分析符號列表

將elf_lists轉成pandas.DataFrame,然后分別進行排序。

elf_data = pd.DataFrame(np.asarray(elf_lists), columns=['size', 'address', 'type', 'name'])
elf_data['size'] = elf_data['size'].astype(int)

#elf_data.sort_values(by=['size', 'type'], ascending=[False, False]).head(top_counts).to_csv(elf_summary, mode='w')
top_all_head = pd.DataFrame(elf_data.sort_values(by=['size', 'type'], ascending=[False, False]))

top_func_head = elf_data[elf_data['type'] == 'FUNC'].sort_values(by=['size'], ascending=False)
top_object_head = elf_data[elf_data['type'] == 'OBJECT'].sort_values(by=['size'], ascending=False)

 

 

4.3 查看結果

4.3.1 所有符號總占用空間:

elf_types = elf_data['type'].unique()
totalsize_of_types = 0

print '\nSize of %s:' % (elf_types)
for i in elf_types:
    size_of_type = np.asarray(elf_data[elf_data['type'] == i].sort_values(by=['size'], ascending=False)['size']).sum()
    print i, ':', size_of_type, 'Bytes'
    totalsize_of_types += size_of_type
print 'Total : ', totalsize_of_types/1024, 'KB'

 

如下:

Size of ['NOTYPE' 'SECTION' 'FILE' 'FUNC' 'OBJECT']:
NOTYPE : 0 Bytes
SECTION : 0 Bytes
FILE : 0 Bytes
FUNC : 141478 Bytes
OBJECT : 77770 Bytes
Total : 214 KB

4.3.2 所有符號降序Top 20

top_all_head.to_csv('%s/top_all.csv' % elf_summary)
print '\nThe top %d of %s:' % (top_counts, elf_file)
top_all_head.head(top_counts)

 

 4.3.3 所有OBJECT類型符號Top 20

top_object_head.to_csv('%s/top_object.csv' % elf_summary)
print '\nThe top %d of %s:' % (top_counts, 'OBJECT')
top_object_head.head(top_counts)

 

 

4.3.4 所有FUNC符號Top 20

top_func_head.to_csv('%s/top_func.csv' % elf_summary)
print '\nThe top %d of %s:' % (top_counts, 'FUNC')
top_func_head.head(top_counts)

 

 

 

 

 

 

 

參考文檔:

1.《Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification Version 1.2》,中文翻譯版《可執行文件(ELF)格式的理解

2.《ELF-64 Object File Format

3.《GCC編譯器優化選項分析及具體優化了什么

4.《深入Linux內核架構》附錄E ELF二進制格式

5.《C語言的變量的作用域和生存期

6.《C/C++堆、棧及靜態數據區詳解

7.《elf文件格式和運行時內存布局

8.《ELF格式文件符號表全解析及readelf命令使用方法》

9. objdump

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM