基于本文的一个实践《使用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