ld链接时巨大的二进制大小
Posted
技术标签:
【中文标题】ld链接时巨大的二进制大小【英文标题】:Huge Binary size while ld Linking 【发布时间】:2017-03-22 23:24:52 【问题描述】:我有一个链接器脚本,用于链接 imx6q(cortex-A9) 的代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)
/* SEARCH_DIR(.) */
GROUP(libgcc.a libc.a)
/* INPUT (crtbegin.o crti.o crtend.o crtn.o) */
MEMORY
/* IROM (rwx) : ORIGIN = 0x00000000, LENGTH = 96K */
IRAM (rwx) : ORIGIN = 0x00900000, LENGTH = 256K
IRAM_MMU (rwx): ORIGIN = 0x00938000, LENGTH = 24K
IRAM_FREE(rwx): ORIGIN = 0x00907000, LENGTH = 196K
DDR (rwx) : ORIGIN = 0x10000000, LENGTH = 1024M
/* PROVIDE(__cs3_heap_start = _end); */
SECTIONS
.vector (ORIGIN(IRAM) + LENGTH(IRAM) - 144 ):ALIGN (32)
__ram_vectors_start = . ;
. += 72 ;
__ram_vectors_end = . ;
. = ALIGN (4);
>IRAM
. = ORIGIN(DDR);
.text(.) :ALIGN(8)
*(.entry)
*(.text)
/* __init_array_start = .; */
/* __init_array_end = .; */
. = ALIGN (4);
__text_end__ = .;
>DDR
.data :ALIGN(8)
*(.data .data.*)
__data_end__ = .;
.bss(__data_end__) :
. = ALIGN (4);
__bss_start__ = .;
*(.shbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
__bss_end__ = .;
/* . += 10K; */
/* . += 5K; */
top_of_stacks = .;
. = ALIGN (4);
. += 8;
free_memory_start = .;
.mmu_page_table :
__mmu_page_table_base__ = .;
. = ALIGN (16K);
. += 16K;
>IRAM_MMU
_end = .;
__end = _end;
PROVIDE(end = .);
当我构建时,二进制大小只有 6 KB。但我不能添加任何初始化变量。当我添加一个初始化变量时,二进制大小会跳到 ~246 MB。这是为什么?我试图通过指定确切的位置并为数据段提供 >DDR 来链接文本部分之后的位置的数据段。尽管这似乎将二进制文件大小减小到 6 KB,但二进制文件无法启动。我怎样才能将我的代码保存在 DDR 中,并将数据、bss、堆栈和堆保存在内部 ram 本身中,并且二进制大小很轻?
我在另一个帖子中读到“在链接描述文件中使用 MEMORY 标记应该可以解决内存浪费的问题”,请问如何做到这一点?
linker script wastes my memory
请询问是否还有其他需要。我对链接器脚本没有任何经验。请帮忙
没有给出初始化数据的二进制的readelf --sections输出如下,
There are 19 section headers, starting at offset 0xd804:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .vector NOBITS 0093ff80 007f80 000048 00 WA 0 0 32
[ 2] .text PROGBITS 10000000 008000 0016fc 00 AX 0 0 8
[ 3] .text.vectors PROGBITS 100016fc 0096fc 000048 00 AX 0 0 4
[ 4] .text.proc PROGBITS 10001744 009744 000034 00 AX 0 0 4
[ 5] .bss NOBITS 0093ffc8 007fc8 000294 00 WA 0 0 4
[ 6] .mmu_page_table NOBITS 00938000 008000 004000 00 WA 0 0 1
[ 7] .comment PROGBITS 00000000 009778 00001f 01 MS 0 0 1
[ 8] .ARM.attributes ARM_ATTRIBUTES 00000000 009797 00003d 00 0 0 1
[ 9] .debug_aranges PROGBITS 00000000 0097d8 000108 00 0 0 8
[10] .debug_info PROGBITS 00000000 0098e0 0018a7 00 0 0 1
[11] .debug_abbrev PROGBITS 00000000 00b187 00056f 00 0 0 1
[12] .debug_line PROGBITS 00000000 00b6f6 00080e 00 0 0 1
[13] .debug_frame PROGBITS 00000000 00bf04 000430 00 0 0 4
[14] .debug_str PROGBITS 00000000 00c334 0013dd 01 MS 0 0 1
[15] .debug_ranges PROGBITS 00000000 00d718 000020 00 0 0 8
[16] .shstrtab STRTAB 00000000 00d738 0000cb 00 0 0 1
[17] .symtab SYMTAB 00000000 00dafc 000740 10 18 60 4
[18] .strtab STRTAB 00000000 00e23c 000511 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
并且带有给定初始化数据的二进制文件的 readelf --sections 输出是,
There are 20 section headers, starting at offset 0xd82c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .vector NOBITS 0093ff80 007f80 000048 00 WA 0 0 32
[ 2] .text PROGBITS 10000000 008000 0016fc 00 AX 0 0 8
[ 3] .text.vectors PROGBITS 100016fc 0096fc 000048 00 AX 0 0 4
[ 4] .text.proc PROGBITS 10001744 009744 000034 00 AX 0 0 4
[ 5] .data PROGBITS 0093ffc8 007fc8 000004 00 WA 0 0 8
[ 6] .bss NOBITS 0093ffcc 007fcc 000294 00 WA 0 0 4
[ 7] .mmu_page_table NOBITS 00938000 008000 004000 00 WA 0 0 1
[ 8] .comment PROGBITS 00000000 009778 00001f 01 MS 0 0 1
[ 9] .ARM.attributes ARM_ATTRIBUTES 00000000 009797 00003d 00 0 0 1
[10] .debug_aranges PROGBITS 00000000 0097d8 000108 00 0 0 8
[11] .debug_info PROGBITS 00000000 0098e0 0018b6 00 0 0 1
[12] .debug_abbrev PROGBITS 00000000 00b196 000580 00 0 0 1
[13] .debug_line PROGBITS 00000000 00b716 00080e 00 0 0 1
[14] .debug_frame PROGBITS 00000000 00bf24 000430 00 0 0 4
[15] .debug_str PROGBITS 00000000 00c354 0013dd 01 MS 0 0 1
[16] .debug_ranges PROGBITS 00000000 00d738 000020 00 0 0 8
[17] .shstrtab STRTAB 00000000 00d758 0000d1 00 0 0 1
[18] .symtab SYMTAB 00000000 00db4c 000770 10 19 62 4
[19] .strtab STRTAB 00000000 00e2bc 000513 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
希望这就够了……!!!
注意:我使用 arm-none-eabi-gcc 进行链接。
【问题讨论】:
您可以为您的二进制文件发布readelf --sections
的输出吗?
已添加输出。在第二种情况下,二进制文件的大小变为 ~246MB,前一种情况下为 6 KB....
很明显(类似的问题(或 Q/A)在 SO 上)。 IRAM LENGTH = 256K
和 DDR(256MB) - IRAM(9MB) ~=256MB
二进制文件增加到 246MB。 Read about VMA/LMA 并将其用作搜索词。 BINARY 必须是一个连续的井二进制块!没有部分信息。你看到问题了吗? gnu LD 脚本有 AT
类型指令和 LMA/VMA 来解决这个问题。
How to run code from RAM on ARM architecture的可能重复
【参考方案1】:
如果您没有使用链接器脚本的经验,那么要么使用一个可以正常工作的脚本,要么制作或借用一个更简单的脚本。这是一个简单的例子,它应该说明最有可能发生的事情。
MEMORY
bob : ORIGIN = 0x00001000, LENGTH = 0x100
ted : ORIGIN = 0x00002000, LENGTH = 0x100
alice : ORIGIN = 0x00003000, LENGTH = 0x100
SECTIONS
.text : *(.text*) > bob
.data : *(.text*) > ted
.bss : *(.text*) > alice
第一个程序
.text
.globl _start
_start:
mov r0,r1
mov r1,r2
b .
并不是一个真正的程序,只是在一个段中创建一些字节而已。
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00001000 001000 00000c 00 AX 0 0 4
.text 中的 12 个字节,位于内存中的地址 0x1000,这正是我们告诉它要做的。
如果我使用 -objcopy a.elf -O binary a.bin 我得到一个 12 字节的文件,正如预期的那样,“二进制”文件格式是一个内存映像,从地址中包含一些内容的第一个地址开始空间并以地址空间中内容的最后一个字节结束。所以二进制不是 0x1000+12 字节,而是 12 字节广告,用户必须知道它需要在 0x1000 处加载。
所以稍微改变一下:
.text
.globl _start
_start:
mov r0,r1
mov r1,r2
b .
.data
some_data: .word 0x12345678
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00001000 001000 00000c 00 AX 0 0 4
[ 2] .data PROGBITS 00002000 002000 000004 00 WA 0 0 1
现在我们在 0x1000 处有 12 个字节,在 0x2000 处有 4 个字节,所以 -O 二进制文件必须给我们一个从第一个定义字节到最后一个定义的字节的内存映像,即 0x1000+4。
果然 4100 字节,这正是它所做的。
.text
.globl _start
_start:
mov r0,r1
mov r1,r2
b .
.data
some_data: .word 0x12345678
.bss
some_more_data: .word 0
给了
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00001000 001000 00000c 00 AX 0 0 4
[ 2] .data PROGBITS 00002000 002000 000004 00 WA 0 0 1
[ 3] .bss NOBITS 00003000 003000 000004 00 WA 0 0 1
现在我只有一个 4100 字节的文件,这实际上并不奇怪,假设引导程序将归零 .bss 所以这并没有增加“二进制”文件。
有亲密的关系。系统级设计。在链接描述文件和引导程序之间。对于看起来你正在尝试做的事情(只是 ram no rom),你可能可以使用一个更简单的链接器脚本,与我所拥有的一样,但如果你关心 .bss 被归零,那么你可以使用一些技巧使用:
MEMORY
ram : ORIGIN = 0x00001000, LENGTH = 0x3000
SECTIONS
.text : *(.text*) > ram
.bss : *(.text*) > ram
.data : *(.text*) > ram
确保至少有一个 .data 项,并且您的“二进制”将具有 bss 已经归零的完整图像,引导程序只需要设置堆栈指针并跳转到 main(如果这是用于 C )。
无论如何,希望您可以看到,从 12 字节跳转到 4100 字节是因为添加了 .data 元素,并且“二进制”格式必须填充“二进制”文件以使文件成为内存图像从带有数据的最低地址到带有数据的最高地址(在这种情况下从 0x1000 到 0x2000+sizeof(.data)-1)。更改链接描述文件、0x1000 和 0x2000,这一切都会改变。交换它们,将 .text 放在 0x2000 并将 .data 放在 0x1000,现在“二进制”文件必须是 0x2000-0x1000+sizeof(.text) 而不是 0x2000-0x1000+sizeof(.data)。或 0x100C 字节而不是 0x1004。回到第一个链接描述文件并在 0x20000000 处生成 .data 现在“二进制”将为 0x20000000-0x1000+sizeof(.data) 因为这是在单个文件中生成内存映像所需的信息量(包括填充)。
这很可能是正在发生的事情。如此处所示,只需添加一个数据字,文件大小就从 12 个字节变为 4100 个字节。
编辑。
如果你不加载数据,那么你的初始化变量将不会被初始化,就这么简单
unsigned int x = 5;
如果您丢弃 (NOLOAD) .data,则不会是 5。
如前所述,您可以将数据放入 .text 扇区,然后使用更多链接描述文件 foo,让引导程序找到该数据。
MEMORY
bob : ORIGIN = 0x00001000, LENGTH = 0x100
ted : ORIGIN = 0x00002000, LENGTH = 0x100
alice : ORIGIN = 0x00003000, LENGTH = 0x100
SECTIONS
.text : *(.text*) > bob
.data : *(.text*) > ted AT > bob
.bss : *(.text*) > alice AT > bob
这将创建一个 16 字节的“二进制”文件。 12 个字节的指令和 4 个字节的 .data。但是你不知道数据在哪里,除非你做一些硬编码,这是一个坏主意。这是在链接描述文件中找到 bss_start 和 bss_end 等内容的位置。
类似的东西
MEMORY
bob : ORIGIN = 0x00001000, LENGTH = 0x100
ted : ORIGIN = 0x00002000, LENGTH = 0x100
alice : ORIGIN = 0x00003000, LENGTH = 0x100
SECTIONS
.text : *(.text*) > bob
.data :
__data_start__ = .;
*(.data*)
> ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : *(.text*) > alice AT > bob
.text
.globl _start
_start:
mov r0,r1
mov r1,r2
b .
hello:
.word __data_start__
.word __data_end__
.word __data_size__
.data
some_data: .word 0x12345678
这给了我们。
Disassembly of section .text:
00001000 <_start>:
1000: e1a00001 mov r0, r1
1004: e1a01002 mov r1, r2
1008: eafffffe b 1008 <_start+0x8>
0000100c <hello>:
100c: 00002000 andeq r2, r0, r0
1010: 00002004 andeq r2, r0, r4
1014: 00000004 andeq r0, r0, r4
Disassembly of section .data:
00002000 <__data_start__>:
2000: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
工具链/链接器在链接器脚本中创建并填充这些定义的名称,然后在解析这些外部时将它们填充到您的代码中。然后你的引导程序需要使用这些变量(还有更多我没有在这里包含的在哪里可以找到 .text 中的 .data 你从上面知道有 4 个字节并且它们需要降落在 0x2000 但在 0x1000 .text 区域找到这 4 个字节了吗?更多链接器脚本 foo。另外,请注意 gnu 链接器脚本对于定义这些变量的位置非常敏感。在波浪括号之前或之后可能会产生不同的结果。
这就是为什么我提到你似乎在使用 ram。如果这是一个基于 rom 的目标,并且您想要 .data 并将 .bss 归零,那么您几乎必须将 .data 以及 .bss 的大小和位置放在 flash/rom 区域中,并且引导程序必须复制并归零。或者,您可以选择不使用 .data 或 .bss
unsigned int x=5;
unsigned int y;
改为
unsigned int x;
unsigned int y;
...
x=5;
y=0;
是的,它在二进制大小方面效率不高,但是链接器脚本在很大程度上依赖于工具链,例如,随着时间的推移,链接器脚本语言/规则会随着时间的推移而发生变化,而在 gnu ld 的先前主要版本上工作的东西不会'不一定在当前或下一个工作,因此多年来我不得不重新设计我的最小链接器脚本。
如此处所示,您可以使用命令行工具来试验设置和位置,并查看工具链产生了什么。
底线听起来您在 .data 中添加了一些信息,但随后声明您想要 NOLOAD 它,基本上意味着 .data 不存在/使用您的变量未正确初始化,所以为什么要更改代码以导致所有这只是为了让它不管用吗?要么有 .data 并正确使用它,有正确的引导程序和链接器脚本对,或者如果它是 ram 只是将它全部打包到同一个 ram 空间中,或者不使用您正在使用的“二进制”格式,使用elf 或 ihex 或 srec 或其他。
另一个取决于您的系统的技巧是为 ram 构建二进制文件,将所有内容打包,然后让另一个程序环绕该二进制文件从 rom 运行并复制到 ram 并跳转。以上面的 16 字节程序为例,编写另一个包含来自该构建的 16 字节的程序,并将它们复制到 0x1000,然后分支到 0x1000。根据系统和闪存/ROM 技术和接口,您可能希望这样做,我日常工作中的系统使用 spi 闪存引导,已知它存在读取干扰问题并且是...spi .. . 所以最快、最干净、最可靠的解决方案,就是在做其他任何事情之前先做副本跳转。作为免费的副作用,使链接器脚本更容易。
【讨论】:
当我将(NOLOAD)添加到数据部分时,问题似乎消失了。有什么要确定的吗?NOLOAD
暗示数据部分不是二进制文件的一部分。您必须做的(如果您没有只有 BSS 或零初始化内存)将初始数据值放在主映像中,并让引导代码将其复制到最终位置。 (LMA 加载内存地址和 VMA 虚拟内存地址,这是所有汇编程序应该引用的实际最终绝对地址),以上是关于ld链接时巨大的二进制大小的主要内容,如果未能解决你的问题,请参考以下文章
带有 -L/usr/local/lib 的巨大可执行文件大小