【优化改进】Linux进程文件瘦身——Linux环境验证 form http://sammor.iteye.com/blog/2147762

来源:互联网 发布:js将字符串转换为json 编辑:程序博客网 时间:2024/06/07 22:19

【优化改进】Linux进程文件瘦身——Linux环境验证

    博客分类: 
  • linux
Linux进程无用符号cgcc 

背景:

       最近一直在思考,嵌入式产品的代码中存在着很多的无用的函数和全局变量,这些函数和全局量占用着版本的大小,进而对Flash的要求也更多了。但这些通用人工去排查毕竟不现实,那有什么办法可以做到自动的检测出这些没有用的函数和全局变量?

    这次的总结有一点感受很深刻,那就是“世上并不缺少美,缺少的是发现美的眼睛”。

 

思考:

       针对引言中的思考,我主要从以下两个方向去思考:

      1. 是否可以通过符号进行入手,通过查找符号不存在被调用的来判断函数或全局变量未被使用;

      2. 编译过程中,无用的函数或全局变量是否可以不链接进版本中;

 

方向1——检查没有使用到的符号

       很多人也许会说,在内部使用的函数都加上static不就好了吗?这也是一样办法,但排除不了团队编程过程中,有些人不加static,甚至在头文件中添加声明。

 

       那么,从符号调用角度我主要想到了以前出现段错误经常会用到使用objdump命令进行反汇编来看程序的汇编语言,其中函数的调用关系可以看得出来,那么从汇编语言中是不是可以检查到哪些函数都没有被调用过?下面做一个简单的测试。

 

 

C代码  收藏代码
  1. #include <stdio.h>  
  2. int lUnusedGrobleValue = 2;  
  3. int lUsedGrobleValue = 1;  
  4.   
  5. int unUsedFun()  
  6. {  
  7.     printf("%d", lUnusedGrobleValue);  
  8.     return 0;  
  9. }  
  10.   
  11. int usedFun()  
  12. {  
  13.     printf("It's a usedFun, usedGrobleValue=%d\r\n", lUsedGrobleValue);  
  14. }  
  15.   
  16. int main()  
  17. {  
  18.     printf("hello world\n");   
  19.     usedFun();  
  20.     return 0;  
  21. }  

 

从上面可以看出,存在函数unUsedFun和lUnusedGrobleValue是我们希望找出来的。

 

1. 下面使用nm命令来看一下正常情况下编译出来的进程带的符号信息:

 

Shell代码  收藏代码
  1. sammor@ubuntu:~/test$ gcc test.c -o test  
  2. sammor@ubuntu:~/test$ ls -l test  
  3. -rwxr-xr-x 1 sammor sammor 7362 2014-10-22 22:00 test  
  4. sammor@ubuntu:~/test$ nm --defined-only test  
  5. 08049f28 d _DYNAMIC  
  6. 08049ff4 d _GLOBAL_OFFSET_TABLE_  
  7. 0804854c R _IO_stdin_used  
  8. 08049f18 d __CTOR_END__  
  9. 08049f14 d __CTOR_LIST__  
  10. 08049f20 D __DTOR_END__  
  11. 08049f1c d __DTOR_LIST__  
  12. 08048654 r __FRAME_END__  
  13. 08049f24 d __JCR_END__  
  14. 08049f24 d __JCR_LIST__  
  15. 0804a020 A __bss_start  
  16. 0804a010 D __data_start  
  17. 08048500 t __do_global_ctors_aux  
  18. 08048390 t __do_global_dtors_aux  
  19. 0804a014 D __dso_handle  
  20. 080484f2 T __i686.get_pc_thunk.bx  
  21. 08049f14 d __init_array_end  
  22. 08049f14 d __init_array_start  
  23. 080484f0 T __libc_csu_fini  
  24. 08048480 T __libc_csu_init  
  25. 0804a020 A _edata  
  26. 0804a028 A _end  
  27. 0804852c T _fini  
  28. 08048548 R _fp_hw  
  29. 080482d4 T _init  
  30. 08048360 T _start  
  31. 0804a020 b completed.7108  
  32. 0804a010 W data_start  
  33. 0804a024 b dtor_idx.7110  
  34. 080483f0 t frame_dummy  
  35. 0804a018 D lUnusedGrobleValue  
  36. 0804a01c D lUsedGrobleValue  
  37. 08048457 T main  
  38. 08048414 T unUsedFun  
  39. 08048438 T usedFun  

 分析1: 从以上的验证看出正常编译情况下,没有用到的函数和全局变量也都被链接进来了,这说明这些无用的函数和全局变量确实在影响着我们最终输出的进程文件大小

 

 

