STM32位置无关二进制文件

5

我正在使用以下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的位置无关应用程序?重新定位全局偏移表吗?如果是,应将其放置在内存中的何处?


编辑

根据下面的问题,我的目标是创建具有引导加载程序和2个固件插槽的应用程序。我需要将固件插槽放在FLASH中,以便我的设备可以在运行时进行更新。

为什么需要使用PIC标志编译固件?每个插槽都有其在内存中的空间。默认情况下,它被编译为第一个插槽地址。从第二个插槽运行这样的二进制文件是不可能的,因为数据访问等(老实说,这是我第一次使用GOT和PIC编译)是基于程序计数器寄存器值(程序存储器空间中的当前位置)。固件更新使用下一个可用插槽,该插槽当前未使用。因为我不知道当前正在使用哪个插槽,所以我需要构建二进制文件,使其在所有插槽(在这种情况下为2个)上都能正常工作。这就是为什么在阅读了许多网站、帖子等之后,我认为我需要这个。

从引导加载程序切换到主应用程序的流程如下:

  1. 检查应使用哪个固件插槽
  2. 禁用IRQs。
  3. 将向量表复制到RAM中。该部分RAM对于两个插槽都相同。在复制过程中,我正在更改每个地址的偏移量,以便它们与特定的固件插槽兼容。默认情况下,地址没有偏移量,在编译后阶段被删除。
  4. 根据RAM中向量表中的第一个字设置堆栈指针。在将向量表复制到RAM时,该地址不会更改。
  5. 设置SCB->VTOR。
  6. 执行数据同步障碍DSB()。
  7. 跳转到已复制到RAM的向量表中的重置处理程序。

在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.首先要在RAM内存空间中为GOT保留空间,并请求引导程序/启动程序将GOT复制到该位置并添加插槽偏移量(如果需要)。我认为可以通过更改链接器代码来实现,使其将GOT放置在RAM的VMA中。因此,我假设,在应用程序的其余部分中,GOT地址将更改为放置在RAM中的地址,我是正确的吗?

为什么这个二进制文件在设备上不起作用?这是因为我应该添加编译标志-mpic-register=r10并将此寄存器设置为将GOT复制到RAM后的地址,并在从插槽执行主应用程序之前进行设置吗?哦!等等,这在固件插槽0上运行,但我不知道为什么。我还没有检查第二个插槽,很快会更新。


以上帖子已更新。我需要展示给你什么反汇编?一份转储文件?一个 ELF 的部分? - VIPPER
是的,不知道你尝试做什么是否有效。代码/反汇编是你如何修改got的?你计划让你的引导加载程序根据你正在访问的银行进行修改吗?如果你认为有编译器或链接器的魔法可以使其工作,那就错了,该工具是为你的链接地址构建的,如果你想要修改它,至少对于GNU来说,你需要修改got。但由于这将进入闪存,所以你必须在提交到闪存之前进行修改。 - old_timer
银行1不需要修改,但是银行2需要至少修改.text。假设SRAM在两个银行的程序中具有相同的地址空间,如果不是,则必须修改所有的GOT条目... - old_timer
如果您使用PIC工具进行两次构建,针对两个地址空间进行设置,然后比较GOT并查看在加载时需要更改什么,将需要某种方案让引导程序知道GOT的位置。我会选择构建两个使用一个的版本,但如果这是一个教育练习,可以为不同的空间构建并比较每个工具生成的内容,以了解该表格的工作原理以及如何修改它... - old_timer
这是我的理解。尝试使用不同的链接器脚本来构建不同的地址空间,并查看它如何影响GOT,或者反汇编引用它的项周围的代码,看看需要更改什么。 - old_timer
显示剩余9条评论
1个回答

6

有许多方法可以做到这一点,但希望这些内容能为您提供一些启示,以便开始了解发生了什么。

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

至少对于我使用的工具和这个简单程序来说,机器代码不会改变(虽然在进行优化时可能会改变),但是GOT会反映出0x30000000地址,因为它被假定为位置无关的,所以你不需要关心代码的位置。

所有指令都是相对地址,且在构建时被假定为位置无关的,闪存相对较小(相对于分支和分支链接指令的范围而言),因此链接器不应该存在任何问题或魔法来生成相对分支,因此不需要程序计数器运算。但是如果你真的尝试了,我敢打赌你可能会发生这种情况,并且如果你一直尝试下去,你可能最终会发现这些文本或其他基于偏移量的指令:

Disassembly of section .got.plt:

0802006c <_GLOBAL_OFFSET_TABLE_>:
    ...

如果你的程序的备用位置也包括数据的备用位置,那么你需要修补全局偏移表。

