LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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

 

posted on 2017-07-08 14:19  ArnoldLu  阅读(5179)  评论(0编辑  收藏  举报

导航