那如何找出这些无用的函数和全局变量,继续进行反汇编来看下结果。

 

 

Shell代码  收藏代码
  1. sammor@ubuntu:~/test$ vim test.dump  
  2.   
  3. test:     file format elf32-i386  
  4.   
  5. Disassembly of section .init:  
  6. 080482d4 <_init>:  
  7. ...  
  8.   
  9. 08048310 <printf@plt-0x10>:  
  10. ...  
  11. 08048320 <printf@plt>:  
  12. ...  
  13. 08048330 <puts@plt>:  
  14. ...  
  15. 08048340 <__gmon_start__@plt>:  
  16. 08048350 <__libc_start_main@plt>:  
  17. 080483f0 <frame_dummy>:  
  18. #以上省略很不相关的汇编信息..  
  19.   
  20. 08048414 <unUsedFun>: // unUsedFun函数的反汇编  
  21.  8048414:       55                      push   %ebp  
  22.  8048415:       89 e5                   mov    %esp,%ebp  
  23.  8048417:       83 ec 18                sub    $0x18,%esp  
  24.  804841a:       8b 15 18 a0 04 08       mov    0x804a018,%edx —— (0804a018 D lUnusedGrobleValue)  
  25.  8048420:       b8 50 85 04 08          mov    $0x8048550,%eax  
  26.  8048425:       89 54 24 04             mov    %edx,0x4(%esp)  
  27.  8048429:       89 04 24                mov    %eax,(%esp)  
  28.  804842c:       e8 ef fe ff ff          call   8048320 <printf@plt>  
  29.  8048431:       b8 00 00 00 00          mov    $0x0,%eax  
  30.  8048436:       c9                      leave  
  31.  8048437:       c3                      ret  
  32. 08048438 <usedFun>:// usedFun函数的反汇编  
  33.  8048438:       55                      push   %ebp  
  34.  8048439:       89 e5                   mov    %esp,%ebp  
  35.  804843b:       83 ec 18                sub    $0x18,%esp  
  36.  804843e:       8b 15 1c a0 04 08       mov    0x804a01c,%edx —— (0804a01c D lUsedGrobleValue)  
  37.  8048444:       b8 54 85 04 08          mov    $0x8048554,%eax  
  38.  8048449:       89 54 24 04             mov    %edx,0x4(%esp)  
  39.  804844d:       89 04 24                mov    %eax,(%esp)  
  40.  8048450:       e8 cb fe ff ff          call   8048320 <printf@plt>  
  41.  804844d:       89 04 24                mov    %eax,(%esp)  
  42.  8048450:       e8 cb fe ff ff          call   8048320 <printf@plt>  
  43.  8048455:       c9                      leave  
  44.  8048456:       c3                      ret  
  45. 08048457 <main>: // main函数的反汇编  
  46.  8048457:       55                      push   %ebp  
  47.  8048458:       89 e5                   mov    %esp,%ebp  
  48.  804845a:       83 e4 f0                and    $0xfffffff0,%esp  
  49.  804845d:       83 ec 10                sub    $0x10,%esp  
  50.  8048460:       c7 04 24 79 85 04 08    movl   $0x8048579,(%esp)  
  51.  8048467:       e8 c4 fe ff ff          call   8048330 <puts@plt>  
  52.  804846c:       e8 c7 ff ff ff          call   8048438 <usedFun>  
  53.  8048471:       b8 00 00 00 00          mov    $0x0,%eax  
  54.  8048476:       c9                      leave  
  55.  8048477:       c3                      ret  
  56.  8048478:       90                      nop  
  57.  8048479:       90                      nop  
  58.  804847a:       90                      nop  
  59.  804847b:       90                      nop  
  60.  804847c:       90                      nop  
  61.  804847d:       90                      nop  
  62.  804847e:       90                      nop  
  63.  804847f:       90                      nop  
  64. 0804852c <_fini>:  
  65. ......  

 

 

分析2:从这个反汇编文件中,我们可以看出如下:

1. 函数被调用时,在汇编语言中是以call命令进行用(在我验证的arm板上是bl命令,说明在不同的架构上还是有差异的)。

2. 全局变量函数中被使用时,是以mov命令进行。

 

从以上两点分析来看,如果要做到从dump文件中检查出无用的函数和全局变量,则需要从符号表中找到所有定义的函数和全局变量,在dump文件中分析。

解析原则:

      待思考,当前这也是只思路,也许有更好的办法可以解决这个问题。

 

方向2——编译过程中,无用的函数或全局变量是否可以不链接进版本中

