我只能在符号上找到零碎的信息_start
,从目标启动代码中调用它以建立C运行时环境。这对于确保在分支到main()
.
就我而言,我使用的是带有 ARM Cortex-R4F 核心 CPU 的 MCU。当设备重置时,我会执行 MCU 制造商推荐的所有步骤,然后尝试_start
使用以下代码行跳转到符号:
extern void _start(void);
_start();
我正在使用类似于以下内容的内容来链接程序:
armeb-eabi-gcc-7.5.0" -marm -fno-exceptions -Og -ffunction-sections -fdata-sections -g -gdwarf-3 -gstrict-dwarf -Wall -mbig-endian -mcpu=cortex-r4 -Wl,-Map,"app_tms570_dev.map" --entry main -static -Wl,--gc-sections -Wl,--build-id=none -specs="nosys.specs" -o[OUTPUT FILE NAME HERE] [ALL OBJECT FILES HERE] -Wl,-T[LINKER COMMAND FILE NAME HERE]
在这种情况下,我的工具链是gcc-linaro-7.5.0-2019.12-i686-mingw32_armeb-eabi
,因为我的 MCU 设备是大端的,所以正在使用它。
当我跟踪对 symbol 的调用时_start
,我可以看到我的程序分支到 symbol_start
然后发生了一些意想不到的事情。
首先,有几个地方调用了以下指令:
EF123456 svc #0x123456
这基本上会生成一个软件中断,这会导致程序跳转到我为设备配置的软件中断处理程序。
其次,设备最终分支到__libc_init_array
then _init
。但是,符号_init
不包含任何分支指令并允许程序流入_fini
,它也不包含任何分支指令并允许程序流入接下来放置在内存中的任何代码。正如预期的那样,这最终会导致某种类型的中止异常。
与 _init 和 _fini 相关的反汇编:
_init():
00003b00: E1A0C00D mov r12, r13
00003b04: E92DDFF8 push {r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r14, pc}
00003b08: E24CB004 sub r11, r12, #4
_fini():
00003b0c: E1A0C00D mov r12, r13
00003b10: E92DDFF8 push {r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r14, pc}
00003b14: E24CB004 sub r11, r12, #4
根据我阅读的其他一些文档,我也尝试main()
直接调用,但这只是导致程序跳转到main()
没有初始化任何东西。我还尝试调用__main()
类似于使用 ARM 编译器时所做的操作以执行启动代码的符号,但未找到此符号。
请注意,这是针对不使用半主机的裸机系统。
我的问题是:有没有办法设置系统并调用将自动建立 C 运行时环境并分支到main()
使用 GCC 链接器的函数?
目前,我已经实现了我自己的函数来初始化.data
部分,并且这些.bss
部分已经在复位时使用 MCU 设备的内置功能归零。
在此处添加更多详细信息:我正在使用的特定 MCU 不应该相关,特别是考虑到以下讨论。
首先,我已经在汇编文件中设置了设备的异常向量:
.section .excvecs,"ax",%progbits
.type Exc_Vects, %object
.size Exc_Vects, .-Exc_Vects
// See DDI0363G, Table 3-6
Exc_Vects:
b c_int00 // Reset vector
b exc_undef // Undefined instruction
b exc_software // Software
b exc_prefetch // Pre-fetch abort
b exc_data // Data abort
b exc_invalid // Invalid vector
IRQ 和 FIQ 中断也有两条指令,但它们是根据 MCU 数据表设置的。我已经为未定义的指令、预取中止、数据中止和无效向量异常定义了处理程序。对于软件异常,我使用一些程序集跳转到可以在运行时更改的地址。我的启动顺序从c_int00
. 这些都已经过测试并且没有问题。
我的复位处理程序负责按照 MCU 数据表初始化 MCU 所需的所有步骤。这包括初始化 CPU 寄存器和堆栈指针,它们是使用链接器文件中的符号加载的。
如上所述,我正在使用的工具链包括 C 标准库和其他库,这些库可以毫无问题地编译和链接我的程序。这包括_start
我之前提到的符号。
据我了解,该函数_start
通常包含main()
. 在它调用main()
它之前,它会初始化.bss
和.data
分段,配置堆,以及执行一些其他任务来设置环境。返回时main()
,它会执行一些清理任务并分支到指定的exit()
功能。(旁注:_start
根据我从 linaro 下载的源代码在 newlib 中定义)。
在此处的单独回复中有一些关于此的详细信息:
我一直在使用 ARM 编译器作为同一个项目的替代方案。在那里,__main
执行这些功能。对于堆栈初始化,我基本上为它提供了一个空钩子函数,而对于退出,我为它提供了一个安全终止程序的函数,该函数应main()
出于某种原因返回。我不确定GCC是否需要这样的东西。
我会注意到我已经包含了 option-specs="nosys.specs"
而没有 option -nostartfiles
。我的理解是这样可以避免实现一些我的应用程序中不想使用的功能,比如I/O操作,而是链接启动代码。
我没有在我的项目中使用堆,因为动态内存使用是不受欢迎的,但我希望能够主要使用启动代码以避免必须记住.data
手动初始化部分。上面我注意到我的应用程序是baremetal-ish。我实际上正在使用 RTOS 并将内存分区为块,以便我可以使用设备 MPU。