STM32 位置无关二进制文件

VIP*_*PER 4 linker compilation stm32 cortex-m stm32f4

我正在使用以下 CFLAGS 为 Cortex-M4 (STM32F4) 编译我的 FreeRTOS 应用程序:

-fpic -msingle-pic-base -mpic-data-is-text-relative -mpic-register=r10
Run Code Online (Sandbox Code Playgroud)

并-fpic为链接器设置了标志。

矢量表已正确复制到 RAM,并更改了当前正在启动的特定固件插槽设备的偏移值。

然而,从开始时隙固件主应用程序后,在设备挂起HardFault与IMPRECISERR集CFSR寄存器。执行以下行时,它发生在vPortSVCHandler 中:

ldmia r0!, {r4-r11, r14}
Run Code Online (Sandbox Code Playgroud)

此操作后,链接寄存器值等于0000 0000。

我还应该做些什么来为 STM32 正确创建位置无关的应用程序?重新定位全局偏移表?如果是,在内存中的哪个位置?


编辑

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

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

  1. 检查应该使用哪个固件插槽
  2. 禁用 IRQ。
  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
Run Code Online (Sandbox Code Playgroud)

正如@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
Run Code Online (Sandbox Code Playgroud)

1. 首先要做的是为 GOT 保留 RAM 存储空间,并请求引导加载程序/启动程序将 GOT 复制到该位置,并在必要时添加插槽偏移量。我想我可以通过更改链接器代码来做到这一点,以便将 GOT 放在 RAM 的 VMA 中。所以我假设,在其余的应用程序中,GOT 地址将更改为放置在 RAM 中的地址,对吗?

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

old*_*mer 5

可能有无数种方法可以做到这一点,但希望这能给你一些弹药来开始看看发生了什么。

靴子

.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_
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

乐趣.c

unsigned int fun ( unsigned int x )
{
    return(x+3);
}
Run Code Online (Sandbox Code Playgroud)

闪存文件

MEMORY
{
    rom : ORIGIN = 0x08020000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .rodata : { *(.rodata*) } > rom
    .bss : { *(.bss*) } > ram
}
Run Code Online (Sandbox Code Playgroud)

建造

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
Run Code Online (Sandbox Code Playgroud)

拆卸

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
Run Code Online (Sandbox Code Playgroud)

这是故意的,很多,很少。关于位置独立性的第一个也是最有趣的项目是:

.got 部分的反汇编:

08020060 <.got>:
 8020060:   20000000
 8020064:   20000004
 8020068:   20000008
Run Code Online (Sandbox Code Playgroud)

这显然是我们在程序中拥有的三个全局数据项。添加更多项目您将看到此更改。

如果您更改链接描述文件中的地址

rom : ORIGIN = 0x08010000, LENGTH = 0x1000
ram : ORIGIN = 0x30000000, LENGTH = 0x1000
Run Code Online (Sandbox Code Playgroud)

至少对于使用我使用的工具的这个简单程序,机器代码不会改变(技术上可以通过优化,但假设是位置无关的,所以你不应该关心代码的位置)但得到了反映0x30000000 地址。

由于所有拇指(可能无关紧要)和所有构建位置独立并且闪烁相对较小(与分支和分支链接指令的范围相比),链接器不应该有任何问题或魔术制作相关分支,所以没有程序计数器数学,我会希望,虽然如果你真的真的尝试过我打赌你可以实现它,我会假设,但是如果你推动它,你会发现,如果是这样的话。并且很可能如果你推动它,你可能会在这里得到 .text 或其他基于偏移量:

Disassembly of section .got.plt:

0802006c <_GLOBAL_OFFSET_TABLE_>:
    ...
Run Code Online (Sandbox Code Playgroud)

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

我的引导程序不仅仅是最小的,正在用一种蛮力的方式来获取 GOT 的地址。毫无疑问,链接器脚本和/或 ghee 高手代码可以获取此信息。同样,您可能会使用链接描述文件来强制/放置 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]
Run Code Online (Sandbox Code Playgroud)

这些项目正在做 y = 6 的位置无关版本;他们计算得到的偏移量,然后计算到它的偏移量并使用它来寻址内存位置,删除 pic 命令行选项并查看它是如何变化的。

因此,相同的机器代码取决于项目的实际地址。

如果我们有 0x20000004 那么这就是 y 所在的位置,如果该表具有 0x30000004 那么这就是 y 所在的位置。

如上面编码和构建

08020060 <.got>:
 8020060:   20000000
 8020064:   20000004
 8020068:   20000008
Run Code Online (Sandbox Code Playgroud)

该表位于闪存中,因此您用来将该程序放置在闪存中的代码将需要在该表写入闪存时对其进行修补。如果您玩链接器游戏将表放在 ram 中,但在那里的闪存中,例如 .data 是 ram 中的字节,可以去某个地方,但在闪存中,然后由引导程序复制,引导程序和链接器脚本代码的组合。