这个方向一直在思想困惑着,也没有去具体去找资源,突然今天在和一朋友聊到这个想法的时候,这位朋友和我说了他们那边通过编译选项来做到的,毕竟他们也是嵌入式设备,flash的空间非常小。他告诉我使用的了编译选项是编译过程添加-ffunction-sections和-fdata-sections, 链接过程添加选项-Wl,--gc-sections。

使用以上两个编译选项做一下试验:

 

Shell代码  收藏代码
  1. sammor@ubuntu:~/test$ gcc -c -ffunction-sections -fdata-sections  test.c   
  2. sammor@ubuntu:~/test$ gcc -Wl,--gc-sections test.o -o test  
  3. sammor@ubuntu:~/test$ nm --defined-only test  
  4. 08049f28 d _DYNAMIC  
  5. 08049ff4 d _GLOBAL_OFFSET_TABLE_  
  6. 08048528 R _IO_stdin_used  
  7. 08049f18 d __CTOR_END__  
  8. 08049f14 d __CTOR_LIST__  
  9. 08049f20 D __DTOR_END__  
  10. 08049f1c d __DTOR_LIST__  
  11. 08048630 r __FRAME_END__  
  12. 08049f24 d __JCR_END__  
  13. 08049f24 d __JCR_LIST__  
  14. 0804a014 A __bss_start  
  15. 080484e0 t __do_global_ctors_aux  
  16. 08048390 t __do_global_dtors_aux  
  17. 080484d2 T __i686.get_pc_thunk.bx  
  18. 08049f14 d __init_array_end  
  19. 08049f14 d __init_array_start  
  20. 080484d0 T __libc_csu_fini  
  21. 08048460 T __libc_csu_init  
  22. 0804a014 A _edata  
  23. 0804a01c A _end  
  24. 0804850c T _fini  
  25. 080482d4 T _init  
  26. 08048360 T _start  
  27. 0804a014 b completed.7108  
  28. 0804a018 b dtor_idx.7110  
  29. 080483f0 t frame_dummy  
  30. 0804a010 D lUsedGrobleValue  
  31. 08048432 T main  
  32. 08048413 T usedFun  

 分析:从符号表来看,没有用到的函数unUsedFun和全局变量lUnusedGrobleValue已经不在符号表里面了。而且和不加编译选项编译出来的进程相比减小了181字节。

 

Shell代码  收藏代码
  1. -rwxr-xr-x 1 sammor sammor   7181 2014-10-22 23:54 test  
  2. -rwxr-xr-x 1 sammor sammor   7362 2014-10-22 22:00 test1  

 

 

结论:编译过程添加-ffunction-sections和-fdata-sections, 链接过程添加选项-Wl,--gc-sections,可以使得编译出来的进程去除无用函数和全局变量符号,减少进程大小。

 

 

具体原理:

先看看gcc官方文档对这几个选项的说明。



 


 

从说明可以大概看说意思是:

1. 编译过程中添加-ffunction-sections和-fdata-sections会在输出文件object中给每个函数和全局变量控制在一个section中并以对应的函数名或全局变量名命名,

2. 链接过程中-Wl,--gc-sections,因为链接时查找符号是以section为单元进行引用的,对于没有引用到的符号,对应的section也不会引进来,故排除掉了无用的函数和全局变更,从而减少可执行文件的大小。

 

注意:从文档来看,这些选项对-g和gprof会有些影响,需要关注下,具体影响我还没去深挖。

 

这里用readelf命令查看section来比较下使用与未使用编译选项的结果做一下检查,结果如下:

a.未添加编译选项查看:

 

