从 Reset_Handler 开始没有 C 代码的程序

Starting program from Reset_Handler without C code

我希望能够 运行 并调试在 ARM Cortex-M4 微控制器上从纯汇编生成的二进制文件,而不必在 C 程序中使用内联汇编。

我有一个链接描述文件和一些实用程序 C 启动代码,它设置中断向量 table,实现 Reset_Handler 功能,将 .data 部分从闪存复制到 SRAM然后调用 main()。这个工作流工作正常,但有点笨拙,我宁愿直接编写汇编而不是在 C 程序中内联,无非是 main() 和汇编助记符。出于兴趣,我也想知道 - 也许有更好的方法来解决这个问题。 Reset_Handler 函数如下所示:

void Reset_Handler(void)
{
        //copy .data section to SRAM
        uint32_t size = (uint32_t)&_edata - (uint32_t)&_sdata;

        uint8_t *pDst = (uint8_t*)&_sdata; //sram
        uint8_t *pSrc = (uint8_t*)&_la_data; //flash

        for(uint32_t i =0 ; i < size ; i++)
        {
                *pDst++ = *pSrc++;
        }

        //Init. the .bss section to zero in SRAM
        size = (uint32_t)&_ebss - (uint32_t)&_sbss;
        pDst = (uint8_t*)&_sbss;
        for(uint32_t i =0 ; i < size ; i++)
        {
                *pDst++ = 0;
        }

        __libc_init_array();

        main();

}

编辑:我的工具链和链接脚本的详细信息如下。

我正在尝试跟随 this tutorial,但不是 运行 在 VM 中编写代码,目标是 运行 并直接在板上调试。

我的链接器代码:


ENTRY(Reset_Handler)

MEMORY
{
  FLASH(rx):ORIGIN =0x08000000,LENGTH =1024K
  SRAM(rwx):ORIGIN =0x20000000,LENGTH =128K
}


SECTIONS
{
  .text :
  {
    *(.isr_vector)
    *(.text)
    *(.rodata)
    . = ALIGN(4);
    _etext = .;
  }> FLASH
  
  _la_data = LOADADDR(.data);
  
  .data :
  {
    _sdata = .;
    *(.data)
    *(.data.*)
    . = ALIGN(4);
    _edata = .;
  }> SRAM AT> FLASH
  
  .bss :
  {
    _sbss = .;
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss.*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
    __bss_end__ = _ebss;
       . = ALIGN(4); 
    end = .;
    __end__ = .;
  }> SRAM

  
}

你的链接描述文件

ENTRY(Reset_Handler)
MEMORY
{
  FLASH(rx):ORIGIN =0x08000000,LENGTH =1024K
  SRAM(rwx):ORIGIN =0x20000000,LENGTH =128K
}
SECTIONS
{
  .text :
  {
    *(.isr_vector)
    *(.text)
    *(.rodata)
    . = ALIGN(4);
    _etext = .;
  }> FLASH
  _la_data = LOADADDR(.data);
  .data :
  {
    _sdata = .;
    *(.data)
    *(.data.*)
    . = ALIGN(4);
    _edata = .;
  }> SRAM AT> FLASH
  .bss :
  {
    _sbss = .;
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss.*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
    __bss_end__ = _ebss;
       . = ALIGN(4);
    end = .;
    __end__ = .;
  }> SRAM
}

由于您已经阅读了 arm 和 st 文档,您知道向量 table 以堆栈指针加载值开始,然后是重置向量,然后是其他向量,根据芯片的不同,可能有数百个。芯片供应商将应用程序闪存映射到 0x08000000,并使用某些可以镜像到 0x00000000 的启动选项,以便 arm 启动它。并且 ram 从 0x20000000 开始,并且根据芯片具有一定的大小。

.cpu cortex-m4

.word 0x20001000
.word Reset_Handler
.word loop
.word loop

.globl Reset_Handler
.thumb_func
Reset_Handler:
    b loop

.thumb_func
loop:
    b .

.align
.word 0x11223344
.word _edata
.word _sdata
.word _la_data
.word _ebss
.word _sbss
.word 0x55667788

