ARM学习(39)ARM-GCC编译出的Bin文件过大解决方案
笔者之前一直有遇到ARM-GCC生成的bin文件过大的情况,就是要烧录的bin文件高达上百M,比axf文件还大。
1、问题背景
如下图所示,笔者要烧录的bin文件是gd32vf103.bin,有384MB,elf文件才60KB,明显有问题,无法烧录。
生成的elf文件地址也没问题,因为加载到Trace32上面,地址也显示正常,是通过对比链接脚本观察得出来的结论。
- 启动代码地址:0x08000164
- main函数地址:0x20000010
Trace32 一般不做分散加载,加载到哪的地址就算哪,分散加载一般是启动代码做的,属于运行态。具体分散加载可以参考这篇文章ARM学习(4) 分散加载启动。
链接脚本的代码如下:
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
/* Run in FLASH */
flash (rxai!w) : ORIGIN = 0x08000000, LENGTH = 128k
ram (wxa!ri) : ORIGIN = 0x20000000, LENGTH = 32K
}
SECTIONS
{
__stack_size = DEFINED(__stack_size) ? __stack_size : 2K;
.init :
{
KEEP (*(SORT_NONE(.init)))
}>flash AT>flash
.boot :
{
KEEP (*(SORT_NONE(.boot)))
} >flash AT>flash
.entry :
{
KEEP (*(SORT_NONE(.entry)))
FILL(0xAA)
} >flash AT>flash
/* .sysinit :
{
FILL(0xAA)
obj/system_gd32vf103.o
FILL(0xAA)
}>flash AT>flash */
.ilalign :
{
FILL(0xAA)
. = ALIGN(4);
PROVIDE( _ilm_lma = . );
}>ram AT>ram
.ialign :
{
FILL(0xAA)
PROVIDE( _ilm = . );
}>ram AT>ram
.text :
{
FILL(0xAA)
*(.rodata .rodata.*)
*(.text.unlikely .text.unlikely.*)
*(.text.startup .text.startup.*)
*(.text .text.*)
*(.gnu.linkonce.t.*)
}>ram AT>ram
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}>ram AT>ram
. = ALIGN(4);
PROVIDE (__etext = .);
PROVIDE (_etext = .);/*0x80022c8*/
PROVIDE (etext = .);/*0x80022c8*/
PROVIDE( _eilm = . );
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}>ram AT>ram
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
} >ram AT>ram
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}>ram AT>ram
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}>ram AT>ram
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}>ram AT>ram
. = ALIGN(4);
PROVIDE( _eilm = . );
.lalign :
{
. = ALIGN(4);
PROVIDE( _data_lma = . );
}>ram AT>ram
.dalign :
{
. = ALIGN(4);
PROVIDE( _data = . );
}>ram AT>ram
.data :
{
*(.rdata)
*(.gnu.linkonce.r.*)
*(.data .data.*)
*(.gnu.linkonce.d.*)
. = ALIGN(8);
PROVIDE( __global_pointer$ = . + 0x800);
*(.sdata .sdata.*)
*(.gnu.linkonce.s.*)
. = ALIGN(8);
*(.srodata.cst16)
*(.srodata.cst8)
*(.srodata.cst4)
*(.srodata.cst2)
*(.srodata .srodata.*)
}>ram AT>ram
. = ALIGN(4);
PROVIDE( _edata = . );
PROVIDE( edata = . );
PROVIDE( _fbss = . ); /*0X200052A0 0X200002A0*/
PROVIDE( __bss_start = . );
.bss :
{
*(.sbss*)
*(.gnu.linkonce.sb.*)
*(.bss .bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(4);
} >ram AT>ram
. = ALIGN(8);
PROVIDE( _end = . ); /*0X2000,0340*/
PROVIDE( end = . );
.stack ORIGIN(ram) + LENGTH(ram) - __stack_size :
{
PROVIDE( _heap_end = . );
. = __stack_size;
PROVIDE( _sp = . );
} >ram AT>ram
}
flash的起始地址:0x08000000
RAM的起始地址:0x20000000
关键的地方如下面所示,
- entry在flash位置,而test 代码段放到了ram的位置,且加载地址也是ram地址,
- 这样一来就导致代码加载的地址也放到ram的位置,和前面启动代码的加载地址有gap,从0x0800000到0x20000000,
- 前后的代码均需要加载,在生成一个bin的情况,又需要把其加载到争取的偏移位置,看起来就只能通过补数据(填充0x0或者其他数据)到0x20000000,
- 最后就导致生成bin文件暴大,bin文件的size还取决于gap的大小
- 0x20000000-0x08000000=0x18000000,大约就是384MB,和笔者碰到一开始的文件大小就匹配上了。
.entry :
{
KEEP (*(SORT_NONE(.entry)))
FILL(0xAA)
} >flash AT>flash
.text :
{
FILL(0xAA)
*(.rodata .rodata.*)
*(.text.unlikely .text.unlikely.*)
*(.text.startup .text.startup.*)
*(.text .text.*)
*(.gnu.linkonce.t.*)
}>ram AT>ram
看起来关键问题是链接脚本的问题,那么如何修改呢?
2、问题解决方案
2.1 链接脚本修改
上面介绍到因为链接脚本中因为加载地址的问题,导致需要连续的bin文件,所以会生成大文件bin,那么我们修改加载地址,也放在flash,可以保证加载地址连续。类似于下面这样。
执行地址还是在ram,然后AT,加载地址在flash。
.text :
{
FILL(0xAA)
*(.rodata .rodata.*)
*(.text.unlikely .text.unlikely.*)
*(.text.startup .text.startup.*)
*(.text .text.*)
*(.gnu.linkonce.t.*)
}>ram AT>flash
.data :
{
*(.rdata)
*(.gnu.linkonce.r.*)
*(.data .data.*)
*(.gnu.linkonce.d.*)
. = ALIGN(8);
PROVIDE( __global_pointer$ = . + 0x800);
*(.sdata .sdata.*)
*(.gnu.linkonce.s.*)
. = ALIGN(8);
*(.srodata.cst16)
*(.srodata.cst8)
*(.srodata.cst4)
*(.srodata.cst2)
*(.srodata .srodata.*)
}>ram AT>flash
经过修改之后,编译后的bin大小正常,烧录进行也可以使用,
注意的点:要自己写copy函数,因为代码段是放在加载地址的,要在执行地址跑起来,就必须要搬代码到对应地址。
根据代码段的起始位置,可以编写copy函数,copy到对应位置。
__attribute__((section(".boot")))
extern void* _ilm_lma;
extern void* _ilm;
extern void* _eilm;
void copy(void)
{
unsigned char *pa,*pb;
unsigned int i=0;
pa=(unsigned char *)(&_ilm_lma);
pb=(unsigned char *)(&_ilm);
int text_size = (int)((char*)&_eilm-(char*)&_ilm);
for(i=0;i<(text_size);i++)
{
*pb = *pa;
pb++;
pa++;
}
}
通过看map文件,可以确认相关地址的正确性
至于数据段的搬移,本身启动代码里面就有相关的搬移代码。
/* Load data section */
la a0, _data_lma
la a1, _data
la a2, _edata
bgeu a1, a2, 2f
1:
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, 1b
2:
/* Clear bss section */
la a0, __bss_start
la a1, _end
bgeu a0, a1, 2f
1:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
由于代码还处于加载地方,所以会看到main函数出都是空的,没有任何指令。
注意的是如果开的-O2优化,则copy函数里面的逻辑会被优化成memcopy,导致无法完成拷贝,因为memcpy的代码还没有在代码段,所以不能进行优化,当然可以改成-O0,笔者尝试了可以拷贝过去。
笔者优化了一下copy函数,然后就正常了,将 拷贝的指针定义成volatile,则这里的拷贝编译器就不优化了。
extern void* _ilm_lma;
extern void* _ilm;
extern void* _eilm;
void copy(void)
{
volatile char *pa,*pb;
unsigned int i=0;
pa=(unsigned char *)(&_ilm_lma);
pb=(unsigned char *)(&_ilm);
int text_size = (int)((char*)&_eilm-(char*)&_ilm);
for(i=0;i<(text_size);i++)
{
*pb = *(volatile char*)pa;
pb++;
pa++;
}
}
可以看到下图中,右侧的main函数只有部分指令,当copy完之后,则所有的数据都有了。
当然为什么启动代码没有代码段的copy,因为代码本身就可以放到flash中,加载地址就是执行地址,这样就不用拷贝代码段。
2.2 不修改链接脚本
如果不修改链接脚本,则需要自己处理暴大的bin文件,将中间的无效数据处理掉。
通用需要分散加载,把text放到对应的ram地址,但是这次链接脚本里面没有响应的符号,表明加载地址等,需要自己考虑header等格式,然后搬移。
还有一个办法,就是通过0bjdump工具,生成一个个的小section bin文件,然后处理形成一个大bin文件。
- -j:表示保留的section名字,其他均去除。
3、参考
1、arm-gcc ld链接器中的链接脚本格式编写