接着QEMU+FreeRTOS+STM32+VS Code_阅后即奋的博客-CSDN博客,在QEMU中实现了STM32的启动,并在终端中打印了日志。
在那篇blog中讲到,启动点在下图①位置处。为什么?
还是看Makefile文件,并看其在终端的打印信息:
$(OUTPUT_DIR)/$(IMAGE): ./mps2_m3.ld $(OBJS_OUTPUT) Makefile
@echo ""
@echo ""
@echo "--- Final linking ---"
@echo ""
$(LD) $(OBJS_OUTPUT) $(CFLAGS) -Xlinker --gc-sections -Xlinker -T ./mps2_m3.ld \
-Xlinker -Map=$(OUTPUT_DIR)/RTOSDemo.map -specs=nano.specs \
-specs=nosys.specs -specs=rdimon.specs -o $(OUTPUT_DIR)/$(IMAGE)
$(SIZE) $(OUTPUT_DIR)/$(IMAGE)
--- Final linking ---
arm-none-eabi-gcc ./output/tasks.o ./output/list.o ./output/queue.o ./output/timers.o ./output/event_groups.o ./output/stream_buffer.o ./output/heap_4.o ./output/port.o ./output/AbortDelay.o ./output/BlockQ.o ./output/blocktim.o ./output/countsem.o ./output/death.o ./output/dynamic.o ./output/EventGroupsDemo.o ./output/GenQTest.o ./output/integer.o ./output/IntQueue.o ./output/IntQueueTimer.o ./output/IntSemTest.o ./output/MessageBufferAMP.o ./output/MessageBufferDemo.o ./output/PollQ.o ./output/QPeek.o ./output/QueueOverwrite.o ./output/QueueSet.o ./output/QueueSetPolling.o ./output/recmutex.o ./output/semtest.o ./output/StaticAllocation.o ./output/StreamBufferDemo.o ./output/StreamBufferInterrupt.o ./output/TaskNotify.o ./output/TaskNotifyArray.o ./output/TimerDemo.o ./output/main.o ./output/main_blinky.o ./output/main_full.o ./output/startup_gcc.o ./output/printf-stdarg.o -I./../../../..//Source/include -I./../../../..//Source/portable/GCC/ARM_CM3 -I./../../../..//Demo/Common/include -I./../../../..//Demo/CORTEX_MPS2_QEMU_IAR_GCC -I./../../../..//Demo/CORTEX_MPS2_QEMU_IAR_GCC/CMSIS -nostartfiles -ffreestanding -mthumb -mcpu=cortex-m3 -Wall -Wextra -g3 -O0 -ffunction-sections -fdata-sections -MMD -MP -MF"output/RTOSDemo.out" -MT output/RTOSDemo.out -Xlinker --gc-sections -Xlinker -T ./mps2_m3.ld \
-Xlinker -Map=./output/RTOSDemo.map -specs=nano.specs \
-specs=nosys.specs -specs=rdimon.specs -o ./output/RTOSDemo.out
/home/mi/bin/arm-gnu-toolchain-12.2/bin/../lib/gcc/arm-none-eabi/12.2.1/../../../../arm-none-eabi/bin/ld: warning: ./output/RTOSDemo.out has a LOAD segment with RWX permissions
arm-none-eabi-size ./output/RTOSDemo.out
text data bss dec hex filename
17285 224 65491 83000 14438 ./output/RTOSDemo.out
在这个Makefile文件中,"-T mps2_m3.ld"是告诉链接器使用mps2_m3.ld文件作为链接脚本。在链接阶段,链接器将使用mps2_m3.ld文件中定义的内存布局和符号表等信息来生成最终的可执行文件。
当执行"make"命令时,Makefile文件将会编译所有的源文件,并将它们链接成一个名为"RTOSDemo.out"的可执行文件。在链接阶段,mps2_m3.ld文件将被自动加载并使用。
下面是mps2_m3.ld文件的信息,ld文件的语法这里不讲,可以参考:链接脚本 - 知乎。
MEMORY
{
FLASH (xr) : ORIGIN = 0x00000000, LENGTH = 4M /* to 0x00003FFF = 0x007FFFFF*/
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 4M /* to 0x21FFFFFF = 0xFFFFFF */
}
ENTRY(Reset_Handler)
_Min_Heap_Size = 0x8 ; /* Not used as building heap_4.c */
_Min_Stack_Size = 0x400 ; /* Required amount of stack. Used by main(), then re-used as the interrupt stack after the kernel starts. */
_estack = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS
{
.isr_vector :
{
__vector_table = .;
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
.text :
{
*(.text)
*(.rodata*)
*(.constdata*)
_etext = .;
_sidata = .;
} > FLASH
.data :
{
. = ALIGN(8);
_data = .;
_sdata = .;
*(vtable)
*(.data)
_edata = .;
} > RAM
.bss :
{
. = ALIGN(8);
_bss = .;
_sbss = .;
*(.bss)
_ebss = .;
} > RAM
.heap :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
_heap_bottom = .;
. = . + _Min_Heap_Size;
_heap_top = .;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
/* Set stack top to end of RAM, and stack limit move down by
* size of stack_dummy section */
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - _Min_Stack_Size;
PROVIDE(__stack = __StackTop);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= _heap_top, "region RAM overflowed with stack")
}
该文件定义了内存布局和各个节(section)的位置和大小。
首先,定义了两个内存区域:FLASH和RAM。FLASH是只读的,用于存储程序代码和只读数据,起始地址为0x00000000,大小为4M。RAM是可读写的,用于存储程序运行时的数据和堆栈,起始地址为0x20000000,大小也为4M。
接着,定义了程序的入口点为Reset_Handler。
然后,定义了一些符号(symbol),包括最小堆大小(_Min_Heap_Size)、最小栈大小(_Min_Stack_Size)和栈顶(__StackTop)。其中,最小堆大小和最小栈大小是为了保证程序正常运行所需的最小内存空间,栈顶是指向RAM区域末尾的指针。
最后,定义了各个节的位置和大小。其中,.isr_vector节存放中断向量表,位于FLASH区域;.text节存放程序代码,也位于FLASH区域;.data节存放程序运行时的数据,位于RAM区域;.bss节存放未初始化的全局变量和静态变量,也位于RAM区域;.heap节存放堆空间,位于RAM区域;__StackTop和__StackLimit是栈顶和栈底的符号,用于在程序中获取栈的信息。最后,通过ASSERT宏检查数据、堆和栈的总大小是否超出了RAM区域的大小。
到这解释了为什么入口处是Reset_Handler函数。
使用objdump -h RTOSDemo.out命令(建议重定向到一个文本文件中去查看),可以看到各个section的内容,这里就只展示前10个吧(与前文讲的section分配是一致的):
RTOSDemo.out: 文件格式 elf32-little
节:
Idx Name Size VMA LMA File off Algn
0 .isr_vector 00000078 00000000 00000000 00001000 2**2
CONTENTS, ALLOC, LOAD, DATA
1 .text 0000021b 00000078 00000078 00001078 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .text.xTaskCreateStatic 000000a4 00000294 00000294 00001294 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .text.xTaskCreate 00000094 00000338 00000338 00001338 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
4 .text.prvInitialiseNewTask 00000108 000003cc 000003cc 000013cc 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
5 .text.prvAddNewTaskToReadyList 00000124 000004d4 000004d4 000014d4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
6 .text.xTaskDelayUntil 000000dc 000005f8 000005f8 000015f8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
7 .text.vTaskStartScheduler 000000d0 000006d4 000006d4 000016d4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
8 .text.vTaskSuspendAll 0000001c 000007a4 000007a4 000017a4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
9 .text.xTaskResumeAll 000001f0 000007c0 000007c0 000017c0 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
10 .text.xTaskGetTickCount 0000001c 000009b0 000009b0 000019b0 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
size命令也显示了分配的各个section的情况,dec和hex分别是以十进制和十六进制展示该文件的大小:
arm-none-eabi-size ./output/RTOSDemo.out
text data bss dec hex filename
17285 224 65491 83000 14438 ./output/RTOSDemo.out