在任何一种情况下,我都看不到任何股票引导程序怎么可能知道您希望它位于何处。假设 .text 代码在某些对齐假设下可重定位,但假设 .data 和其他代码不在同一内存空间中,并且不随 .text 线性移动(不能只获取一些链接的 .text 地址和发现的 .text 地址并按该数量调整数据偏移量,因为假设两者是分开的,并且在这种情况下是(cortex-m 微控制器))

所以我会(除了这里的答案从来不需要弄乱这个)从上面的弹药开始,只是知道如何反汇编和阅读上面的代码和/或一些代码,并检查工具正在构建什么对你来说,他们把东西放在哪里。我假设您正在将程序本身(如果您愿意,则为 .text)构建为一个大 blob,因此工具应该使用该部分中访问的相对寻址来构建所有这些。如果您没有编写引导程序,那么它不是工具链的东西,它是 C 库或其他(RTOS、HAL 等)的东西,我不希望它具有与位置无关的全局偏移表补丁代码,因为引导程序如何知道你想让 .data/.bss 移动到哪里?想一想。在这种情况下,如果 GOT 在闪存中,则更糟,那么它必须在执行之前进行修补,而不是在执行期间进行修补,因此其他一些程序必须这样做。这可能就是为什么 elf 文件格式包含 GOT 的位置/大小的原因,以便加载程序、操作系统或其他工具在运行该程序之前加载该程序。可以找到并修补它。

如果您希望您的程序被加载到两个不同的闪存空间之一,那么您需要通过加载的任何内容来解决这个问题。再次非常粗暴的蛮力开始方式:

.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>:
Run Code Online (Sandbox Code Playgroud)

你的加载器可以在你离开的地方找到偏移量(偏移 0x4 到二进制文件中),但当然你需要弄清楚(链接器魔术)并将全局偏移表的大小放在一个已知的地方。或支持完整的 elf 或其他格式文件并解析这些文件。

编辑:

08020068 <.got>:
 8020068:   20000000
 802006c:   20000004
 8020070:   20000008

08020068 <.got>:
 8020068:   30000000
 802006c:   30000004
 8020070:   30000008
Run Code Online (Sandbox Code Playgroud)

GOT 不能/不能移动它必须是相对于 pc 的,以便在 .text 中编译的代码可以找到它。它指出的是,如果/当您将这些项目移动到您想要的位置时,必须修改整个想法。所以如上所示,如果它被链接使得 x、y、z 位于 0x20000000、0x20000004、0x20000008。但是,如果您希望在 0x30000000 处使用 x,y,z 运行,基本上重新定位,那么您需要修改 GOT 本身以指向这些项目,就像上面的第二个示例一样。因为 got 在 .text 中,所以它是相对于代码的 pc(如上所示,代码是如何使用我使用的命令行选项构造的)并且这是一个 mcu,如果您在 flash 中有 .text,那么 got必须在放入闪存之前进行修改,该代码不在运行时。所以你的引导加载程序或这个程序在闪存中的任何地方都需要做那个补丁,

08020068 <.got>:
 8020068:   20000000
 802006c:   20000004
 8020070:   20000008

08020068 <.got>:
 8020068:   30000000
 802006c:   30000004
 8020070:   30000008
Run Code Online (Sandbox Code Playgroud)

GOT 不能/不能移动它必须是相对于 pc 的,以便在 .text 中编译的代码可以找到它。它指出的是,如果/当您将这些项目移动到您想要的位置时,必须修改整个想法。所以如上所示,如果它被链接使得 x、y、z 位于 0x20000000、0x20000004、0x20000008。但是,如果您希望在 0x30000000 处使用 x,y,z 运行,基本上重新定位,那么您需要修改 GOT 本身以指向这些项目,就像上面的第二个示例一样。因为 got 在 .text 中,所以它是相对于代码的 pc(如上所示,代码是如何使用我使用的命令行选项构造的)并且这是一个 mcu,如果您在 flash 中有 .text,那么 got必须在放入闪存之前进行修改,该代码不在运行时。所以你的引导加载程序或这个程序在闪存中的任何地方都需要做那个补丁,

使用编译器标志

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
Run Code Online (Sandbox Code Playgroud)

这将它更改为使用此

 802002e:   4654        mov r4, r10
Run Code Online (Sandbox Code Playgroud)

但是请注意,这些工具没有设置 r10,您必须添加代码以将 r10 指向 GOT,以便即使在链接地址上也能正常工作。

所以再次 GOT 本身不会移动,这就是重点,内容更改为指向 .data/.bss 的重定位位置。请参见上面的最后一个示例,GOT 位于同一位置,但 x、y、z 的地址已更改以反映其新位置。反汇编显示基于链接地址的地址,但是如果您链接不同的地址,并且只比较机器代码,您将看到它不会改变,所使用的指令是相对于 pc 的。