嵌入式设备flash空间有限,为节省成本,一般程序的存储空间都会非常小,这就需要我们将程序中不需要的内容清除,或者是直接对程序进行压缩处理,以达到减小执行程序大小的目的。
控制嵌入式程序大小有下面几种方法有:
1.使用strip命令清除符号表信息
2.使用objcopy生成特定的格式文件
3.使用tar zip等命令对程序进行压缩处理
在实际应用中发现下面两个问题点:
1.为什么静态库使用strip之后不能再被成功链接?
2.为什么有些时候objcopy生成的bin不能被执行?
(一)strip命令使用
strip经常用来去除目标文件中的一些符号表、调试符号表信息,以减小程序的大小。
1.1原理:
strip 命令减少 XCOFF(Common Object File Format) 对象文件的大小。strip 命令从 XCOFF 对象文件中有选择地除去行号信息、重定位信息、调试段、typchk 段、注释段、文件头以及所有或部分符号表。COFF文件格式可以查看《Common Object File Format》中的第七章
1.2 file命令查看文件是否已经strip
使用file main_s可以看到文件not stripped,执行strip之后变成了stripped。另外文件大小由原来的8840Byte变成了6328byte
biao@ubuntu:~/test/demo_elf$ file main_s
main_s: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0c91436f1562e4221a28c0d19039361f7d51285b, not stripped
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ ll main_s
-rwxrwxr-x 1 biao biao 8840 May 22 19:50 main_s*
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ strip main_s
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ file main_s
main_s: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0c91436f1562e4221a28c0d19039361f7d51285b, stripped
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ ll main_s
-rwxrwxr-x 1 biao biao 6328 May 22 19:51 main_s*
biao@ubuntu:~/test/demo_elf$
1.3 nm命令查看符号表
strip之前可以看到所有的符号表信息, strip之后什么东西也没有了
biao@ubuntu:~/test/demo_elf$ nm main_s
0000000000601048 B __bss_start
0000000000601048 b completed.7585
0000000000601028 D __data_start
0000000000601028 W data_start
0000000000400460 t deregister_tm_clones
00000000004004e0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601030 D __dso_handle
0000000000600e28 d _DYNAMIC
0000000000601048 D _edata
0000000000601060 B _end
.................
0000000000400660 T __libc_csu_fini
00000000004005f0 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000400526 T main
U printf@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
0000000000400430 T _start
0000000000601048 D __TMC_END__
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ strip main_s
biao@ubuntu:~/test/demo_elf$ nm main_s
nm: main_s: no symbols
biao@ubuntu:~/test/demo_elf$
1.4 readelf命令查看elf文件信息
执行strip之后,Number of section headers 变小了。
biao@ubuntu:~/test/demo_elf$ readelf -h main_s
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430
Start of program headers: 64 (bytes into file)
Start of section headers: 6856 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 28
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ strip main_s
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ readelf -h main_s
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430
Start of program headers: 64 (bytes into file)
Start of section headers: 4472 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
biao@ubuntu:~/test/demo_elf$
1.5动态库使用strip
动态库在elf文件格式中是被定义为可连接文件。它里面有elft头文件,程序表头,节表头,节。节里面又有表项。还有一些符号表等信息。
在动态库中,可以先进行strip,然后再连接程序,也可以先连接程序,然后再对可执行文件进行strip操作。
1.6静态库使用strip
静态库就是将所需要的.o文件组合到一起,然后添加了一个Unix archiver (ar) files 类型的文件头信息,告诉系统这里一个库LIB。
.o文件在elf中被定义为:可重定位文件。
在静态库的使用中,不能在链接之前进行strip操作,应为strip会将符号表和索引表给删除了,静态链接的时候就找不到相应的索引和符号了。
对于静态库,需要在链接之后再进行strip操作。这样就不会出现索引和符号表找不到的问题了。
关于elf文件格式可以查看《Understanding_ELF.pdf》文档。
(二)objcopy命令的使用
2.1 功能:
objcopy是将目标文件的一部分或者全部内容拷贝到另外一个目标文件中,或者实现目标文件的格式转换。
2.2 描述:
objcopy 工具使用 BFD(Binary format descriptor, 即二进制文件格式描述符)库读写目标文件,它可以将一个目标文件的内容拷贝到另外一个目标文件当中。 objcopy 通过它的选项来控制其不同的动作,它可以将目标文件拷贝成和原来的文件不一样的格式。需要注意的是 objcopy 能够在两种格式之间拷贝一个完全链接的文件,在两种格式之间拷贝一个可重定位的目标文件可能不会正常地工作。
objcopy 在做转换的时候会创建临时文件,然后将这些临时文件删除。 objcopy 使用 BFD 来做它所有的转换工作;它访问 BFD 中描述的所有格式,可以不必指定就识别大多数的格式。
通过指定输出目标为 srec (例如 -O srec ), objcopy 可以用来生成 S-record 文件。
通过指定输入目标为二进制文件(例如 -O binary ), objcopy 可以生成原始格式的二进制文件。当 objcopy 生成一个原始格式的二进制文件的时候,它会生成输入的目标文件的基本内存拷贝,然后所有的标号和可重定位信息都会被去掉。内存拷贝开始于最低段的加载地址,拷贝到输出文件
2.3 关于可执行文件格式
不同的嵌入式环境中,其组织可执行文件的格式也不相同,主要以下几种: ELF 文件格式、 S-record 文件格式、 HEX 文件格式、 bin 文件格式。
2.3.1 BIN文件格式
原始的二进制格式,内部没地址标记,直接照二进制格式下载,并且照绝对地址烧写到 Flash 中就可以启动了,而如果下载运行,则下载到编译时的地址即可。
2.3.2 ELF文件格式(Executable and linking format)
Executable and linking format (ELF)文件是Linux系统 下的一种常用、可移植目标文件( object file )格式,它有三种主要类型:
可重定位文件( Relocatable File ):包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。
可执行文件( Executable File ):包含适合于执行的一个程序,此文件规定了 exec() 如何创建一个程序的进程映像。
共享目标文件( Shared Object File ):包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器( Dynamic Linker )可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
2.3.3 S-Record文件格式
S-Record 文件遵循Motorola制定的格式规范,是一种标准的、可打印格式的文件。 S-Record 文件是通过对链接器生成的目标程序或数据进行编码生成的,适用于在计算机平台间传送,也可以在编辑后用于交叉平台间的传送。 S-Record 文件编码简单,可以通过IDE下载,但无法在线实时调试。 S-Record 文件是由多条记录组成的,每条记录都是由5个字段组成的ASCII字符串。
2.3.4 HEX文件格式
Intel HEX 文件是记录文本行的 ASCII 文本文件,在 Intel HEX 文件中,每一行是一个 HEX 记录,由十六进制数组成的机器码或数据常量。 Intel HEX 文件经常被用于将程序或数据传输存储到 ROM 、 EPROM 。大多数编程器和模拟器使用 Intel HEX 文件。
2.4 可执行文件格式的差别
- HEX 文件是包括地址信息的,而 BIN 文件格式只包括了数据本身,在烧写或下载 HEX 文件的时候,一般都不需要用户指定地址,因为 HEX 文件内部的信息已经包括了地址。而烧写 BIN 文件的时候,用户是一定需要指定地址信息的。
- BIN 文件格式对二进制文件而言,其实没有”格式”。文件只是包括了纯粹的二进制数据。
- HEX 文件格式文件都是由记录( RECORD )组成的。在 HEX 文件里面,每一行代表一个记录。记录类型包括:记录数据域,文件结束域,扩展线性地址的记录,扩展段地址的记录。在上面的后2种记录,都是用来提供地址信息的。每次碰到这2个记录的时候,都可以根据记录计算出一个“基”地址。对于后面的数据记录,计算地址的时候,都是以这些“基”地址为基础的。
- AXF 是Arm特有的文件格式,它除了包含 bin 文件外,还额外包括了许多其他调试信息。在下载到目标板的时候,烧入ROM还是 bin 文件,额外的调试信息会被去掉
一般来说,可以由 elf 文件转化为其它两种文件, hex 也可以直接转换为 bin 文件,但是 bin 要转化为 hex 文件必须要给定一个基地址。而 hex 和 bin 不能转化为 elf 文件,因为 elf 的信息量要大。另外还有一种 ads 的调试文件 axf ,它可以用以下命令 fromelf -nodebug xx.axf -bin xx.bin 转化为 bin 文件。
(三)使用tar zip等命令对程序进行压缩处理
这种方式在uboot中比较常见,将制作好的运行程序,再使用第三方的工具进行压缩。系统运行的时候,将程序从flash中读取出来,然后再对应的使用解压程序将程序解压到指定位置去运行。在uboot的程序中,它是有一部分进行了压缩,一部分没有进行压缩。程序被加载到内存中去后,先运行没有压缩的部分,从未压缩部分程序中可以知道后面程序使用的压缩方式,压缩文件大小等等信息,然后再进行进一步的解压的一个工作。
这种压缩的方式可以达到减小程序大小的目的,只是在使用的时候,压缩方式和解压方式要对应好,不然也是会遇到很多的问题。
(四)结尾
回答最开始提出的两个问题:
(1)为什么静态库不能使用strip
我们使用readelf -S 查看strip之前和之后的静态库之后,我们发现,所有的重定位节.rela. 都已经被清除了,这样在链接的时候,在需要重定位的地方,自然就是会出现找不到符号表的问题。所以静态库应该是在链接之后再进行strip操作。
biao@ubuntu:~/test/demo_elf$ readelf -S libpart.a
File: libpart.a(part.o)
There are 14 section headers, starting at offset 0x428:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004d 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000318
0000000000000060 0000000000000018 I 12 1 8
[ 3] .data PROGBITS 0000000000000000 00000090
0000000000000010 0000000000000000 WA 0 0 8
[ 4] .rela.data RELA 0000000000000000 00000378
0000000000000018 0000000000000018 I 12 3 8
[ 5] .bss NOBITS 0000000000000000 000000a0
0000000000000000 0000000000000000 WA 0 0 1
[ 6] .rodata PROGBITS 0000000000000000 000000a0
0000000000000028 0000000000000000 A 0 0 1
[ 7] .comment PROGBITS 0000000000000000 000000c8
0000000000000035 0000000000000001 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 000000fd
0000000000000000 0000000000000000 0 0 1
[ 9] .eh_frame PROGBITS 0000000000000000 00000100
0000000000000058 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000390
0000000000000030 0000000000000018 I 12 9 8
[11] .shstrtab STRTAB 0000000000000000 000003c0
0000000000000066 0000000000000000 0 0 1
[12] .symtab SYMTAB 0000000000000000 00000158
0000000000000180 0000000000000018 13 9 8
[13] .strtab STRTAB 0000000000000000 000002d8
0000000000000039 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)
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ strip libpart.a
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$
biao@ubuntu:~/test/demo_elf$ readelf -S libpart.a
File: libpart.a(part.o)
There are 9 section headers, starting at offset 0x1a0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000004d 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000090
0000000000000010 0000000000000000 WA 0 0 8
[ 3] .bss NOBITS 0000000000000000 000000a0
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .rodata PROGBITS 0000000000000000 000000a0
0000000000000028 0000000000000000 A 0 0 1
[ 5] .comment PROGBITS 0000000000000000 000000c8
0000000000000035 0000000000000001 MS 0 0 1
[ 6] .note.GNU-stack PROGBITS 0000000000000000 000000fd
0000000000000000 0000000000000000 0 0 1
[ 7] .eh_frame PROGBITS 0000000000000000 00000100
0000000000000058 0000000000000000 A 0 0 8
[ 8] .shstrtab STRTAB 0000000000000000 00000158
0000000000000047 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)
biao@ubuntu:~/test/demo_elf$
(2)为什么有些人制作的bin文件不能被执行?
上面有解释到“原始的二进制格式,内部没地址标记,直接照二进制格式下载,并且照绝对地址烧写到 Flash 中就可以启动了,而如果下载运行,则下载到编译时的地址即可。”那为什么直接下载到flash的绝对位置就可以启动呢?而直接下载却需要指定地址呢?
这里需要解释下flash中的程序是怎么运行的。有些flash是可以直接运行启动,有些flash是不能直接运行启动的。但不管是哪种方式,一般的操作都是系统程序起来之后,会去bin文件所在的flash位置,将bin文件拷贝到内存中指定的位置然后再去运行,其实它也是相当于指定了地址,只是这个地址是又系统来指定而不是由用户直接输入而已。
制作的bin文件不能运行,很大的可能就是所指定的位置不对。
Reference:
《Common Object File Format》
http://www.ti.com/lit/an/spraao8/spraao8.pdf
《linux中的strip命令简介------给文件脱衣服》
https://blog.csdn.net/stpeace/article/details/47090255
《ELF 格式解析》
https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf
《elf文件格式总结》
https://blog.csdn.net/flydream0/article/details/8719036
《Linux命令学习手册-objcopy》
https://zhuanlan.zhihu.com/p/115834422
《深入理解静态链接》
https://www.anquanke.com/post/id/184434