[其他] Cortex-M3 ARM代码编译,链接与启动过程深度分析

[复制链接]
 楼主| 米多0036 发表于 2022-12-30 23:56 | 显示全部楼层 |阅读模式
本篇文章以武汉杰开科技的汽车级MCU芯片AC7811为硬件平台,使用GNU GCC作为开发工具。详细分析Compile 、Link 、Loader的过程以及Image(二进制程序)启动的详细分析。整个过程分析涉及到RW可读写DATA段从Flash到Mem的Copy,BSS段的初始化,Stack和Heap的初始化,C库函数移植、利用Semihosting 实现基本的IO等内容。基本可以让你从更深刻的层面理解源码 -> 编译 -> 链接 -> 运行的整个过程。理解了这些个过程之后,你就对那些从语言编程层面来说难于理解的问题自然领会了,比如:我们的源码时如何生成相应的代码段和数据段,代码段和数据段在哪?全局变量和局部变量的区别到底在哪?Stack和Heap的区别到底在哪?等等一些看起来是规定的东西,书本里一切不自然的概念都需要你用心去理解,去实践,达到自然的状态才有可能去解决实际遇到的问题。本文参考官方文档:Makefile,GNU GCC,Linkers and Loaders,Cortex-M3 Technical Reference Manual和程序员的自我修养—链接、装载与库。

评论

———————————————— 版权声明:本文为CSDN博主「背包旅行码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/zhuwade/article/details/121944254  发表于 2022-12-31 13:27
 楼主| 米多0036 发表于 2022-12-30 23:57 | 显示全部楼层
AUTOChip Cortex-M3 地址映射(Memory mapping)
​ 如在我们比较常用的操作系统(Windows/Linux)中,整个Virtual Memory地址通过硬件的MMU被映射到相应的Physical RAM/Memory。然而在嵌入式系统中对于RAM,它时没有MMU的。因此在一些嵌入式系统里,如比较常用的STM32来说,地址映射被分为Flash Segments(也是我们所知的Flash,用来存储代码和数据)和RAM Segments用来存可读写数据。在分析代码编译,链接与启动之前,我们必须了解整个Cortex-M3 MCU的地址分配和启动模式。
 楼主| 米多0036 发表于 2022-12-30 23:58 | 显示全部楼层
 楼主| 米多0036 发表于 2022-12-30 23:59 | 显示全部楼层
从上图中AutoChip AC7811的Flash和SRAM地址范围分别在0x000 0000 ~ 0x2000 0000和0x2000 0000 ~ 0x4000 0000,内部外围总线地址0x4000 0000 ~ 0x6000 0000(这是我们常用的说的_APB_(Advanced Peripheral Bus),外围总线地址),外部存储设备地址0x6000 0000 ~ 0x6100 0000(这里是给外挂SPI FLASH映射的地址空间),Cortex-M3私有Debug,外部和内部总线接口地址0xE000 0000 ~ 0xE100 0000(参考Cortex-M3 Technical Reference Manual)。本文主要讲的是Flash地址0x000 0000 ~ 0x2000 0000和SRAM地址0x2000 0000 ~ 0x4000 0000的应用。
 楼主| 米多0036 发表于 2022-12-30 23:59 | 显示全部楼层
下面通过分析AutoChip AC7811的四个boot mode启动模式帮助大家理解对应的地址映射转换。在AC7811技术参考手册page23 Boot configuration可以设置UART1_CTS和UART1_RTS管脚使能不同的启动模式。
 楼主| 米多0036 发表于 2022-12-31 13:15 | 显示全部楼层
 楼主| 米多0036 发表于 2022-12-31 13:16 | 显示全部楼层
Flash memory boot up 和 SRAM boot up映射框图如下:
6890163afc5a69629e.png
 楼主| 米多0036 发表于 2022-12-31 13:18 | 显示全部楼层
ISP boot up 和 Serial flash boot up映射框图如下:

 楼主| 米多0036 发表于 2022-12-31 13:19 | 显示全部楼层
 楼主| 米多0036 发表于 2022-12-31 13:20 | 显示全部楼层
裸机程序的整体说明
​ 我们都熟悉有操作系统支持的应用程序开发,比如 Linux下C语言的开发。我们可以不用关心程序启动的细节,同时我们一般还可以使用各种方便的lib 库,比如基本的IO操作(printf scan),动态分配内存操作(malloc),文件操作(fopen fwrite fread)等。有操作系统支持的情况下,程序的编译、链接、启动都是有操作系统支持的,常用的编程库函数使用的是标准的C库。
 楼主| 米多0036 发表于 2022-12-31 13:25 | 显示全部楼层
