GCC编译二进制产物一般有静态库、动态库和可执行程序。本篇文章的目的是为了探寻可能存在的减小二进制程序体积的方法。

查询文件格式

file查看是否是ELF格式

格式:file [file path]

10:31:34 ELF $file *
json_example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, with debug_info, not stripped
libcjson.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, not stripped
libjson-c.a: current ar archive

readelf详细查看ELF文件信息

格式:readelf <option(s)> elf-file(s)

10:46:14 ELF $readelf -h json_example 
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: 0x11189
Start of program headers: 52 (bytes into file)
Start of section headers: 143200 (bytes into file)
Flags: 0x5000400, Version5 EABI, hard-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 33

分析文件体积

size/du查看文件所占内存/磁盘

10:50:12 ELF $size json_example
text data bss dec hex filename
40726 1032 304 42062 a44e json_example
14:00:42 ELF $du -k json_example
144 json_example # k
14:00:47 ELF $du -b json_example
144560 json_exampl # 字节

字段说明

字段 含义 示例值 说明
text 代码段 40726 字节 包含程序的可执行代码(机器指令),通常是只读的
data 数据段 1032 字节 包含已初始化的全局变量和静态变量,占用磁盘空间
bss 未初始化数据段 304 字节 包含未初始化的全局变量和静态变量,运行时初始化为0
dec 十进制总和 42062 字节 前三项的总和:40726 + 1032 + 304
hex 十六进制总和 a44e 总大小的十六进制表示
filename 文件名 json_example 被分析的可执行文件名称

体积优化

移除调试信息

  1. file命令查看是否包含符号信息
    查看是否包含not stripped字段
    10:50:26 ELF $file libcjson.so 
    libcjson.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, not stripped
    13:35:25 ELF $file json_example
    json_example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, with debug_info, not stripped
  2. strip --strip-debug --strip-unneeded libcjson.so移除调试符号
    动态库不能过度strip,不然有可能导致无法使用,上面两个参数是相对安全的参数
  3. strip json_example移除所有调试信息

-Os 编译选项

GCC-O选项是一组用于控制编译优化级别的参数,这些优化级别在代码大小和执行速度之间进行不同的权衡。

优化级别 编译时间 执行速度 代码体积 可调试性
-O0 最快 最慢 最大 最好
-O1 较快 较快 较小 较好
-O2 中等 中等 一般
-O3 较慢 最快* 较大
-Os 中等 较快 最小 一般
-Og 较快 中等 中等

bloaty分析二进制文件大小

Google开源维护的二进制文件分析工具

编译

  1. 检查一下编译工具的C++标准:g++ -dM -E -x c++ /dev/null | grep -i cplusplus
    16:14:04 build $g++ -dM -E -x c++ /dev/null | grep -i cplusplus
    #define __cplusplus 201703L
    199711L: C++98/C++03
    201103L: C++11
    201402L: C++14
    201703L: C++17
    202002L: C++20
    202302L: C++23
    保持CMakeLists.txtCMAKE_CXX_STANDARD和系统版本一致
  2. 编译
    git clone https://github.com/google/bloaty.git
    cd bloaty
    mkdir build
    cd build
    cmake ..
    make
    sudo make install

使用

  • bloaty ./my_program
  • bloaty -d compileunits,symbols ./my_program
数据源 解释
sections 按 ELF 文件的段(Section)查看
symbols 按符号(函数名、变量名)查看
compileunits 按编译单元(源文件)查看
inlines 尝试解析内联函数,显示内联展开后的体积分布
17:00:27 ELF $bloaty json_example 
FILE SIZE VM SIZE
-------------- --------------
22.8% 32.2Ki 77.7% 32.2Ki .text
20.6% 29.0Ki 0.0% 0 .debug_info
15.7% 22.2Ki 0.0% 0 .debug_line
9.4% 13.3Ki 0.0% 0 .debug_str
7.1% 9.97Ki 0.0% 0 .symtab
4.2% 5.95Ki 0.0% 0 .debug_frame
4.0% 5.68Ki 0.0% 0 .strtab
3.8% 5.33Ki 0.0% 0 .debug_abbrev
2.6% 3.69Ki 0.0% 0 [Unmapped]
2.5% 3.52Ki 8.5% 3.52Ki .rodata
1.1% 1.53Ki 0.0% 0 .debug_aranges
1.0% 1.47Ki 2.7% 1.12Ki [17 Others]
1.0% 1.42Ki 0.0% 0 .debug_ranges
0.9% 1.33Ki 0.0% 0 [ELF Section Headers]
0.8% 1.06Ki 2.6% 1.06Ki .dynsym
0.6% 800 1.9% 800 .dynstr
0.5% 744 1.8% 744 .plt
0.4% 548 1.3% 548 .hash
0.4% 540 1.3% 540 .data
0.3% 480 1.1% 480 .gnu.hash
0.3% 480 1.1% 480 .rel.plt
100.0% 141Ki 100.0% 41.4Ki TOTAL