打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
S-Records介绍+objcopy命令
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://www.blogbus.com/ooooooo-logs/1361958.html
今天有位同事来问如何使用objcopy将一个s-records文件转化成一个binary文件。我虽然知道objcopy,但却不清楚到底什么是s-records格式。
经过一番网上搜索,终于知道s-records原来是Motorola推出的一种标准文件格式。用来将数据从pc flash到目标平台,这在嵌入式上广为应用。
赶紧仔细查一下我们自己的shx文件的内容(我过去从来没有关心过),果然,其内容就是s-records格式的。恍然大悟!这正是我喜欢帮助别人解决问题的原因,因为自己也可以从这个过程中学习到平常可能永远也不会在意的知识。
大致说一下s-records的格式:
s-records是文本文件格式的;
每一行都是一个s-records纪录;
每一行的开头都是大写的S;加上后面的一个数字,代表当前纪录的类型;
算了,还是把从网上得到的格式文档拷贝过来吧(http://www.amelek.gda.pl/avr/uisp/srecord.htm):
The general format of an S-record follows:
+-------------------//------------------//-----------------------+
| type | count | address  |            data           | checksum |
+-------------------//------------------//-----------------------+
type -- A char[2] field. These characters describe the type of record (S0, S1, S2, S3, S5, S7, S8, or S9).
count -- A char[2] field. These characters when paired and interpreted as a hexadecimal value, display the count of remaining character pairs in the record.
address -- A char[4,6, or 8] field. These characters grouped and interpreted as a hexadecimal value, display the address at which the data field is to be loaded into memory. The length of the field depends on the number of bytes necessary to hold the address. A 2-byte address uses 4 characters, a 3-byte address uses 6 characters, and a 4-byte address uses 8 characters.
data -- A char [0-64] field. These characters when paired and interpreted as hexadecimal values represent the memory loadable data or descriptive information.
checksum -- A char[2] field. These characters when paired and interpreted as a hexadecimal value display the least significant byte of the ones complement of the sum of the byte values represented by the pairs of characters making up the count, the address, and the data fields.
objcopy [选项]... 输入文件 [输出文件]  http://vaqeteart.iteye.com/blog/1129693
[功能]
将目标文件的一部分或者全部内容拷贝到另外一个目标文件中,或者实现目标文件的格式转换。
[描述]
objcopy工具使用BFD库读写目标文件,它可以将一个目标文件的内容拷贝到另外一个目标文件当中。objcopy通过它的选项来控制其不同的动作,它可以将目标文件拷贝成和原来的文件不一样的格式。需要注意的是objcopy能够在两种格式之间拷贝一个完全链接的文件,在两种格式之间拷贝一个可重定位的目标文件可能不会正常地工作。
objcopy在做转换的时候会创建临时文件,然后将这些临时文件删除。objcopy使用BFD来做它所有的转换工作;它访问BFD中描述的所有格式,可以不必指定就识别大多数的格式。
通过指定输出目标为srec(例如 -O srec),objcopy可以用来生成S-record文件。
通过指定输入目标为而进制文件(例如-O binary),objcopy可以生成原始格式的二进制文件。当objcopy生成一个原始格式的二进制文件的时候,它会生成输入的目标文件的基本内存拷贝,然后所有的标号和可重定位信息都会被去掉。内存拷贝开始于最低段的加载地址,拷贝到输出文件。
当生成一个S-record或者原始的二进制文件的时候,可以使用-S这个很有用的选项选项来移除一些包含调试信息的节。有时-R可以用来移除一些二进制文件不需要的节。
注意:objcopy工具不能用来改变文件的大端和小端属性。
命令参数:
infile/outfile
源文件/目标文件,如果不指定目标文件那么objcopy将会创建一个临时文件,并且将其命名为源文件。
命令选项:
-I bfdname
--input-target=bfdname
指定输入文件的bfdname,可取值elf32-little,elf32-big等。
-O bfdname
--output-target=bfdname
指定输出文件的bfdname
-F bfdname
--target=bfdname
指定输入、输出文件的bfdname,目标文件格式,只用于在目标和源之间传输数据,不转换。
-j sectionname
--only-section=sectionname
只将由sectionname指定的section拷贝到输出文件,可以多次指定,并且注意如果使用不当会导致输出文件不可用。
-R sectionname
--remove-section=sectionname
从输出文件中去除掉由sectionname指定的section,可以多次指定,并且注意如果使用不当会导致输出文件不可用。
-S
--strip-all
不从源文件拷贝符号信息和relocation信息。
-g
--strip-debug
不从源文件拷贝调试符号信息和相关的段。对使用-g编译生成的可执行文件执行之后,生成的结果几乎和不用-g进行编译生成可执行文件一样。
--strip-unneeded
去掉所有重定位处理不需要的符号。
-K symbolname
--keep-symbol=symbolname
strip的时候,保留由symbolname指定的符号信息。可以指定多次。
-N symbolname
--strip-symbol=symbolname
不拷贝由symbolname指定的符号信息,可以多次指定。
-G symbolname
--keep-global-symbol=symbolname
只保留symbolname为全局的,让其他的都是文件局部的变量这样外部不可见,这个选项可以多次指定。
-L symbolname
--localize-symbol=symbolname
将变量symbolname变成文件局部的变量。可以多次指定。
-W symbolname
--weaken-symbol=symbolname
弱化变量。
--globalize-symbol=symbolname
让变量symbolname变成全局范围,这样它可以在定义它的文件外部可见。可以多次指定。
-w
--wildcard
允许对其他命令行选项中的symbolnames使用正则表达式。问号(?),星号(*),反斜线(\),和中括号([])操作可以用在标号名称的任何位置。如果标号的第一个字符是感叹号(!),那么表示相反的含义,例如:
-w -W !foo -W fo*
表示objcopy将要弱化所有以"fo"开头的标号,但是除了标号"foo"。
-x
--discard-all
不从源文件中拷贝非全局变量。
-X
--discard-locals
不拷贝编译生成的局部变量(一般以L或者..开头)。
-b byte
--byte=byte
只保留输入文件的每个第byte个字节(不会影响头部数据)。byte的范围可以是0到interleave-1。这里,interleave通过-i选项指定,默认为4。将文件创建成程序rom的时候,这个命令很有用。它经常用于srec输出目标。
-i interleave
--interleave=interleave
每隔interleave字节拷贝1 byte。通过-b选项指定选择哪个字节,默认为4。如果不指定-b那么objcopy会忽略这个选项。
--gap-fill val
在section之间的空隙中填充val,
--set-start val
设定新文件的起始地址为val,并不是所有格式的目标文件都支持设置起始地址。
--change-start incr
--adjust-start incr
通过增加incr量来调整起始地址,并不是所有格式的目标文件都支持设置起始地址。
--change-address incr
--adjust-vma incr
通过增加incr量调整所有sections的VMA(virtual memory address)和LMA(linear memory address),以及起始地址。
--change-section-address section{=,+,-}val
--adjust-section-vma section{=,+,-}val
调整指定section的VMA/LMA地址。
--set-section-flags section=flag
指定指定section的flag,flag的取值可以alloc,contents, load, noload, readonly, code, data, rom, share, debug。我们可以设置一个没有内容的节的flag,但是清除一个有内容的节的flag是没有意义的--应当把相应的节移除。并不是所有的flags对于所有的目标文件都有意义。
--add-section sectionname=filename
在拷贝文件的时候,添加一个名为sectionname的section,该section的内容为filename的内容,大小为文件大小。这个选项只在那些可以支持任意名称section的文件好用。
--rename-section oldname=newname[,flags]
更改section的名
将一个section的名字从oldname更改为newname,同时也可以指定更改其flags。这个在执行linker脚本进行重命名的时候,并且输出文件还是一个目标文件并不成为可执行的连接文件,这个时候很有优势。
这个选项在输入文件是binary的时候很有用,因为这经常会创建一个名称为.data的section,例如,你想创建一个名称为.rodata的包含二进制数据的section,这时候,你可以使用如下命令:
objcopy -I binary -O <output_format> -B <architecture> \
--rename-section .data=.rodata,alloc,load,readonly,data,contents \
<input_binary_file> <output_object_file>
--add-gnu-debuglink=path-to-file
创建一个.gnu_debuglink节,这个节包含一个特定路径的文件引用,并且把它添加到输出文件中。
--only-keep-debug
对文件进行strip,移走所有不会被--strip-debug移走的section,并且保持调试相关的section原封不动。
这个选项应该和--add-gnu-debuglink选项一起使用,创建一个两部分的可执行文件。一个是已经被stripped的占用RAM空间以及发布的大小很小的二进制文件,另一个是调试信息文件,这个信息只有在调试的时候会用到。建立这样的两个文件的步骤如下:
a.像正常那样链接可执行文件,foo
b.运行"objcopy --only-keep-debug foo foo.dbg"创建一个包含调试信息的文件
c.运行"objcopy --strip-debug foo"创建一个去掉调试信息的(strip的)可执行文件
d.运行"objcopy --add-gnu-debuglink=foo.dbg foo",为strip的文件添加调试信息链接。
注意这里选择".dbg"扩展名是任意的,"--only-keep-debug"的步骤也是可选的,你也可以这样做:
a.像正常那样链接可执行文件,foo
b.将"foo"拷贝为"foo.full"
c.运行"objcopy --strip-debug foo"
d.运行"objcopy --add-gnu-debuglink=foo.full foo"
可见,使用--add-gnu-debuglink添加调试信息的可以是完全的可执行文件,不用非得用"--only-keep-debug"创建。
注意 这个只能用于完全链接的文件,对于一个目标文件来说这个功能没有意义,因为调试信息可能不完整。另外,gnu_debuglink特性当前一个包含调试信息的文件,不能多个文件用于每个文件一个目标文件。
-V
--version
显示objcopy的版本号。
@file 可以将选项集中到一个文件中,然后使用这个@file选项载入。
[举例]
a)后面的测试,我们查看采用的源代码如下:
[root@lv-k cppDemo]# cat main.cpp
#include <iostream>
using std::cout;
using std::endl;
void my_print();
int main(int argc, char *argv[])
{
my_print();
cout<<"hello!"<<endl;
return 0;
}
void  my_print()
{
cout<<"print!"<<endl;
}
b)对以上源代码进行编译,生成不含调试信息的可执行文件如下:
[root@lv-k cppDemo]# g++ main.cpp -o main
c)生成包含调试信息的可执行文件如下:
[root@lv-k cppDemo]# g++ -g main.cpp -o main.debug
d)采用"readelf -S main"查看其段信息如下:
[root@lv-k cppDemo]# readelf -S main
There are 29 section headers, starting at offset 0xca0:
Section Headers:
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .interp           PROGBITS        08048134 000134 000013 00   A  0   0  1
[ 2] .note.ABI-tag     NOTE            08048148 000148 000020 00   A  0   0  4
[ 3] .gnu.hash         GNU_HASH        08048168 000168 000030 04   A  4   0  4
[ 4] .dynsym           DYNSYM          08048198 000198 0000d0 10   A  5   1  4
[ 5] .dynstr           STRTAB          08048268 000268 000183 00   A  0   0  1
[ 6] .gnu.version      VERSYM          080483ec 0003ec 00001a 02   A  4   0  2
[ 7] .gnu.version_r    VERNEED         08048408 000408 000060 00   A  5   2  4
[ 8] .rel.dyn          REL             08048468 000468 000010 08   A  4   0  4
[ 9] .rel.plt          REL             08048478 000478 000048 08   A  4  11  4
[10] .init             PROGBITS        080484c0 0004c0 000017 00  AX  0   0  4
[11] .plt              PROGBITS        080484d8 0004d8 0000a0 04  AX  0   0  4
[12] .text             PROGBITS        08048580 000580 000268 00  AX  0   0 16
[13] .fini             PROGBITS        080487e8 0007e8 00001c 00  AX  0   0  4
[14] .rodata           PROGBITS        08048804 000804 00001a 00   A  0   0  4
[15] .eh_frame_hdr     PROGBITS        08048820 000820 000044 00   A  0   0  4
[16] .eh_frame         PROGBITS        08048864 000864 00010c 00   A  0   0  4
[17] .ctors            PROGBITS        08049970 000970 00000c 00  WA  0   0  4
[18] .dtors            PROGBITS        0804997c 00097c 000008 00  WA  0   0  4
[19] .jcr              PROGBITS        08049984 000984 000004 00  WA  0   0  4
[20] .dynamic          DYNAMIC         08049988 000988 0000e0 08  WA  5   0  4
[21] .got              PROGBITS        08049a68 000a68 000004 04  WA  0   0  4
[22] .got.plt          PROGBITS        08049a6c 000a6c 000030 04  WA  0   0  4
[23] .data             PROGBITS        08049a9c 000a9c 000004 00  WA  0   0  4
[24] .bss              NOBITS          08049aa0 000aa0 000098 00  WA  0   0  8
[25] .comment          PROGBITS        00000000 000aa0 000114 00      0   0  1
[26] .shstrtab         STRTAB          00000000 000bb4 0000e9 00      0   0  1
[27] .symtab           SYMTAB          00000000 001128 000510 10     28  53  4
[28] .strtab           STRTAB          00000000 001638 0003f4 00      0   0  1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
*将文件转换成S-record格式:
[root@lv-k cppDemo]#objcopy -O srec main main.srec
这里,如果不指定main.srec那么会改变原来的文件,转换通常涉及到text段,也可以转换.o文件。当然,转换之后的文件不能直接运行了。在生成s-record过程中,有时需要用选项“-S”,“-R”去除掉binary文件,s-record文件不需要的相应信息。
输出的文件内容类似如下:
[root@lv-k cppDemo]# head main.rec
S00B00006D61696E2E726563E7
S315080481342F6C69622F6C642D6C696E75782E736F57
S308080481442E3200C6
S31508048148040000001000000001000000474E550016
S3150804815800000000020000000600000009000000F4
S3150804816803000000090000000100000005000000E3
S31508048178012A102100000000090000000B00000075
S31508048188AC4BE3C021FDF40914980C4379496BB642
S3150804819800000000000000000000000000000000C5
S315080481A83701000000000000540000001200000017
...省略...
可见srec格式的文件是文本形式的。
*将文件转换成rawbinary格式:
[root@lv-k cppDemo]# objcopy -O binary main main.bin
这里同样,转换之后的main.bin也是不可直接运行的。比较一下三种格式文件的大小:
[root@lv-k cppDemo]# ls -l main main.rec main.bin
-rwxr-xr-x 1 root root 6700 07-07 18:04 main
-rwxr-xr-x 1 root root 6508 07-19 16:42 main.bin
-rwxr-xr-x 1 root root 7366 07-19 16:34 main.rec
将object文件转换成raw binary格式时,通常将去除掉symbols和relocation信息。
*生成一个不含重定位以及标号目标文件:
[root@lv-k cppDemo]# objcopy -S main main.stripall
这里,生成的文件,仍然可以运行。查看大小对比如下:
[root@lv-k cppDemo]# ls -l main.stripall main
-rwxr-xr-x 1 root root 6700 07-07 18:04 main
-rwxr-xr-x 1 root root 4296 07-19 17:02 main.stripall
经过实践,如果对包含调试信息的可执行文件进行这样的操作,生成的文件大小一样。
可以对比main的信息,查看文件的段信息:
[root@lv-k cppDemo]#  readelf main.stripall -S
There are 27 section headers, starting at offset 0xc90:
Section Headers:
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .interp           PROGBITS        08048134 000134 000013 00   A  0   0  1
[ 2] .note.ABI-tag     NOTE            08048148 000148 000020 00   A  0   0  4
[ 3] .gnu.hash         GNU_HASH        08048168 000168 000030 04   A  4   0  4
[ 4] .dynsym           DYNSYM          08048198 000198 0000d0 10   A  5   1  4
[ 5] .dynstr           STRTAB          08048268 000268 000183 00   A  0   0  1
[ 6] .gnu.version      VERSYM          080483ec 0003ec 00001a 02   A  4   0  2
[ 7] .gnu.version_r    VERNEED         08048408 000408 000060 00   A  5   2  4
[ 8] .rel.dyn          REL             08048468 000468 000010 08   A  4   0  4
[ 9] .rel.plt          REL             08048478 000478 000048 08   A  4  11  4
[10] .init             PROGBITS        080484c0 0004c0 000017 00  AX  0   0  4
[11] .plt              PROGBITS        080484d8 0004d8 0000a0 04  AX  0   0  4
[12] .text             PROGBITS        08048580 000580 000268 00  AX  0   0 16
[13] .fini             PROGBITS        080487e8 0007e8 00001c 00  AX  0   0  4
[14] .rodata           PROGBITS        08048804 000804 00001a 00   A  0   0  4
[15] .eh_frame_hdr     PROGBITS        08048820 000820 000044 00   A  0   0  4
[16] .eh_frame         PROGBITS        08048864 000864 00010c 00   A  0   0  4
[17] .ctors            PROGBITS        08049970 000970 00000c 00  WA  0   0  4
[18] .dtors            PROGBITS        0804997c 00097c 000008 00  WA  0   0  4
[19] .jcr              PROGBITS        08049984 000984 000004 00  WA  0   0  4
[20] .dynamic          DYNAMIC         08049988 000988 0000e0 08  WA  5   0  4
[21] .got              PROGBITS        08049a68 000a68 000004 04  WA  0   0  4
[22] .got.plt          PROGBITS        08049a6c 000a6c 000030 04  WA  0   0  4
[23] .data             PROGBITS        08049a9c 000a9c 000004 00  WA  0   0  4
[24] .bss              NOBITS          08049aa0 000aa0 000098 00  WA  0   0  8
[25] .comment          PROGBITS        00000000 000aa0 000114 00      0   0  1
[26] .shstrtab         STRTAB          00000000 000bb4 0000d9 00      0   0  1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
由此可见去掉的两个信息分别是:
[27] .symtab           SYMTAB          00000000 001128 000510 10     28  53  4
[28] .strtab           STRTAB          00000000 001638 0003f4 00      0   0  1
*去掉指定名称的节:
[root@lv-k cppDemo]# objcopy -R .comment main main.remove
这里,去掉之后,可执行文件也是可以运行的。通过"readelf -S main.remove"可知,其中的.comment节已经没有了。和此“相反”的操作是通过-j将指定的节拷贝到目标文件。
*添加一个自定义的节到可执行文件并将一个文件内容添加到其中:
[root@lv-k cppDemo]# objcopy --add-section mysection=hello_text main main.add
这样,将会创建一个mysection的节并把hello_text的内容添加到其中。通过readelf -S main.add可以看到会多了一个"mysection"节。这个选项只在那些可以支持任意名称section的文件好用。添加到在我的centeros中编译的可执行文件中,可执行文件仍然可以运行。
*将指定的段拷贝出来:
[root@lv-k cppDemo]# objcopy -j mysection main.add section_hello
输入之后,输出如下:
[root@lv-k cppDemo]# objcopy -j mysection main.add section_hello
BFD: main.add: warning: Empty loadable segment detected, is this intentional ?
BFD: main.add: warning: Empty loadable segment detected, is this intentional ?
这样会将main.add中刚才添加的mysection拷贝出来,拷贝出来的内容存放在section_hello文件中。这个命令将指定的section拷贝到输出文件,可以多次指定,并且注意如果使用不当会导致输出文件不可用。通过file命令查看,发现生成的文件仍然是elf文件,不只有该段的内容。使用"readelf -S section_hello"查看文件内容,如下:
There are 5 section headers, starting at offset 0x144:
Section Headers:
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] mysection         PROGBITS        00000000 0000f4 000028 00      0   0  1
[ 2] .shstrtab         STRTAB          00000000 00011c 000025 00      0   0  1
[ 3] .symtab           SYMTAB          00000000 00020c 000020 10      4   2  4
[ 4] .strtab           STRTAB          00000000 00022c 000001 00      0   0  1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
readelf: Error: no .dynamic section in the dynamic segment
**分离可执行文件以及调试信息并将两者关联
这里,main.debug是包含调试信息的可执行文件(使用"gcc -g"编译生成),为了减小文件大小,并且同样可以进行调试,将可执行文件分成两个部分:将其中的调试信息提取出来之后保存成一个文件,再生成去掉调试信息的大小减少了的可执行文件,最后通过链接的形式将两个文件关联。
过程如下:
1)生成调试信息文件:
[root@lv-k cppDemo]# objcopy --only-keep-debug main.debug main.debuginfo
这样,会将调试信息提取,保存到文件main.debuginfo中。执行之后比较文件大小如下:
[root@lv-k cppDemo]# ls -l main.debuginfo main main.debug
-rwxr-xr-x 1 root root  6700 07-07 18:04 main
-rwxr-xr-x 1 root root 38932 07-07 18:04 main.debug
-rwxr-xr-x 1 root root 38628 07-21 09:47 main.debuginfo
这里,main是没有使用-g命令生成的可执行文件只是为了比较。
2)生成不含调试信息的可执行文件:
[root@lv-k cppDemo]# objcopy --strip-debug main.debug main.stripdebug
这样,就将原来可执行文件中的调试信息去掉,结果可执行的不含调试信息的可执行文件。生成之后,大小如下:
[root@lv-k cppDemo]# ls -l main main.debug main.stripdebug
-rwxr-xr-x 1 root root  6700 07-07 18:04 main
-rwxr-xr-x 1 root root 38932 07-07 18:04 main.debug
-rwxr-xr-x 1 root root  6632 07-21 09:49 main.stripdebug
3)为不含调试信息的可执行文件添加调试信息:
[root@lv-k cppDemo]# objcopy --add-gnu-debuglink=main.debuginfo main.stripdebug
这样,就为main.stripdebug文件添加了调试信息,现在可以运行"gdb main.stripdebug"进行调试了。运行完毕之后,main.stripdebug文件中会多了一个"[26] .gnu_debuglink    PROGBITS        00000000 000bb4 000014 00      0   0  1"节。运行这个命令之后,文件大小信息对比如下:
[root@lv-k cppDemo]# ls -l main main.debug main.debuginfo main.stripdebug
-rwxr-xr-x 1 root root  6700 07-07 18:04 main
-rwxr-xr-x 1 root root 38932 07-07 18:04 main.debug
-rwxr-xr-x 1 root root 38628 07-21 09:47 main.debuginfo
-rwxr-xr-x 1 root root  6720 07-21 09:51 main.stripdebug
注意,实践发现:a)使用上面的命令之后,main.debuginfo以及源代码main.cpp两个文件必须在同一个目录上面才能使用gdb进行调试,否则会在调试的时候出现找不到文件的问题。当然,就是使用-g的选项编译之后,也得让源文件放在正确的路径下面才能够在调试的时候载入文件。b)对于原来没有使用-g生成的可执行文件,也可以使用这个方法为它添加调试信息让它(原来没有用-g生成的并且添加调试信息之后的可执行文件)可以调试。
**
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Linux程序减肥三步走
阿当正传
Linux命令学习手册
log4j2 使用详解
内存区域分配与ELF 之类的关系
Linux下查看.so和可执行文件是否debug编译
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服