大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家講的是嵌入式開發(fā)里的executable文件(elf)。
第四、五節(jié)課里,痞子衡已經(jīng)給大家介紹了2種output文件,本文繼續(xù)給大家講project生成的另一種output文件-executable文件,也是特別重要的output文件。
文件關(guān)系:鏈接文件(.icf) + 工程文件(.ewp) + 可重定向文件(.o/.a) -> 可執(zhí)行文件(.out/.elf)
仔細看過痞子衡之前課程的朋友肯定知道,痞子衡在第四節(jié)課可重定向文件(.o/.a)里介紹的object文件在格式上其實跟本文要講的elf文件是類似的,它們都屬于ELF文件分支。只不是relocatable文件只是中間過渡文件,而本文要講的elf卻是標準的output文件,這個文件幾乎包含了工程的所有信息,有了這個文件我們既可以在線調(diào)試工程,也可以將elf文件轉(zhuǎn)換成image文件,直接下載image文件數(shù)據(jù)進芯片中脫機運行。今天痞子衡就為大家仔細分析elf文件。
一、elf文件基礎(chǔ)
ELF全稱Executable and Linkable Format,可執(zhí)行連接格式,ELF格式的文件最早用于存儲Linux程序,后演變到ARM系統(tǒng)上存儲ARM程序。ELF文件(目標文件)格式主要三種:
可重定向文件:用來和其他的目標文件一起來創(chuàng)建一個可執(zhí)行文件或者共享目標文件(也稱object文件或者靜態(tài)庫文件,通常后綴為.o和.a的文件)。這個文件是用于編譯和鏈接階段。
可執(zhí)行文件:用于生成應用image,載入存儲器執(zhí)行(后綴通常為.out或者.elf)。這個文件是用于加載執(zhí)行階段。
共享目標文件:用于和其他共享目標文件或者object文件一起生成可執(zhí)行文件,或者和可執(zhí)行文件一起創(chuàng)建應用image。(也稱共享庫文件,后綴為.so的文件)。這個文件既可用于編譯和鏈接階段,也可用于加載執(zhí)行階段。
我們在ARM開發(fā)中更多接觸的是前兩種格式,第一種格式前面系列文章可重定向文件(.o/.a)已經(jīng)介紹過,本文的主角是第二種格式-可執(zhí)行文件。不管是哪種格式的ELF文件,其都可能包含如下三種基本索引表:
file header:一般在文件的開始,描述了ELF文件的整體組織情況。
program header:告訴系統(tǒng)如何創(chuàng)建image,可執(zhí)行文件必須具有program header,而可重定向文件則不需要。
section header:包含了描述文件section的信息,每個section都有一個header,每一個header給出諸如section名稱、section大小等信息??芍囟ㄏ蛭募仨毎瑂ection header。
既然知道了存在三種索引表,那么表的結(jié)構(gòu)定義在哪里呢?github上的linux倉庫里有具體定義,在elf.h頭文件里。
Linux倉庫:https://github.com/torvalds/linux.gitelf.h路徑:\linux\include\uapi\linux\elf.h
打開elf.h文件便可找到三個表的原型定義,鑒于目前的ARM Cortex-M都是32bit,所以此處僅列出32bit下的表的原型:Elf32_Ehdr、Elf32_Phdr、Elf32_Shdr。
// file header
#define EI_NIDENT 16
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
// program header
typedef struct elf32_phdr{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment, file & memory */
} Elf32_Phdr;
// section header
typedef struct elf32_shdr {
Elf32_Word sh_name; /* Section name, index in string tbl */
Elf32_Word sh_type; /* Type of section */
Elf32_Word sh_flags; /* Miscellaneous section attributes */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Size of section in bytes */
Elf32_Word sh_link; /* Index of another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
二、解析elf文件
所謂工欲善其事,必先利其器,在開始解析elf文件之前,我們必須先找到一款合適的解析工具,readelf就是GNU/Linux官方推出的專用解析工具。有了這個解析工具,我們便可以逐步分析elf文件。
2.1 解析工具readelf
既然elf文件是Linux系統(tǒng)下常用的可執(zhí)行文件格式,那么Linux社區(qū)一定會有配套的工具去解析它,是的,這個工具就叫readelf,在GNU工具集binutils里。
2.1.1 GNU工具集(binutils)
GNU是“GNU's Not Unix”的遞歸縮寫,又稱為GNU計劃,很多著名的開源軟件及工具都是GNU開發(fā)的(比如大名鼎鼎的C語言編譯器GCC)。binutils是GNU一系列binary小工具的集合,大家從下面的鏈接里找到官方binutils包。
- 主頁:http://www.gnu.org/software/binutils/
- 倉庫:git://sourceware.org/git/binutils-gdb.git
- 下載:http://ftp.gnu.org/gnu/binutils/
- 文檔:https://sourceware.org/binutils/docs-2.29/binutils/index.html
但是使用上述包里的readelf會有一個問題,上述工具是在Linux系統(tǒng)下使用的,而大家平常做ARM Cortex-M開發(fā)很多都是在windows平臺下,那么怎么在windows下使用readelf工具呢?別急,cygwin給了我們幫助。
2.1.2 cygwin(windows下使用GNU)
Cygwin是一個在windows平臺上運行的類UNIX模擬環(huán)境,是cygnus solutions公司(已被Redhat收購)開發(fā)的自由軟件。它對于學習UNIX/Linux操作環(huán)境,或者從UNIX到Windows的應用程序移植,尤其是使用GNU工具集在Windows上進行嵌入式系統(tǒng)開發(fā),非常有用。
Installer:
http://cygwin.com/install.htmlPackage:https://cygwin.com/packages/package_list.html
// 相關(guān)工具包
binutils - GNU assembler, linker, and similar utilities
cygwin32-binutils - Binutils for Cygwin 32bit toolchain
mingw64-x86_64-binutils - Binutils for MinGW-w64 Win64 toolchain
mingw64-i686-binutils - Binutils for MinGW-w64 Win32 toolchain
下載安裝好cygwin包后,便可在安裝目錄下\cygwin64\bin\找到x86_64-w64-mingw32-readelf.exe工具(痞子衡選擇的是mingw64-x86_64-binutils包)。
2.1.3 readelf.exe用法
readelf.exe遵循標準的windows命令行用法,使用--help可以列出所有命令option及其簡介,下面僅列出比較常用的option。
C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe --help
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
-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
-r --relocs Display the relocations (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)
-I --histogram Display histogram of bucket list lengths
@<file> Read options from <file>
2.2 逐步分析elf文件
萬事俱備了,開始分析elf文件,以第三節(jié)課工程文件(.ewp)里demo工程為例。編譯鏈接該工程可在D:\myProject\bsp\builds\demo\Release\Exe路徑下得到demo.elf文件。該文件大小32612 bytes,顯然這么精簡的一個小工程image size不可能這么大,說明elf文件里的記錄信息數(shù)據(jù)占比非常大。
2.2.1 獲得file header
C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -h demo.elf
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: 0x41
Start of program headers: 31740 (bytes into file)
Start of section headers: 31772 (bytes into file)
Flags: 0x5000000, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 1
Size of section headers: 40 (bytes)
Number of section headers: 21
Section header string table index: 1
第一步首先分析file header,前面介紹里說過file header是放在文件最前面的。通過readelf -h命令可以獲得file header解析后的信息。讓我們來對照一下,使用HexEditor直接打開demo.elf可得到如下數(shù)據(jù),僅取前52bytes(0x34)數(shù)據(jù),因為Elf32_Ehdr大小就是52bytes:
offset(h)
00000000: 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00
00000010: 02 00 28 00 01 00 00 00 41 00 00 00 FC 7B 00 00
00000020: 1C 7C 00 00 00 00 00 05 34 00 20 00 01 00 28 00
00000030: 15 00 01 00 -- -- -- -- -- -- -- -- -- -- -- --
可以看到前16byte是e_ident[16],與解析后的Magic是一致的;再來驗證prgram header偏移e_phoff=0x00007BFC,數(shù)量e_phnum=0x0001,大小e_phentsize=0x0020,也是與解析后的信息匹配的;余下可自行對照。
2.2.2 獲得program header
C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -l demo.elf
Elf file type is EXEC (Executable file)
Entry point 0x41
There are 1 program headers, starting at offset 31740
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000034 0x00000000 0x00000000 0x004c4 0x004c4 R E 0x100
Section to Segment mapping:
Segment Sections...
00 A0 rw P1 ro
再來分析program header,通過readelf -l命令可以獲得program header解析后的信息。從上面可以得知header起始位置在demo.elf的31740 byte處(與file header里的e_phoff信息是對應的),header信息提示program data從offset 0x34開始,大小共0x4c4 bytes,Reset_Handler入口是0x41。繼續(xù)在HexEditor查看31740處開始的32byte數(shù)據(jù),因為Elf32_Phdr大小就是32bytes:
offset(h)
00007BF0: -- -- -- -- -- -- -- -- -- -- -- -- 01 00 00 00
00007C00: 34 00 00 00 00 00 00 00 00 00 00 00 C4 04 00 00
00007C10: C4 04 00 00 05 00 00 00 00 01 00 00 -- -- -- --
可以看到p_offset=0x00000034,p_memsz=0x000004c4, 與上面解析后的信息是一致的;余下可自行對照。這里的信息就比較重要了,因為這指示了整個image binary數(shù)據(jù)所在(知道了這個信息,我們便可以直接寫腳本根據(jù)elf文件生成image binary),繼續(xù)在HexEditor里看下去(僅截取部分顯示):
offset(h)
00000030: -- -- -- -- 00 20 00 10 41 00 00 00 03 04 00 00
00000040: 3F 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000060: 61 04 00 00 00 00 00 00 00 00 00 00 63 04 00 00
00000070: 65 04 00 00 72 B6 0E 48 0E 49 88 60 00 22 00 23
00000080: 00 24 00 25 00 26 00 27 B8 46 B9 46 BA 46 BB 46
ARM系統(tǒng)的image前16個指針都是系統(tǒng)中斷向量,我們可以看到SP=0x10002000, PC=0x00000041,這與上面解析的Reset_Handler入口是0x41是匹配的。
2.2.3 獲得section header
c:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -S demo.elf
There are 21 section headers, starting at offset 0x7c1c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 006338 000000 00 0 0 4
[ 1] .shstrtab STRTAB 00000000 006338 0000e6 00 0 0 4
[ 2] .strtab STRTAB 00000000 006420 000b7c 00 0 0 4
[ 3] .symtab SYMTAB 00000000 006f9c 000c60 10 2 135 4
[ 4] A0 rw PROGBITS 00000000 000034 000040 01 AX 0 0 256
[ 5] P1 ro PROGBITS 00000040 000074 000484 01 AX 0 0 4
[ 6] P3 ui NOBITS 10000000 0004f8 002000 01 WA 0 0 8
[ 7] P2 rw NOBITS 10002000 0004f8 000438 01 WA 0 0 8
[ 8] .debug_abbrev PROGBITS 00000000 0004f8 0002c6 01 0 0 0
[ 9] .debug_aranges PROGBITS 00000000 0007c0 00016c 01 0 0 0
[10] .debug_frame PROGBITS 00000000 00092c 00057c 01 0 0 0
[11] .debug_info PROGBITS 00000000 000ea8 000e2e 01 0 0 0
[12] .debug_line PROGBITS 00000000 001cd8 000dcb 01 0 0 0
[13] .debug_loc PROGBITS 00000000 002aa4 00024c 01 0 0 0
[14] .debug_macinfo PROGBITS 00000000 002cf0 00011e 01 0 0 0
[15] .debug_pubnames PROGBITS 00000000 002e10 00012a 01 0 0 0
[16] .iar.debug_frame PROGBITS 00000000 002f3c 00007e 01 0 0 0
[17] .iar.debug_line PROGBITS 00000000 002fbc 000367 01 0 0 0
[18] .comment PROGBITS 00000000 003324 002fa2 01 0 0 0
[19] .iar.rtmodel PROGBITS 00000000 0062c8 000047 01 0 0 0
[20] .ARM.attributes ARM_ATTRIBUTES 00000000 006310 000026 01 0 0 0
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
y (purecode), p (processor specific)
再來分析section header,通過readelf -S命令可以獲得section header解析后的信息??梢钥吹接泻芏鄠€section,其中最重要的4個section是A0(readonly vector), P1(readonly code,data), P2(readwrite data, heap), P3(STACK)。具體分析,各位朋友自己試試看。
2.2.4 獲得symbol list
c:cygwin64\bin>x86_64-w64-mingw32-readelf.exe -s demo.elf
Symbol table '.symtab' contains 198 entries:
Num: Value Size Type Bind Vis Ndx Name
74: 10002018 16 OBJECT LOCAL DEFAULT 7 s_array
75: 10002014 4 OBJECT LOCAL DEFAULT 7 s_variable0
76: 10002010 4 OBJECT LOCAL DEFAULT 7 s_variable2
135: 00000000 0 OBJECT GLOBAL DEFAULT 4 __vector_table
140: 00000041 0 FUNC GLOBAL DEFAULT 5 Reset_Handler
141: 00000098 4 OBJECT GLOBAL DEFAULT 5 s_constant
142: 000000ad 32 FUNC GLOBAL DEFAULT 5 main
143: 000000cd 14 FUNC GLOBAL DEFAULT 5 normal_task
144: 000000db 60 FUNC GLOBAL DEFAULT 5 heap_task
155: 0000034d 84 FUNC GLOBAL DEFAULT 5 init_data_bss
156: 000003a1 18 FUNC GLOBAL DEFAULT 5 init_interrupts
157: 000003dd 12 FUNC GLOBAL DEFAULT 5 SystemInit
186: 10002001 16 FUNC GLOBAL DEFAULT 7 ram_task
191: 10002034 4 OBJECT GLOBAL DEFAULT 7 n_variable1
通過readelf -s命令可以獲得symbol list解析后的信息??梢钥吹接泻芏鄠€symbol,痞子衡在這里僅列出應用工程里自定義的函數(shù)和變量,從symbol表里我們可以得知函數(shù)/變量在存儲器中具體分配地址和長度,這對于我們進一步分析和調(diào)試應用是有幫助的。
2.3 elf文件layout
經(jīng)過上一節(jié)對demo.elf里各個header的分析,此時我們便可以粗略地畫出elf文件layout。
番外一、幾個elf轉(zhuǎn)換image工具
在今天的番外篇里,痞子衡給大家順便介紹幾款標準的elf文件轉(zhuǎn)換成image文件的工具。
工具1:GNU工具objcopy
位置:C:\cygwin64\bin>x86_64-w64-mingw32-objcopy.exe
用法:
objcopy.exe -O binary -S demo.elf demo.bin
objcopy.exe -O srec -S demo.elf demo.s19
備注:一說需用arm-linux-objcopy,待驗證
工具2:IAR工具ielftool.exe
位置:\IAR Systems\Embedded Workbench xxx\arm\bin\ielftool.exe
用法: ielftool.exe --bin demo.elf demo.bin ielftool.exe --ihex demo.elf demo.hex ielftool.exe --srec demo.elf demo.s19
至此,嵌入式開發(fā)里的executable文件(elf)文件痞子衡便介紹完畢了,掌聲在哪里~~~