STM32位置独立二进制文件

时间:2018-06-02 08:52:43

标签: compilation linker stm32 cortex-m stm32f4

我正在使用以下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)。这就是为什么在阅读了许多网站,线程等之后,我想我需要这个。

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

  1. 检查应使用哪个固件插槽
  2. 禁用IRQ。
  3. 将矢量表复制到RAM。两个插槽的RAM部分相同。在复制过程中,我正在更改每个地址的偏移量,因此它们将与特定的固件插槽兼容。默认情况下,地址没有偏移量,在后编译阶段将其删除。
  4. 根据RAM中向量表中的第一个字设置堆栈指针。将向量表复制到RAM时,不会更改该地址。
  5. 设置SCB-> VTOR。
  6. 执行数据同步屏障DSB()。
  7. 从复制到RAM的向量表跳转到重置处理程序。
  8. 在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上工作,但我不知道为什么。我没有检查第二个插槽,很快就会更新。

1 个答案:

答案 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相对的。