那如果没有OS支持的情况下,想实现上面这些功能的话,该怎么做呐?这种情况就叫 Bare Metal (裸)程序开发。在嵌入式开发中是比较常见的情况,本文主要讲解基于Cortex-M3 的裸程序开发。本裸机程序实现了 基本IO,动态内存分配,基本函数库等功能。
 楼主| 米多0036 发表于 2022-12-31 13:27 | 显示全部楼层
如何实现的startup
​ 还是和有OS支持的情况下来对比,有OS的情况下分析一个Project,一般会从3个方面来进行分析:一是看源代码的组织形式;一是看Compile && Link过程(即Makefile);三是看Run时的情况(一般看运行起来后几个Process,几个Thread,以及他们之间的关系)。分析完这3个方面后,整个project从静到动,以及动静之间的转换都包括了,也就掌握了整个的Project。
 楼主| 米多0036 发表于 2022-12-31 13:28 | 显示全部楼层
在没有OS的情况下,1 2 两个方面是一样的,只不过程序运行的基础环境不一样,裸机程序运行需要考虑的细节多一些。裸机程序需要考虑的基本问题有:

编译生成的可执行程序结构是什么样的?整个可执行程序的入口在哪?

需要将可执行程序下载到什么地方?程序运行前需要做哪些准备工作?

C语言运行需要什么样的环境?

我们按照上面说的方法,从3各方面出发,分析我们的Project。
 楼主| 米多0036 发表于 2022-12-31 13:29 | 显示全部楼层
源代码:
顶层目录结构:
  1. # tree -l
  2. .
  3. ├── App
  4. ├── Device
  5. │   ├── Include
  6. │   │   ├── CMSIS
  7. │   └── Source
  8. │       ├── ARM
  9. ├── Drivers
  10. │   ├── inc
  11. │   └── src
  12. └── makefile
 楼主| 米多0036 发表于 2022-12-31 13:31 | 显示全部楼层
  1. 其中App目录是Application层的主逻辑代码,其中main.c就在App目录中,是业务逻辑层的主代码。 Device-->include-->CMSIS目录是arm cmsis框架层的interface 说明文件。 Device-->Source-->ARM目录是启动代码startup_ac78xx.s和ac7811_flash.ld脚本。 ac7811_flash.ld脚本主要告诉ld(链接器)如何链接各个Objects文件为可执行程序. Drivers目录是AutoChip提供的ac7811的SDK包。
 楼主| 米多0036 发表于 2022-12-31 13:31 | 显示全部楼层
更详细的项目目录结构:
  1. # tree -l
  2. .
  3. ├── App
  4. │   └── main.c
  5. ├── Device
  6. │   ├── Include
  7. │   │   ├── CMSIS
  8. │   │   │   ├── arm_common_tables.h
  9. │   │   │   ├── arm_const_structs.h
  10. │   │   │   ├── arm_math.h
  11. │   │   │   ├── cmsis_armcc.h
  12. │   │   │   ├── cmsis_armclang.h
  13. │   │   │   ├── cmsis_compiler.h
  14. │   │   │   ├── cmsis_gcc.h
  15. │   │   │   ├── cmsis_iccarm.h
  16. │   │   │   ├── cmsis_version.h
  17. │   │   │   ├── core_cm3.h
  18. │   │   │   ├── mpu_armv7.h
  19. │   │   ├── ac78xx.h
  20. │   │   ├── ac78xx_ckgen.h
  21. │   │   ├── ac78xx_debugout.h
  22. │   │   ├── ac78xx_spm.h
  23. │   │   ├── debugzone.h
  24. │   │   └── system_ac78xx.h
  25. │   └── Source
  26. │       ├── ARM
  27. │       │   ├── ac7811_flash.ld
  28. │       │   └── startup_ac78xx.s
  29. │       ├── ac78xx_ckgen.c
  30. │       ├── ac78xx_ckgen_regs.h
  31. │       ├── ac78xx_debugout.c
  32. │       ├── ac78xx_spm.c
  33. │       ├── ac78xx_spm_regs.h
  34. │       ├── syscalls.c
  35. │       └── system_ac78xx.c
  36. ├── Drivers
  37. │   ├── inc
  38. │   │   ├── ac78xx_can.h
  39. │   │   ├── ac78xx_can_reg.
  40. │   │   ├── ac78xx_dma.h
  41. │   │   ├── ac78xx_dma_reg.h
  42. │   │   ├── ac78xx_eflash.h
  43. │   │   ├── ac78xx_eflash_reg.h
  44. │   │   ├── ac78xx_uart.h
  45. │   │   ├── ac78xx_uart_reg.h
  46. │   │   ├── ......
  47. │   └── src
  48. │       ├── ac78xx_can.c
  49. │       ├── ac78xx_dma.c
  50. │       ├── ac78xx_eflash.c
  51. │       ├── ac78xx_uart.c
  52. │       ├── ......
  53. └── makefile
 楼主| 米多0036 发表于 2022-12-31 13:32 | 显示全部楼层