我的引导程序不仅是最小的,而且还在试图找到一种暴力方法来获取GOT地址。毫无疑问,可以使用链接器脚本和/或高级代码方式来获取此信息。同样,您可以使用链接器脚本来强制/放置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的位置无关版本;它们计算出到got的偏移量,然后偏移到其中一个地址并用其寻址内存位置。删除pic命令行选项并查看其变化。
因此,相同的机器代码依赖于got来获取实际地址。
如果我们有0x20000004,则y所在的位置就是那里,如果表格具有0x30000004,则y所在的位置就是那里。
如上所述编码和构建。
08020060 <.got>:
 8020060:   20000000
 8020064:   20000004
 8020068:   20000008

该表格存储在Flash中,因此您用于将该程序放置在Flash中的代码将需要在写入Flash时修补此表格。如果您玩链接器游戏将表格放置在RAM中但在到达Flash时像.data那样是字节在RAM中而被复制到某个地方但是在Flash中,这是引导程序和链接器脚本代码的组合。
无论哪种情况,我都不明白任何股票引导程序如何知道您想要将其定位在何处。.text代码被认为是可重定位的,并带有一些对齐假设,但是.data和其他内容被认为不在相同的内存空间中,并且不随.text线性移动(不能仅采取一些链接的.text地址和发现的.text地址并通过该量调整数据偏移量,因为两者被认为是分开的,在这种情况下(Cortex-M微控制器))
因此,除非您在此之前从未需要处理过此问题,否则我会(除了此答案以外)从上面的弹药开始,即简单地知道如何反汇编和阅读代码和/或上面的一些代码,并检查工具为您构建的内容,它们将东西放在哪里。我会假设您正在构建程序本身(如果您愿意,则为.text)作为一个大块,因此工具应该使用相对寻址来访问该部分内的内容。如果您没有编写引导程序,则这不是工具链问题,而是C库或其他(RTOS、HAL等)问题,我不会期望这里有位置无关的全局偏移表修补代码,因为引导程序如何知道您希望.data/.bss移动到哪里?思考一下这个问题。在这种情况下更糟糕的是,如果GOT在Flash中,则必须在执行之前进行修补,而不是在执行期间进行修补,因此其他程序必须执行此操作。这可能是为什么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)找到偏移量,但是你需要想办法确定(链接器魔术)并将全局偏移表的大小放置在已知的位置。或者支持完整的elf或其他格式的文件,并解析其中的内容。
编辑:
08020068 <.got>:
 8020068:   20000000
 802006c:   20000004
 8020070:   20000008

08020068 <.got>:
 8020068:   30000000
 802006c:   30000004
 8020070:   30000008

GOT不能移动,必须是相对于PC的,以便编译后的代码可以找到它所指向的内容。如果要将这些项目移动到其他位置,则必须修改指针指向的地址。例如,如果x、y、z分别位于0x20000000、0x20000004和0x20000008,则需要修改GOT本身,使其指向这些位置。由于GOT在.text中,因此它与代码是相对的(如上所示),而且这是一个MCU,如果在flash中有.text,则必须在放入flash之前修改GOT,而不是在运行时修改。因此,如果您希望.data/.bss位于链接之外的其他位置,则启动程序或任何其他程序将该程序放入flash时都需要进行修补。
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,则需要修改GOT本身以使其指向这些项目,就像第二个示例一样。GOT位于.text中,因此它与代码(如上述我使用的命令行选项构建代码)是PC相关的,由于这是一个MCU,并且如果您在Flash中有.text,则在将GOT放置在运行时之前必须对其进行修改,这对于此代码不适用。因此,如果您希望.data/.bss位于链接的其他位置,则您的引导加载程序或任何其他程序会将该程序放入Flash需要进行该修补。 使用您的编译器标志。
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的。

  1. 为什么需要对GOT进行修补,它包含了RAM中的地址,所以一切似乎都没问题。
- VIPPER
1
如果需要进行修补,我能否在链接器命令中使用“RAM AT >FLASH”这样的部分呢?这会导致GOT地址位于RAM中吗?如果是的话,我可以在启动时将GOT复制到RAM中,然后动态更改地址...有意义吗? - VIPPER
使用GOT如何影响使用FreeRTOS,因为FreeRTOS部分是用汇编语言编写的,所以编译器无法控制使用寄存器? - VIPPER
我猜想你希望应用程序在两个位置之一,因此你可能希望.data和.bss在两个位置之一。代码是按照pic构建的,使用pc相对寻址来解决(分支等),因此不需要进行任何修改,将代码放置在对齐的位置并跳转到它(记得设置地址的lsbit)。.data/.bss与.text无关,因此工具链不可能知道除链接位置外你可能想要.data/.bss的位置,所以这取决于你。 - old_timer
x、y、z是全局变量,所以它们位于.bss和GOT中。我甚至不需要使用它们,只是轻松地使用它们来演示编译器生成访问它们的代码的过程。 - old_timer
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接