产品瘦身

2018-11-21  本文已影响0人  池塘游泳的蜗牛

背景前言

  最近遇到一个棘手的问题,编译生成的可执行文件过大而无法上电。这就好像一个人过于肥胖致其11路公共汽车罢工一个道理。导致该结果的原因也很明确,一个开源组件(对于该组件的使用个人持保留意见,因为我们引入了一个不太适合自己并且无法驾驭的洪水猛兽。暂且认为选择的那个人当时大脑短路了,谁又没有短路时候呢)。说到这不得不赞赏下老外。那些脾气差的鬼魅一怒之下往往会给你搞个新玩意出来,而我们只会百度与GOOGLE。久而久之的结果就是人家有GOOGLE、MS、INTEL,我们最后就只剩下了”厉害了我的国“,有时甚至到了让人感觉吹牛逼都可以改变世界的地步。
  言归正传,后面的内容我会尽量使用通俗的语言MAKE MY MIND CLEAR。如果没有那么请原谅,因为我语文确实不怎么好

一、可执行文件的生成过程

这个过程可能大家都知道,不过为了文章连贯性在此还得赘述下。
     [预处理] -> [编译] -> [汇编] -> [链接]
可执行文件的生成大致经历了上述四个过程。不过对于预处理和汇编两个过程,我们真的无能为力。这样一来我们的突破口就只能在编译、链接两个过程。

(1)编译

一般的工程是由大量的.h与.cpp文件构成。头文件的作用在于前向声明,cpp为最基本的编译单元。为了方便物理隔离,我们将基础的逻辑处理分布在了瀚海的CPP中。人都是视觉动物,就好像大家都喜欢美的事物一样,不过太过投入一不小心就撞墙上了。太过零碎的CPP导致编译器需要重复的进行文件IO操作进而影响编译效率,故此我们又发明了"BIGCPP"。将众多的CPP打包成一个CPP文件。整个过程看起来有点“脱裤子放屁,多此一举”的感觉,不过好处确是显而易见。众多CPP最终会在编译器废寝忘食的工作下生成相应的可重定向文件。

(2)链接

链接器将生成的可重定向文件,链接成可执行文件。至此可执行文件生成了。一切看看起来那么简单,不过事实真的是那样么? 就好像一个人还不知道人家长啥样你就说人家美或丑一样。

(3) ELF 文件格式

Linux 环境下的可执行文件都遵从ELF文件格式。ELF文件一般包含下面三个表头:
1,ELF 文件表头

x86.png

ELF文件展示文件的基本属性信息,例如文件类型,大小端,ABI版本,32位(64位)等基本属性信息。同时包含程序头表信息(用于程序加载时创建映像,在此我们不讨论),节字头表信息等其它信息。在链接库文件时可能会报”不合适的库文件“这时注意下ELF文件头表信息一般就可能知道原因了。
2,节头表

结头.png
包含符号表,bss,data, got表地址等相关信息。从上图节头表可以看出符号表偏移地址为0x00001918,size为0x660占用空间最大,所以我们的突破口落在了符号表上。
3,符号表 fuhao.png

符号表展示了程序运行过程中,所使用的符号信息。下面我们具体分析下符号表相关信息。

├── build
├── build.sh
├── CMakeLists.txt
├── inc
│   └── hello.h
└── src
    ├── hello.cpp
    └── main.cpp

/***hello.h***/
#ifndef _TEST_HELLO_WORLD_PRINT_   
#define _TEST_HELLO_WORLD_PRINT_

extern "C" void print_hello();

int rubbish_function();

#endif
/***hello.cpp***/
#include<stdio.h>
#include"hello.h"

void  print_hello()
{
        printf("hello world \n ");
}

int rubbish_function()
{
        int  a = 1;
        int b = 2;
        int c = a + b;
        return c;
}


#include "hello.h"
int main()
{
      print_hello();
};

如上图显示
49: 0000000000400542 34 FUNC GLOBAL DEFAULT 13 _Z16rubbish_functionv
rubbish_function这个符号我们并没有使用,但是其仍然存在于我们最终的可执行文件中,反汇编结果如下。这个也就是我们所要讨论的优化点。

huibian.png
二、优化方式
(1) 编译

上面看到某些无用符号被链接进入了最终产品,那么是否存在方法将其剔出呢?链接器的默认链接最小单元仍然是文件(同编译)。也就是说如果某个文件中有一个符号被链接,该文件的所有符号都将被一同链接。
 GCC编译器提供了编译优化选项,告诉编译器将单个符号(函数和数据)作为最小链接单元。下面我们重新将上面hello.cpp重新编译。

                                 `-fdata-sections  -ffunction-sections`

加选项后

hou.png

加选项前

qian.png

对比两个节头,表明显看到加入选项后每个符号自成一个节字。

(2) 链接

获得可连接文件后,我们还得告诉连接器,剔除无用符号。

 --gc-sections               删除未使用的节(在某些目标上)
 --no-gc-sections            不删除未使用的节(默认)

最后我们重新编译再看看最后的结果。


elf.png

从上图明显看出无用符号被剔除了。

(3) 问题

上述方式可以完美剔除无用符号,但是“杀敌一千自损八百”这个道理一直都是成立的。产品中可能会剔除某些调试符号例如桩函数。当然解决方式也很简单。我们可以针对不同功能模块单独编译优化。如果你发现某些倒霉蛋被优化掉了,那么你可以将其CPP include 进未被优化的模块就OK了。前面说过了默认链接是以文件为最小单位的。

结束

一切看起总是那么完美。为了减小影响面,我们最终只优化了一个我们认为无用符号较多的库。实际的结果也十分可喜,最后产品尺寸减少了近九百分点(10M左右),我们又可以愉快的玩耍好长时间了~~。

上一篇下一篇

猜你喜欢

热点阅读