编译与链接
当然是直接 make 喽。但是我们还是需要知道对应的编译规则。不得不说AC7811的makefile写的还是非常规范的。我们可以把作为一个很好的makefile模版。
 楼主| 米多0036 发表于 2022-12-31 13:33 | 显示全部楼层
makefile详细说明:
  1. #--------------------------------- 编译参数 ------------------------------------
  2. #把编译过程中的命令参数log不往屏幕显示
  3. ifneq ($(V),1)
  4. Q                := @
  5. NULL        := 2>/dev/null
  6. endif

  7. TARGET := DEMO#编译文件名称,根据命名需要可自行修改
  8. OPT    := -O0#不做任何优化,这是默认的编译选项。
  9. CSTD   := -std=c11#使用C11标准库
  10. CXXSTD := -std=c++11#使用C++11标准库

  11. #--------------工程需要编译的头文件,根据需要自行添加--------------------
  12. INC_FLAGS += -I ./Device/Include     \
  13.                      -I ./Device/Include/CMSIS \
  14.                          -I ./Drivers/inc       
  15. #------链接文件,里面指定了芯片flash,ram大小,需根据实际大小进行修改
  16. LDSCRIPT := ./Device/Source/ARM/ac7811_flash.ld

  17. ARCH_FLAGS += -mthumb#thumb指令
  18. ARCH_FLAGS += -mcpu=cortex-m3#cortex-m3 cpu架构

  19. #编译告警设置
  20. CWARN_FLAGS += -Wall -Wshadow
  21. CWARN_FLAGS += -fno-common -ffunction-sections -fdata-sections
  22. CWARN_FLAGS += -Wimplicit-function-declaration  
  23. CWARN_FLAGS += -Wstrict-prototypes

  24. #--通过printf打印串口log,需设置-specs=nosys.specs,并且在syscalls.c中实现_write_r函数,把printf映射到串口上。
  25. LDLIBS                += -Wl,--start-group -lc -lgcc  -Wl,--end-group -lm -specs=nosys.specs

  26. #----------------------------- 搜索工程目录下的源代码 ---------------------------

  27. AS_SRC := ./Device/Source/ARM/startup_ac78xx.s
  28. AS_OBJ := $(AS_SRC:%.s=%.o)
  29. #-------源代码需根据实际情况删减-------------
  30. C_SRC := ./Device/Source/ac78xx_ckgen.c    \
  31.                  ./Device/Source/ac78xx_spm.c      \
  32.                  ./Device/Source/system_ac78xx.c   \
  33.                  ./Device/Source/ac78xx_debugout.c \
  34.                  ./Device/Source/syscalls.c \
  35.                  ./Drivers/src/ac78xx_dma.c        \
  36.                  ./Drivers/src/ac78xx_gpio.c        \
  37.                  ./Drivers/src/ac78xx_timer.c        \
  38.                  ./Drivers/src/ac78xx_uart.c        \
  39.                  ./Drivers/src/ac78xx_wdg.c        \
  40.                  ./App/main.c
  41. C_OBJ := $(C_SRC:%.c=%.o)  

  42. #--------------------------------- 参数整合 ------------------------------------
  43. # C flags
  44. CFLAGS := $(OPT) $ $(CSTD) $(INC_FLAGS) $(FP_FLAGS)
  45. CFLAGS += $(DEFINES) $(ARCH_FLAGS) $(CWARN_FLAGS) -g #-g 增加调试选项,可以使用GDB进行调试

  46. # Linker flags 链接器编译选项
  47. LDFLAGS                := --static#静态编译
  48. LDFLAGS                += -Wl,-Map=$(TARGET).map -Wl,--gc-sections
  49. LDFLAGS                += -T$(LDSCRIPT) $(ARCH_FLAGS) $(LDLIBS)

  50. # OBJ
  51. OBJ = $(AS_OBJ) $(C_OBJ)

  52. #-------------------------------- 编译器调用指令 --------------------------------
  53. PREFIX        := arm-none-eabi

  54. CC                := $(PREFIX)-gcc
  55. CXX                := $(PREFIX)-g++
  56. LD                := $(PREFIX)-gcc
  57. AR                := $(PREFIX)-ar
  58. AS                := $(PREFIX)-as
  59. OBJCOPY        := $(PREFIX)-objcopy
  60. OBJDUMP        := $(PREFIX)-objdump
  61. GDB                := $(PREFIX)-gdb

  62. .SUFFIXES: .elf .bin .hex .list .map .images
  63. .SECONDEXPANSION:
  64. .SECONDARY:

  65. all: elf bin hex

  66. elf: $(TARGET).elf
  67. bin: $(TARGET).bin
  68. hex: $(TARGET).hex
  69. list: $(TARGET).list
  70. images: $(TARGET).images

  71. %.images: %.bin %.hex %.list %.map
  72.         [url=home.php?mod=space&uid=384087]@printf[/url] "*** $* images generated ***\n"
  73. #objdump生成二进制文件
  74. %.bin: %.elf         
  75.         @printf "  OBJCOPY $(*).bin\n"
  76.         $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).bin
  77. #objdump生成hex文件
  78. %.hex: %.elf
  79.         @printf "  OBJCOPY $(*).hex\n"
  80.         $(Q)$(OBJCOPY) -Oihex $(*).elf $(*).hex
  81.        
  82. %.list: %.elf
  83.         @printf "  OBJDUMP $(*).list\n"
  84.         $(Q)$(OBJDUMP) -S $(*).elf > $(*).list
  85. #链接map生成elf规则       
  86. %.elf %.map: $(OBJ) $(LDSCRIPT)
  87.         @printf "  LD      $(TARGET).elf\n"
  88.         $(Q)$(LD) $(OBJ) $(LDFLAGS) -o $(TARGET).elf
  89. #汇编文件编译规则
  90. $(AS_OBJ): %.o:%.s
  91.         @printf "  AS      $(*).s\n"
  92.         $(Q)$(CC) $(ARCH_FLAGS) $(FP_FLAGS) -g -Wa,--no-warn -x assembler-with-cpp -o $(*).o -c $(*).s
  93. #C文件编译规则
  94. $(C_OBJ): %.o:%.c
  95.         @printf "  CC      $(*).c\n"
  96.         $(Q)$(CC) $(CFLAGS) -o $(*).o -c $(*).c
  97.        
  98. clean:
  99.         @#printf "  CLEAN\n"
  100.         $(Q)$(RM) $(shell find -name '*.o' -o -name '*.d' -o -name '*.elf' -o -name '*.bin')
  101.         $(Q)$(RM) $(shell find -name '*.hex' -o -name '*.srec' -o -name '*.list' -o -name '*.map')
  102.         $(Q)$(RM) $(shell find -name 'generated.*' -o -name '*.srec' -o -name '*.list' -o -name '*.map')

  103. .PHONY: images clean elf bin hex list flash debug
 楼主| 米多0036 发表于 2022-12-31 13:33 | 显示全部楼层
