也许这是你已经知道的东西,我不能从问题中判断...
unsigned int x;
void fun ( void )
{
x=5;
}
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
.bss : { *(.bss) } > two
}
arm-none-eabi-gcc -O2 -c -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld so.o -o so.elf
arm-none-eabi-objdump -D so.elf
Disassembly of section .text:
00000000 <fun>:
0: 4b01 ldr r3, [pc, #4] ; (8 <fun+0x8>)
2: 2205 movs r2, #5
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 00000100 andeq r0, r0, r0, lsl #2
Disassembly of section .bss:
00000100 <x>:
100: 00000000 andeq r0, r0, r0
IMO这是正常的/典型的.(意思是,优化,没有图片/馅饼之类的东西)(稍后将介绍GC部分).您可以看到,编译器在池中为链接器生成一个值来填充x的地址.
注意,我从来不使用这些,因为我做了很多裸金属嵌入的东西,但人GCC和寻找-fPIC-fPIC和派来看区别,这是一个有趣的阅读.在本例中,对于这段代码,四个组合产生了相同的结果.
Disassembly of section .text:
00000000 <fun>:
0: 4b03 ldr r3, [pc, #12] ; (10 <fun+0x10>)
2: 4a04 ldr r2, [pc, #16] ; (14 <fun+0x14>)
4: 447b add r3, pc
6: 589b ldr r3, [r3, r2]
8: 2205 movs r2, #5
a: 601a str r2, [r3, #0]
c: 4770 bx lr
e: bf00 nop
10: 00000008 andeq r0, r0, r8
14: 00000000 andeq r0, r0, r0
这是一种双重间接,或者说它增加了一种间接性.现在请注意问题所在
4: 447b add r3, pc
池中的值不是GET的地址,而是GOT的相对偏移量.编译器将 for each 函数生成这个函数,这是使用GET的整个过程,首先,你不想在每个函数的池中使用固定地址来寻址数据.
从理论上讲,位置独立也会使二进制代码变得位置独立,是的,就像所有的数据访问一样,这会使代码变得更大.因此,只有在绝对需要的情况下才使用PIC/PIE,特别是对于资源有限的MCU.您保存的每一个字节都是成功的.时钟周期的惩罚通常是可衡量的.
如果您使用的是位置独立性,您可以使用它做两件事,或者同时做两件事:移动代码、移动数据或同 timeshift 动两者.如果您移动代码,那么就像它是如何为这个目标构建的一样,您必须保持GET相对于代码.如果PIC指的是共享库,那么这意味着基于RAM的系统,即将程序加载到RAM中的操作系统.我们不是在一个基于RAM的系统上,除非您为RAM和复制和 skip 构建.如上所述(链接器不会更改/修复这一点),代码到GET地址的关系必须是固定的(对于此编译器、版本、目标、示例等)(如果它发生在一个示例中,那么它可能发生在您身上).但是,如果您想要移动数据,那么您必须更新GET,这意味着它必须在RAM中.所以GET需要在RAM中,但假设二进制文件在闪存中,所以如果你想从不同的位置(闪存或RAM)运行二进制文件,那么你必须将GET移动到一个相对距离之外.然后,如果您想要移动数据(无论是否进行二进制移动),则必须转到GET本身,并将地址的相对更改添加到每个条目.是的,在这两种情况下,你都需要知道GET在哪里,有多大.
如果你没有移动任何东西,那么GET就准备好了……从构建的Angular 来看.如果您在MCU中为ram链接它,那么它将不会被填充,除非您在 bootstrap 中这样做,就像您对.data或.bss所做的那样,这意味着您必须将变量添加到链接器脚本(对于此工具链)或一些其他您可以做的技巧.
它基本上是这个的一些变体:
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x200, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
__LAB0__ = .;
.bss : {
__LAB1__ = .;
*(.bss)
__LAB2__ = .;
} > two AT > one
__LAB3__ = .;
.got :
{
__LAB4__ = .;
*(.got)
__LAB5__ = .;
} > two AT > one
}
.align
.word __LAB1__
.word __LAB2__
.word __LAB3__
.word __LAB4__
.word __LAB5__
我将变量放在内部和外部,因为虽然有人会认为它们应该总是相同的,但GNU链接器并非如此.您还需要一些对齐,并调整您的复制循环(在ASM中,不要 bootstrap C中的C)(永远不要使用Memset或Memcpy,只需正确使用它,使用ASM bootstrap C),这样您就可以一次对齐两个或四个寄存器,因此在32位或64位边界上对齐,然后复制32或64的某个倍数.(如果下一个地址不等于结束,则可以使用准确的地址,然后中断循环)
无论如何,同样的方式,你已经隔离了大小和开始的数据,以及如何处理.data,你只需要模仿.data链接器脚本,并复制代码为.got,如果你想要它在ram中.
如果您因为要try 链接的其他已构建内容而以.get结束,则将.get放入您的链接器脚本中的Flash中.(也得到了.plt).当然,在您try 运行它之前,可以使用Readself和objump来确认它们应该在哪里,以及地址是否正确等等.
所以你的问题有一些令人困惑的漏洞.
-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -fno-common -ffunction-sections -fdata-sections -Wl,--gc-sections -specs=nano.specs -ffreestanding -Wall -O0 -ggdb
这看起来像是借来的.段1是为了使您可以在链接器中执行-gc-sections,但随后您指定了单独的链接器命令行
-T link.ld -lc -lgcc
-lc非常非常可怕,让我不寒而栗.但是好吧,你可以问更多的问题,所以我猜…-lgcc不会像从链接器那样工作,你需要从GCC做这件事,或者添加一个路径,只是GNU是如何工作的.
-O0不与-GC-SECTIONS匹配,无论您想要将其变小还是想要使其更大,请 Select 一个.或者是某些行业出于安全原因故意避免优化的-O0.
为了实际删除内容,您可以组合使用链接中的编译和-gc-sections中的-sections选项.
arm-none-eabi-gcc -O2 -c -ffunction-sections -fdata-sections -mcpu=cortex-m4 so.c -o so.o
arm-none-eabi-ld -Tso.ld -gc-sections -print-gc-sections so.o -o so.elf
arm-none-eabi-objdump -D so.elf
So.self:文件格式elf32-littlearm
你想要GC-Print在那里,这样你就可以看到什么是被删除的,人们可能会认为,嘿,这做得很好,然后可能在砖块或对正在发生的事情感到困惑之后,发现有一个错误.
ENTRY(fun)
MEMORY {
one : ORIGIN = 0x000, LENGTH = 256
two : ORIGIN = 0x100, LENGTH = 256
}
SECTIONS {
.text : { *(.text) } > one
.bss : { *(.bss) } > two
}
链接器遵循所有代码路径和数据路径,如果它没有命中,则它会移除一些东西,因此您必须非常小心地处理您想要保留但没有通过名称引用的项.
在任何情况下,都是固定的.
Disassembly of section .text:
00000000 <fun>:
0: 4b01 ldr r3, [pc, #4] ; (8 <fun+0x8>)
2: 2205 movs r2, #5
4: 601a str r2, [r3, #0]
6: 4770 bx lr
8: 00000100 andeq r0, r0, r0, lsl #2
Disassembly of section .bss:
00000100 <x>:
100: 00000000 andeq r0, r0, r0
它删除了我们未使用的数据和函数,节省了程序占用的大量空间.并且经过了优化,节省了更多的空间,运行速度更快.
您没有提供足够的信息来完整回答,但正如我们在 comments 中所述,有一些提示.您的代码可能没有创建.get,但您链接的一些代码可能已经创建了.在这种情况下,让链接器将.get项放入闪存中,您就可以使用它们了.如果您可以在没有图片/饼的情况下进行构建,那么就这样做吧.重新判断您的所有命令行选项-例如,fno-Common似乎是borrow 的和可怕的.没有必要用调试器的东西来inflating 代码,除非你需要,但你仍然想要为发布和调试而构建,因为发布和调试是(可以)不同的二进制文件,可能会有不同的结果,特别是裸机.
根据我们从您的问题中得出的结论.如果需要初始化.get,则需要将其包装在链接器中,就像.Data一样,并执行复制循环,就像.Data一样.使用.Data作为参照,剪切、粘贴并更改名称.如果您必须更改.get运行时.如果没有,那么在闪光灯中放入.get,它就完成了,没有init.如果有可能重新构建创建.get的任何东西,那么这就更好了(如果您不需要位置独立),更小、更快、更容易处理.只有当您计划使用位置独立性并且已经添加了运行时代码(不是由工具生成的,这是您的责任)来完成使用它的工作时,才指定位置独立性.
真正的GET不会全为零,对于这个平台来说也不是.正如您可能已经了解的那样,它充满了地址,并且需要它才能工作.除非,这个库是这样构建的,所以你必须以某种方式神奇地找出所有东西的位置,然后自己填写GET和PLT,这让我头疼,你不是在做动态库,对吗?