起点不错。您从阅读中了解到的链接器可以生成变量,如果您愿意的话,您可以在代码中使用这些变量,如 C 代码中所示,并且在此处可用。

建造它

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m4 so.s -o so.o
arm-none-eabi-ld -nostdlib -nostartfiles -T so.ld so.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy -O binary so.elf so.bin
arm-none-eabi-objcopy -O srec --srec-forceS3 so.elf so.srec

检查转储

Disassembly of section .text:

08000000 <Reset_Handler-0x10>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000011    stmdaeq r0, {r0, r4}
 8000008:   08000013    stmdaeq r0, {r0, r1, r4}
 800000c:   08000013    stmdaeq r0, {r0, r1, r4}

08000010 <Reset_Handler>:
 8000010:   e7ff        b.n 8000012 <loop>

08000012 <loop>:
 8000012:   e7fe        b.n 8000012 <loop>
 8000014:   11223344            ; <UNDEFINED> instruction: 0x11223344
 8000018:   20000000    andcs   r0, r0, r0
 800001c:   20000000    andcs   r0, r0, r0
 8000020:   08000030    stmdaeq r0, {r4, r5}
 8000024:   20000000    andcs   r0, r0, r0
 8000028:   20000000    andcs   r0, r0, r0
 800002c:   55667788    strbpl  r7, [r6, #-1928]!   ; 0xfffff878

那是反汇编所以它正在尝试反汇编所有内容,看这个

08000000 <Reset_Handler-0x10>:
 8000000:   20001000   sp initialization value
 8000004:   08000011   reset handler address orred with one (see the docs)
 8000008:   08000013   some other handler
 800000c:   08000013   some other handler


 8000014:   11223344   .word 0x11223344
 8000018:   20000000   .word _edata
 800001c:   20000000   .word _sdata
 8000020:   08000030   .word _la_data
 8000024:   20000000   .word _ebss
 8000028:   20000000   .word _sbss
 800002c:   55667788   .word 0x55667788

没有 .data 所以 edata 和 sdata 在同一个地方。 la_data 是一种奇怪的东西,然后没有.bss 所以开始和结束在同一个地方。所以添加一些

.cpu cortex-m4

.word 0x20001000
.word Reset_Handler
.word loop
.word loop

.globl Reset_Handler
.thumb_func
Reset_Handler:
    b loop

.thumb_func
loop:
    b .

.align
.word 0x11223344
.word _edata
.word _sdata
.word _la_data
.word _ebss
.word _sbss
.word 0x55667788

.section .bss
.byte 0

.section .data
.byte 0x66


Disassembly of section .text:

08000000 <Reset_Handler-0x10>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000011    stmdaeq r0, {r0, r4}
 8000008:   08000013    stmdaeq r0, {r0, r1, r4}
 800000c:   08000013    stmdaeq r0, {r0, r1, r4}

08000010 <Reset_Handler>:
 8000010:   e7ff        b.n 8000012 <loop>

08000012 <loop>:
 8000012:   e7fe        b.n 8000012 <loop>
 8000014:   11223344            ; <UNDEFINED> instruction: 0x11223344
 8000018:   20000004    andcs   r0, r0, r4
 800001c:   20000000    andcs   r0, r0, r0
 8000020:   08000030    stmdaeq r0, {r4, r5}
 8000024:   20000008    andcs   r0, r0, r8
 8000028:   20000004    andcs   r0, r0, r4
 800002c:   55667788    strbpl  r7, [r6, #-1928]!   ; 0xfffff878

Disassembly of section .data:

20000000 <_sdata>:
20000000:   00000066    andeq   r0, r0, r6, rrx

Disassembly of section .bss:

20000004 <__bss_start__>:
20000004:   00000000    andeq   r0, r0, r0

 8000018:   20000004    andcs   r0, r0, r4
 800001c:   20000000    andcs   r0, r0, r0
 8000020:   08000030    stmdaeq r0, {r4, r5}
 8000024:   20000008    andcs   r0, r0, r8
 8000028:   20000004    andcs   r0, r0, r4

所以 .data 从 0x20000000 到 0x20000004(-1) 和 bss 从 0x20000004 到 0x20000008(-1)

S00A0000736F2E7372656338
S315080000000010002011000008130000081300000863
S31508000010FFE7FEE744332211040000200000002019
S315080000203000000808000020040000208877665584
S309080000306600000058
S70508000011E1

在地址 0x0800030 我们可以看到 .data 值

所以你可以简单地 re-write 汇编语言的 C 代码(不需要做这个分析但很好)。如果您没有将对齐放入链接器脚本中,那么您必须像 C 代码一样逐字节复制,或者如果幸运的话并且想要将代码放入其中,您可以尝试更快地检测一些东西,但两端都需要不对齐同理。

您需要在 bootstrap 中为这样的 MCU 做的事情,至少,

1) stack pointer
2) .data
3) .bss
4) call/branch to C entry point
5) infinite loop

