基於本文的一個實踐《使用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 程序。

使用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:


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