我正在使用以下CFLAGS为Cortex-M4(STM32F4)编译我的FreeRTOS应用程序:
-fpic -msingle-pic-base -mpic-data-is-text-relative -mpic-register=r10
并为链接器设置-fpic
标志。
向量表已正确复制到RAM,并且当前特定固件插槽设备已从更改的偏移值开始。
但是,从固件插槽启动主应用程序后,设备将挂起 HardFault ,并在 CFSR 寄存器中设置 IMPRECISERR 。 它在执行以下行时出现在 vPortSVCHandler 中:
ldmia r0!, {r4-r11, r14}
此操作后,链接寄存器值等于0000 0000
。
我是否应该为STM32正确创建与位置无关的应用程序?重新定位全球抵消表?如果是的话,记忆中的什么地方?
修改
根据下面的问题,我的目标是使用bootloader和2个固件插槽创建应用程序。我需要FLASH中的固件插槽,以便我的设备能够在运行时更新。 为什么我需要使用PIC标志编译固件?每个插槽都有内存空间。默认情况下,它是为第一个插槽地址编译的。从第二个插槽运行这样的二进制文件,为第一个插槽准备,是不可能的,因为数据访问等(说实话,这是我第一次使用GOT和PIC编译)是基于程序计数器寄存器值(当前位置在程序存储空间)。固件更新使用下一个当前未使用的空闲插槽。因为我不知道当前使用哪个,我需要构建二进制文件,它将在所有插槽中正常工作(在这种情况下为2)。这就是为什么在阅读了许多网站,线程等之后,我想我需要这个。
从引导加载程序切换到主应用程序期间的流程如下:
在old_timer之后编辑发布答案
所以,我尝试为两个插槽编译代码。以下是GOT反汇编的结果:
Disassembly of section .got:
080083ac <_got_address>:
80083ac: 0800beb9 stmdaeq r0, {r0, r3, r4, r5, r7, r9, sl, fp, ip, sp, pc}
80083b0: 0800bf4c stmdaeq r0, {r2, r3, r6, r8, r9, sl, fp, ip, sp, pc}
80083b4: 20000274 andcs r0, r0, r4, ror r2
80083b8: 2000022c andcs r0, r0, ip, lsr #4
80083bc: 20012fb4 ; <UNDEFINED> instruction: 0x20012fb4
80083c0: 080086c5 stmdaeq r0, {r0, r2, r6, r7, r9, sl, pc}
80083c4: 20000200 andcs r0, r0, r0, lsl #4
80083c8: 200132f0 strdcs r3, [r1], -r0
80083cc: 20013330 andcs r3, r1, r0, lsr r3
80083d0: 080088e1 stmdaeq r0, {r0, r5, r6, r7, fp, pc}
80083d4: 20013334 andcs r3, r1, r4, lsr r3
80083d8: 20013338 andcs r3, r1, r8, lsr r3
80083dc: 20000230 andcs r0, r0, r0, lsr r2
80083e0: 200132d0 ldrdcs r3, [r1], -r0
80083e4: 20012fb8 ; <UNDEFINED> instruction: 0x20012fb8
80083e8: 20000234 andcs r0, r0, r4, lsr r2
80083ec: 200131cc andcs r3, r1, ip, asr #3
80083f0: 0800bed1 stmdaeq r0, {r0, r4, r6, r7, r9, sl, fp, ip, sp, pc}
80083f4: 080089a1 stmdaeq r0, {r0, r5, r7, r8, fp, pc}
80083f8: 0800bf7c stmdaeq r0, {r2, r3, r4, r5, r6, r8, r9, sl, fp, ip, sp, pc}
80083fc: 080086a5 stmdaeq r0, {r0, r2, r5, r7, r9, sl, pc}
8008400: 080087f1 stmdaeq r0, {r0, r4, r5, r6, r7, r8, r9, sl, pc}
8008404: 200132cc andcs r3, r1, ip, asr #5
更改固件插槽后,@ old_timer说,这已更改:
Disassembly of section .got:
081043ac <_got_address>:
81043ac: 08107eb9 ldmdaeq r0, {r0, r3, r4, r5, r7, r9, sl, fp, ip, sp, lr}
81043b0: 08107f4c ldmdaeq r0, {r2, r3, r6, r8, r9, sl, fp, ip, sp, lr}
81043b4: 20000274 andcs r0, r0, r4, ror r2
81043b8: 2000022c andcs r0, r0, ip, lsr #4
81043bc: 20012fb4 ; <UNDEFINED> instruction: 0x20012fb4
81043c0: 081046c5 ldmdaeq r0, {r0, r2, r6, r7, r9, sl, lr}
81043c4: 20000200 andcs r0, r0, r0, lsl #4
81043c8: 200132f0 strdcs r3, [r1], -r0
81043cc: 20013330 andcs r3, r1, r0, lsr r3
81043d0: 081048e1 ldmdaeq r0, {r0, r5, r6, r7, fp, lr}
81043d4: 20013334 andcs r3, r1, r4, lsr r3
81043d8: 20013338 andcs r3, r1, r8, lsr r3
81043dc: 20000230 andcs r0, r0, r0, lsr r2
81043e0: 200132d0 ldrdcs r3, [r1], -r0
81043e4: 20012fb8 ; <UNDEFINED> instruction: 0x20012fb8
81043e8: 20000234 andcs r0, r0, r4, lsr r2
81043ec: 200131cc andcs r3, r1, ip, asr #3
81043f0: 08107ed1 ldmdaeq r0, {r0, r4, r6, r7, r9, sl, fp, ip, sp, lr}
81043f4: 081049a1 ldmdaeq r0, {r0, r5, r7, r8, fp, lr}
81043f8: 08107f7c ldmdaeq r0, {r2, r3, r4, r5, r6, r8, r9, sl, fp, ip, sp, lr}
81043fc: 081046a5 ldmdaeq r0, {r0, r2, r5, r7, r9, sl, lr}
8104400: 081047f1 ldmdaeq r0, {r0, r4, r5, r6, r7, r8, r9, sl, lr}
8104404: 200132cc andcs r3, r1, ip, asr #5
1。首先要做的是在GOT的RAM内存空间中保留并请求bootloader / startup将GOT复制到该位置并在必要时添加插槽偏移。我想我可以通过更改链接器代码来实现这一点,这样就可以放置GOT在RAM的VMA中。所以我假设,在应用程序的其余部分,GOT地址将更改为放在RAM中的地址,我是对的吗?
为什么这个二进制文件无法在设备中运行?这是因为我应该添加编译标志-mpic-register=r10
并在将GOT复制到RAM之后以及从插槽执行主应用程序之前将此寄存器设置为GOT地址?
哦!等等,这是在固件插槽0上工作,但我不知道为什么。我没有检查第二个插槽,很快就会更新。
答案 0 :(得分:2)
有很多方法可以做到这一点,但希望这会给你一些弹药,开始看看发生了什么。
boot.s
.thumb
.globl _start
_start:
reset:
mov r0,pc
ldr r1,=0xFFFF0000
and r0,r1
ldr r1,gotbase
add r0,r1
bl centry
b .
.align
gotbase:
.word _GLOBAL_OFFSET_TABLE_-(_start)
.word _start
.word _GLOBAL_OFFSET_TABLE_
.word _GLOBAL_OFFSET_TABLE_
so.c
extern unsigned int fun ( unsigned int );
unsigned int x;
unsigned int y;
unsigned int z;
void centry ( void )
{
x=5;
y=6;
z=fun(77);
}
fun.c
unsigned int fun ( unsigned int x )
{
return(x+3);
}
flash.ld
MEMORY
{
rom : ORIGIN = 0x08020000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
构建
arm-none-eabi-as --warn boot.s -o boot.o
arm-none-eabi-gcc -Wall -O2 -mthumb -fpic -mthumb -c so.c -o so.o
arm-none-eabi-gcc -Wall -O2 -mthumb -fpic -mthumb -c fun.c -o fun.o
arm-none-eabi-ld -o so.elf -T flash.ld boot.o so.o fun.o
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy --srec-forceS3 so.elf -O srec so.srec
arm-none-eabi-objcopy so.elf so.bin -O binary
拆卸
Disassembly of section .text:
08020000 <_start>:
8020000: 4678 mov r0, pc
8020002: 4907 ldr r1, [pc, #28] ; (8020020 <gotbase+0x10>)
8020004: 4008 ands r0, r1
8020006: 4902 ldr r1, [pc, #8] ; (8020010 <gotbase>)
8020008: 1840 adds r0, r0, r1
802000a: f000 f80b bl 8020024 <centry>
802000e: e7fe b.n 802000e <_start+0xe>
08020010 <gotbase>:
8020010: 00000060
8020014: 08020000
8020018: 00000048
802001c: 00000044
8020020: ffff0000
08020024 <centry>:
8020024: 2205 movs r2, #5
8020026: b510 push {r4, lr}
8020028: 4c08 ldr r4, [pc, #32] ; (802004c <centry+0x28>)
802002a: 4b09 ldr r3, [pc, #36] ; (8020050 <centry+0x2c>)
802002c: 447c add r4, pc
802002e: 58e3 ldr r3, [r4, r3]
8020030: 601a str r2, [r3, #0]
8020032: 4b08 ldr r3, [pc, #32] ; (8020054 <centry+0x30>)
8020034: 58e3 ldr r3, [r4, r3]
8020036: 3201 adds r2, #1
8020038: 204d movs r0, #77 ; 0x4d
802003a: 601a str r2, [r3, #0]
802003c: f000 f80e bl 802005c <fun>
8020040: 4b05 ldr r3, [pc, #20] ; (8020058 <centry+0x34>)
8020042: 58e3 ldr r3, [r4, r3]
8020044: 6018 str r0, [r3, #0]
8020046: bc10 pop {r4}
8020048: bc01 pop {r0}
802004a: 4700 bx r0
802004c: 00000030
8020050: 00000000
8020054: 00000008
8020058: 00000004
0802005c <fun>:
802005c: 3003 adds r0, #3
802005e: 4770 bx lr
Disassembly of section .got:
08020060 <.got>:
8020060: 20000000
8020064: 20000004
8020068: 20000008
Disassembly of section .got.plt:
0802006c <_GLOBAL_OFFSET_TABLE_>:
...
Disassembly of section .bss:
20000000 <x>:
20000000: 00000000
20000004 <z>:
20000004: 00000000
20000008 <y>:
20000008: 00000000
这是故意的,很多,很少。关于位置独立性的第一个也是最有趣的项目是:
拆卸部分.got:
08020060 <.got>:
8020060: 20000000
8020064: 20000004
8020068: 20000008
这显然是我们在该计划中拥有的三个全球数据项。添加更多项目,您将看到此更改。
如果更改链接描述文件中的地址
rom : ORIGIN = 0x08010000, LENGTH = 0x1000
ram : ORIGIN = 0x30000000, LENGTH = 0x1000
至少对于这个使用我使用的工具的简单程序,机器代码没有改变(技术上可以通过优化,但假设是位置无关的,所以你不应该关心代码位置有原因)但是得到了确实反映了0x30000000地址。
由于所有拇指(可能无关紧要)和所有内置位置无关且闪烁相对较小(与分支和分支链接指令的范围相比),链接器不应该有任何问题或魔术制作相对分支,因此没有程序计数器数学,我希望,虽然如果你真的试过我打赌你可以实现它,我会假设,但你会发现,如果你推动它,如果是这样的话。如果你推动它可能你最终会得到.text或其他基础的偏移:
Disassembly of section .got.plt:
0802006c <_GLOBAL_OFFSET_TABLE_>:
...
因此,如果您的程序的备用位置还包含数据的备用位置,那么您需要修补全局偏移表。
我的引导程序不仅仅是最小的,只是用一种蛮力的方式来搞定GOT的地址。毫无疑问,链接器脚本和/或ghee whiz代码可以获取此信息。同样,您可以使用链接描述文件强制/放置GOT。
8020024: 2205 movs r2, #5
8020028: 4c08 ldr r4, [pc, #32] ; (802004c <centry+0x28>)
802002c: 447c add r4, pc
8020032: 4b08 ldr r3, [pc, #32] ; (8020054 <centry+0x30>)
8020034: 58e3 ldr r3, [r4, r3]
8020036: 3201 adds r2, #1
802003a: 601a str r2, [r3, #0]
这些项目正在做位置无关的y = 6版本;他们计算得到的偏移量,然后计算偏移量,并使用它来解决内存位置,删除pic命令行选项,看看它是如何变化的。
所以相同的机器代码取决于物品实际地址的得分。
如果我们有0x20000004那么如果表格有0x30000004则y就在那里,那就是y所在的地方。
编码并在上面构建
08020060 <.got>:
8020060: 20000000
8020064: 20000004
8020068: 20000008
该表存在于flash中,因此用于将此程序放入flash中的代码需要在写入闪存时对此表进行修补。如果你玩链接器游戏把表放在ram中但是在闪存中就像.data那样是ram中的字节,它们在某处,但是在flash中然后由bootstrap,bootstrap和链接器脚本代码的组合复制。 / p>
在任何一种情况下,我都不知道任何股票引导程序如何能够知道你想要的位置。假设.text代码可以通过一些对齐假设进行重定位,但.data和其他假设不在同一个内存空间中,并且不能与.text线性移动(只能使用一些链接的.text地址和发现的.text地址)并根据该数量调整数据偏移量,因为假设两者是分开的,在这种情况下(cortex-m微控制器))
所以我会(除了这里的答案从来没有需要弄乱这个)从上面的弹药开始它只是知道如何反汇编和阅读上面的代码和/或一些代码,并检查是什么工具正在为你建造,他们在那里放东西。我假设您正在构建程序本身(.text,如果您愿意)作为一个大blob,因此工具应该构建所有具有该部分内的访问的相对寻址。如果你没有编写引导程序,那么它不是一个工具链,它是一个C库或其他(RTOS,HAL等)的东西,我不希望这有位置无关的全局偏移表补丁代码,因为引导程序如何知道你想要.data / .bss移动的地方?考虑一下。更糟糕的是,在这种情况下,如果GOT处于闪存状态,则必须在执行之前修补它,而不是在其他程序必须执行此操作时。这可能是elf文件格式包含GOT的位置/大小的原因,以便加载程序,操作系统或其他工具在运行之前加载该程序。可以找到并修补它。
如果您希望将程序加载到两个不同的Flash空间中的一个中,那么您需要使用任何加载来解决此问题。再次非常粗暴的蛮力方式开始:
.thumb
.globl _start
_start:
reset:
b skip
.align
.word _GLOBAL_OFFSET_TABLE_-(_start)
skip:
08020000 <_start>:
8020000: e002 b.n 8020008 <skip>
8020002: 46c0 nop ; (mov r8, r8)
8020004: 00000068 andeq r0, r0, r8, rrx
08020008 <skip>:
您的加载程序可以在您离开的位置找到偏移量(偏移量为0x4到二进制文件中),但当然您需要弄清楚(链接器魔术)并将全局偏移表的大小放在已知位置。那个或支持完整的精灵或其他格式文件,并通过这些文件解析。
编辑:
08020068 <.got>:
8020068: 20000000
802006c: 20000004
8020070: 20000008
08020068 <.got>:
8020068: 30000000
802006c: 30000004
8020070: 30000008
GOT没有/不能移动它必须是pc相对的,所以在.text中编译的代码可以找到它。它指出的是,如果/当你移动到你想要那些项目的地方时,必须修改整个想法。因此,如上所示,如果链接使得x,y,z处于0x20000000,0x20000004,0x20000008。但是如果你希望在0x30000000处运行x,y,z,基本上重新定位,那么你需要修改GOT本身以指向那些项,就像上面的第二个例子一样。因为get是在.text中,所以它是相对于代码的pc(如上所示,代码是如何使用我使用的命令行选项构造的),这是一个mcu,如果你在flash中有.text,那么得到了必须在放入闪存之前进行修改,该闪存在运行时不适用于此代码。所以你的引导加载程序或者这个程序在flash中的任何地方都需要做那个补丁,如果你想.data / .bss在某个地方而不是链接。
08020068 <.got>:
8020068: 20000000
802006c: 20000004
8020070: 20000008
08020068 <.got>:
8020068: 30000000
802006c: 30000004
8020070: 30000008
GOT没有/不能移动它必须是pc相对的,所以在.text中编译的代码可以找到它。它指出的是,如果/当你移动到你想要那些项目的地方时,必须修改整个想法。因此,如上所示,如果链接使得x,y,z处于0x20000000,0x20000004,0x20000008。但是如果你希望在0x30000000处运行x,y,z,基本上重新定位,那么你需要修改GOT本身以指向那些项,就像上面的第二个例子一样。因为get是在.text中,所以它是相对于代码的pc(如上所示,代码是如何使用我使用的命令行选项构造的),这是一个mcu,如果你在flash中有.text,那么得到了必须在放入闪存之前进行修改,该闪存在运行时不适用于此代码。所以你的引导加载程序或者这个程序在flash中的任何地方都需要做那个补丁,如果你想.data / .bss在某个地方而不是链接。
使用编译器标志
Disassembly of section .text:
08020000 <_start>:
8020000: e002 b.n 8020008 <skip>
8020002: 46c0 nop ; (mov r8, r8)
8020004: 00000064 andeq r0, r0, r4, rrx
08020008 <skip>:
8020008: 4678 mov r0, pc
802000a: 4907 ldr r1, [pc, #28] ; (8020028 <gotbase+0x10>)
802000c: 4008 ands r0, r1
802000e: 4902 ldr r1, [pc, #8] ; (8020018 <gotbase>)
8020010: 1840 adds r0, r0, r1
8020012: f000 f80b bl 802002c <centry>
8020016: e7fe b.n 8020016 <skip+0xe>
08020018 <gotbase>:
8020018: 00000064 andeq r0, r0, r4, rrx
802001c: 08020000 stmdaeq r2, {} ; <UNPREDICTABLE>
8020020: 00000044 andeq r0, r0, r4, asr #32
8020024: 00000040 andeq r0, r0, r0, asr #32
8020028: ffff0000 ; <UNDEFINED> instruction: 0xffff0000
0802002c <centry>:
802002c: b510 push {r4, lr}
802002e: 4654 mov r4, r10
8020030: 2205 movs r2, #5
8020032: 4b08 ldr r3, [pc, #32] ; (8020054 <centry+0x28>)
8020034: 58e3 ldr r3, [r4, r3]
8020036: 601a str r2, [r3, #0]
8020038: 4b07 ldr r3, [pc, #28] ; (8020058 <centry+0x2c>)
802003a: 58e3 ldr r3, [r4, r3]
802003c: 3201 adds r2, #1
802003e: 204d movs r0, #77 ; 0x4d
8020040: 601a str r2, [r3, #0]
8020042: f000 f80d bl 8020060 <fun>
8020046: 4b05 ldr r3, [pc, #20] ; (802005c <centry+0x30>)
8020048: 58e3 ldr r3, [r4, r3]
802004a: 6018 str r0, [r3, #0]
802004c: bc10 pop {r4}
802004e: bc01 pop {r0}
8020050: 4700 bx r0
8020052: 46c0 nop ; (mov r8, r8)
8020054: 00000000 andeq r0, r0, r0
8020058: 00000008 andeq r0, r0, r8
802005c: 00000004 andeq r0, r0, r4
08020060 <fun>:
8020060: 3003 adds r0, #3
8020062: 4770 bx lr
Disassembly of section .got:
08020064 <.got>:
8020064: 20000000 andcs r0, r0, r0
8020068: 20000004 andcs r0, r0, r4
802006c: 20000008 andcs r0, r0, r8
将其更改为使用此
802002e: 4654 mov r4, r10
但是请注意,这些工具没有设置r10,你必须添加代码到r10指向GOT,这样就可以在链接地址工作了。
因此,GOT本身也不会移动,这就是整点,内容更改为指向.data / .bss的重定位位置。请参阅上面的最后一个示例,GOT位于同一位置,但x,y,z的地址已更改以反映其新位置。反汇编显示了基于链接地址的地址,但是如果链接到不同的地址,并且仅比较机器代码,您将看到它没有变化,所使用的指令是pc相对的。