很多人会说你永远不应该从 main() 中 return 但是

1) you can protect them anyway, and they will thank you later
2) they perhaps have not created a purely event driven solution

不疼。因此,当您阅读 arm 的文档时,他们有一种加载堆栈指针的机制,如果您使用它,则会选中第一个框。

不打算精简和刻薄,完全未经测试,可能有越野车:

.cpu cortex-m4
.syntax unified

.word 0x20001000
.word Reset_Handler
.word loop
.word loop

.globl Reset_Handler
.thumb_func
Reset_Handler:
    /*copy .data section to SRAM */
    /*uint32_t size = (uint32_t)&_edata - (uint32_t)&_sdata;*/
    ldr r0,=_edata
    ldr r1,=_sdata
    subs r0,r0,r1
    bne data_loop_done

    /*uint8_t *pDst = (uint8_t*)&_sdata; //sram*/
    /*uint8_t *pSrc = (uint8_t*)&_la_data; //flash*/

    ldr r2,=_la_data

    /*
    for(uint32_t i =0 ; i < size ; i++)
    {
            *pDst++ = *pSrc++;
    }
    */

data_loop:
    ldrb r3,[r2]
    adds r2,#1
    strb r3,[r1]
    adds r1,#1
    subs r0,r0,#1
    bne data_loop
data_loop_done:

    /*
    Init. the .bss section to zero in SRAM
    size = (uint32_t)&_ebss - (uint32_t)&_sbss;
    pDst = (uint8_t*)&_sbss;
    for(uint32_t i =0 ; i < size ; i++)
    {
            *pDst++ = 0;
    }
    */

    ldr r0,=_ebss
    ldr r1,=_sbss
    mov r2,#0
    subs r0,r0,r1
    bne bss_loop_done
bss_loop:
    strb r2,[r1]
    adds r1,#1
    bne bss_loop
bss_loop_done:

    /*__libc_init_array();*/
    bl __libc_init_array

    /*main();*/
    bl main

    b loop

.thumb_func
loop:
    b .

__libc_init_array:
    bx lr

main:
    bx lr

.align
.word 0x11223344
.word _edata
.word _sdata
.word _la_data
.word _ebss
.word _sbss
.word 0x55667788

.section .bss
.byte 0

.section .data
.byte 0x66

但功能正常