Shell代码  收藏代码
  1. sammor@ubuntu:~/test$ gcc -c test.c -o test.nooption  
  2. sammor@ubuntu:~/test$ readelf -t test.nooption   
  3. There are 11 section headers, starting at offset 0x154:  
  4. Section Headers:  
  5.   [Nr] Name  
  6.        Type            Addr     Off    Size   ES   Lk Inf Al  
  7.        Flags  
  8.   [ 0]   
  9.        NULL            00000000 000000 000000 00   0   0  0  
  10.        [00000000]:   
  11.   [ 1] .text  
  12.        PROGBITS        00000000 000034 000064 00   0   0  4  
  13.        [00000006]: ALLOC, EXEC  
  14.   [ 2] .rel.text  
  15.        REL             00000000 00044c 000048 08   9   1  4  
  16.        [00000000]:   
  17.   [ 3] .data  
  18.        PROGBITS        00000000 000098 000008 00   0   0  4  
  19.        [00000003]: WRITE, ALLOC  
  20.   [ 4] .bss  
  21.        NOBITS          00000000 0000a0 000000 00   0   0  4  
  22.        [00000003]: WRITE, ALLOC  
  23.   [ 5] .rodata  
  24.        PROGBITS        00000000 0000a0 000035 00   0   0  4  
  25.        [00000002]: ALLOC  
  26.   [ 6] .comment  
  27.        PROGBITS        00000000 0000d5 00002b 01   0   0  1  
  28.        [00000030]: MERGE, STRINGS  
  29.   [ 7] .note.GNU-stack  
  30.        PROGBITS        00000000 000100 000000 00   0   0  1  
  31.        [00000000]:   
  32.   [ 8] .shstrtab  
  33.        STRTAB          00000000 000100 000051 00   0   0  1  
  34.        [00000000]:   
  35.   [ 9] .symtab  
  36.        SYMTAB          00000000 00030c 0000f0 10  10   8  4  
  37.        [00000000]:   
  38.   [10] .strtab  
  39.        STRTAB          00000000 0003fc 00004f 00   0   0  1  
  40.        [00000000]:   

 b.添加编译选项查看。

 

Shell代码  收藏代码
  1. sammor@ubuntu:~/test$ readelf -t test.o  
  2. There are 18 section headers, starting at offset 0x1b4:  
  3. Section Headers:  
  4.   [Nr] Name  
  5.        Type            Addr     Off    Size   ES   Lk Inf Al  
  6.        Flags  
  7.   [ 0]   
  8.        NULL            00000000 000000 000000 00   0   0  0  
  9.        [00000000]:   
  10.   [ 1] .text  
  11.        PROGBITS        00000000 000034 000000 00   0   0  4  
  12.        [00000006]: ALLOC, EXEC  
  13.   [ 2] .data  
  14.        PROGBITS        00000000 000034 000000 00   0   0  4  
  15.        [00000003]: WRITE, ALLOC  
  16.   [ 3] .bss  
  17.        NOBITS          00000000 000034 000000 00   0   0  4  
  18.        [00000003]: WRITE, ALLOC  
  19.   [ 4] .data.lUnusedGrobleValue  
  20.        PROGBITS        00000000 000034 000004 00   0   0  4  
  21.        [00000003]: WRITE, ALLOC  
  22.   [ 5] .data.lUsedGrobleValue  
  23.        PROGBITS        00000000 000038 000004 00   0   0  4  
  24.        [00000003]: WRITE, ALLOC  
  25.   [ 6] .rodata  
  26.        PROGBITS        00000000 00003c 000035 00   0   0  4  
  27.        [00000002]: ALLOC  
  28.   [ 7] .text.unUsedFun  
  29.        PROGBITS        00000000 000071 000024 00   0   0  1  
  30.        [00000006]: ALLOC, EXEC  
  31.   [ 8] .rel.text.unUsedFun  
  32.        REL             00000000 000614 000018 08  16   7  4  
  33.        [00000000]:   
  34.   [ 9] .text.usedFun  
  35.        PROGBITS        00000000 000095 00001f 00   0   0  1  
  36.        [00000006]: ALLOC, EXEC  
  37.   [10] .rel.text.usedFun  
  38.        REL             00000000 00062c 000018 08  16   9  4  
  39.        [00000000]:   
  40.   [11] .text.main  
  41.        PROGBITS        00000000 0000b4 000021 00   0   0  1  
  42.        [00000006]: ALLOC, EXEC  
  43.   [12] .rel.text.main  
  44.        REL             00000000 000644 000018 08  16  11  4  
  45.        [00000000]:   
  46.   [13] .comment  
  47.        PROGBITS        00000000 0000d5 00002b 01   0   0  1  
  48.        [00000030]: MERGE, STRINGS  
  49.   [14] .note.GNU-stack  
  50.        PROGBITS        00000000 000100 000000 00   0   0  1  
  51.        [00000000]:   
  52.   [15] .shstrtab  
  53.        STRTAB          00000000 000100 0000b2 00   0   0  1  
  54.        [00000000]:   
  55.   [16] .symtab  
  56.        SYMTAB          00000000 000484 000140 10  17  13  4  
  57.        [00000000]:   
  58.   [17] .strtab  
  59.        STRTAB          00000000 0005c4 00004f 00   0   0  1  
  60.        [00000000]:   

通过比较看出,添加编译选项的输出文件中,确实以每个符号进行取名作为一个section,最终链接出来的可执行文件也不包含无用的函数和全局变量了。

 

相关文章:

http://elinux.org/Function_sections

http://blog.sina.com.cn/s/blog_602f87700100t0t5.html

0 0
原创粉丝点击