Makefile Tips
  1. (1)常用的变量名(约定俗成的):
  2. CC:表示c编译器版本
  3. CFLAGS:表示编译时参数
  4. CPPFLAGS:表示预处理参数
  5. CXX:表示C++编译器版本
  6. CXXFLAGS:表示c++编译时参数
  7. LDFLAGS:表示库参数库选项
  8. INCLUDE:表示头文件目录
  9. TARGET:表示目标名
  10. RM:删除选项
  11. #: 注释符号
  12. (2)一些特殊字符
  13. $(变量):对变量取值
  14. @:只显示命令结果,忽略命令本身
  15. -:如果当前命令出错,忽略错误,继续执行
  16. %:通配符,通配符是以遍历的方式实现的
  17. (3)特殊变量
  18. 用于当前目标:
  19. $@:代表目标
  20. $<:代表依赖中的第一个
  21. $^:代表所有依赖
 楼主| 米多0036 发表于 2022-12-31 13:34 | 显示全部楼层
映像结构与运行
​ 有操作系统的情况下,我们不需要关心可执行映像的具体结构,一个可执行程序文件从静态文件到动态运行这个过程叫Loader&&Run。这个过程是由OS来完成的,应用程序级别的开发是不需要关心这些细节的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

137

主题

1430

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部