从 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();
}
编辑:我的工具链和链接脚本的详细信息如下。
- 开发板:带 Cortex-M4 的 STM32F407VG。
- 用于调试的 OpenOCD 和 GDB
- vim 用于代码编辑器(我的目的是在没有任何
IDE-提供的启动或链接器代码)。
arm-none-eabi-gcc
用于编译和链接
我正在尝试跟随 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的解决方案确实是错误的,只是反映了他们的风格。
我希望能够 运行 并调试在 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();
}
编辑:我的工具链和链接脚本的详细信息如下。
- 开发板:带 Cortex-M4 的 STM32F407VG。
- 用于调试的 OpenOCD 和 GDB
- vim 用于代码编辑器(我的目的是在没有任何 IDE-提供的启动或链接器代码)。
arm-none-eabi-gcc
用于编译和链接
我正在尝试跟随 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的解决方案确实是错误的,只是反映了他们的风格。