08000010 <Reset_Handler>:
 8000010:   4814        ldr r0, [pc, #80]   ; (8000064 <main+0x1e>)
 8000012:   4915        ldr r1, [pc, #84]   ; (8000068 <main+0x22>)
 8000014:   1a40        subs    r0, r0, r1
 8000016:   d106        bne.n   8000026 <data_loop_done>
 8000018:   4a14        ldr r2, [pc, #80]   ; (800006c <main+0x26>)

0800001a <data_loop>:
 800001a:   7813        ldrb    r3, [r2, #0]
 800001c:   3201        adds    r2, #1
 800001e:   700b        strb    r3, [r1, #0]
 8000020:   3101        adds    r1, #1
 8000022:   3801        subs    r0, #1
 8000024:   d1f9        bne.n   800001a <data_loop>

08000026 <data_loop_done>:
...
 8000064:   20000004    andcs   r0, r0, r4
 8000068:   20000000    andcs   r0, r0, r0
 800006c:   08000078    stmdaeq r0, {r3, r4, r5, r6}

如果你小心的话,你可以在没有必要的地方强制执行 thumb2 指令。您可以使用 thumb2 指令来改进这一点,但是如果链接描述文件完成了它的工作,那么您可以使用 ldr/str 并一次做一个词,可能与最终值而不是大小进行比较。随便...

嗯,是的,我确实在上面的代码中遗漏了一条指令...

    ldr r0,=_ebss
    ldr r1,=_sbss
    mov r2,#0
    cmp r0,r1
    beq bss_loop_done
bss_loop:
    str r2,[r1]
    adds r1,#4
    cmp r0,r1
    bne bss_loop
bss_loop_done:

根据系统(芯片)的不同,应该快四倍或更多倍。但是您必须确保开始地址和结束地址对齐。您可以通过将对齐方式增加到 double-word 边界

来更进一步
    ldr r0,=_ebss
    ldr r1,=_sbss
    mov r2,#0
    mov r3,#0
    cmp r0,r1
    beq bss_loop_done
bss_loop:
    stm r1!,{r2,r3}
    cmp r0,r1
    bne bss_loop
bss_loop_done:

本可以在一次循环中使用单词中的stm并保存一条指令。您可能会一次看到 4 个单词的增益,但可能不会在 cortex-m 上看到,最多 2 个单词是一个很好的平衡。您可以对 .data 副本进行相同的优化。

我希望这不是家庭作业,如果是,您仍然可以找到并调试它。但是阅读和移植代码是一件简单的事情。查看无穷无尽的示例。

现在在屏幕上查看链接器脚本,它是为以下设计的:

.cpu cortex-m4
.syntax unified

.section .isr_vector

.word 0x20001000
.word Reset_Handler
.word loop
.word loop

.section .text

.globl Reset_Handler
.thumb_func
Reset_Handler:
    b loop

.thumb_func
loop:
    b .

Disassembly of section .text:

08000000 <Reset_Handler-0x10>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000011    stmdaeq r0, {r0, r4}
 8000008:   08000013    stmdaeq r0, {r0, r1, r4}
 800000c:   08000013    stmdaeq r0, {r0, r1, r4}

08000010 <Reset_Handler>:
 8000010:   e7ff        b.n 8000012 <loop>

08000012 <loop>:
 8000012:   e7fe        b.n 8000012 <loop>

这样您就不必按特定顺序在命令行上获取对象。

链接描述文件和bootstrap代码之间有着密切的关系,缺一不可,它们是一对。您不能也不应该尝试随意混合和匹配各种链接描述文件和 bootstrap 来自项目的代码,需要按照设计将它们放在一起。

链接器脚本不是 portable 并且汇编语言不假定是 portable 所以 IMO 你应该让每个都尽可能简单,精简和平均,少即是多,少到端口,更少维护,更少工具链特定的东西。这不是开发人员的普遍看法,他们喜欢对复杂的链接器脚本进行粗暴的制作。 C 库也可以在这里发挥作用,对于 gnu 模型,C 库实际上是一个单独的部分,您可以插入任何您想要的(它附带相关的 bootstrap 和链接描述文件),但这取决于关于该库的工作原理、目标等

没有 RTOS 的微控制器并不是真正的 C 库友好型,因此您必须问问自己我是否真的需要 C 库,我可以使这个项目更简单、更小(更便宜)、可读性和可维护性如何?

我的看起来像这样

.thumb_func
reset:
    bl main
    b .

MEMORY
{
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > rom
    .rodata : { *(.rodata*) } > rom
    .bss    : { *(.bss*)    } > ram
}

对于我们每个阅读本文的人来说,您都会看到不同的风格、不同的观点等。这是 bare-metal 的另一个特点,可以按照自己的方式自由行事,仅真正受硬件规则约束,仅此而已。 No-one的解决方案确实是错误的,只是反映了他们的风格。