美文网首页
ARM GNU 编译与链接01: 工程创建, 程序烧写和调试

ARM GNU 编译与链接01: 工程创建, 程序烧写和调试

作者: wjundong | 来源:发表于2022-03-20 15:52 被阅读0次

    ARM GNU 编译与链接01: 工程创建, 程序烧写和调试

    基于 STM32 平台, 对编译与链接原理进行探究,以及学习 ARM 汇编指令集, GNU 的汇编语法。

    从零开始的工程创建

    这里以一个简单的汇编程序为例

    • 源代码 start.s

      /* 代码段 */
      .section .text
      .type reset, %function
      .globl  reset
      
      /* 程序入口 */
      reset:
          mov r0, #0x66
          mov r1, #255
      
          push {r0}
          mov r0, r1
          pop {r0}
          b .
      
      /* 中断向量表段 */
      .section  .isr_vector, "a"
          .word _estack
          .word reset
      
    • 链接文件:link.ld

      /* 程序入口 */
      ENTRY(reset)
      
      /* 栈指针初始位置 */
      _estack = 0x20020000;
      
      /* 存储器分布 */
      MEMORY
      {
          DTCMRAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 128K
          FLASH (rx)          : ORIGIN = 0x8000000, LENGTH = 128K
      }
      
      /* 段组织 */
      SECTIONS
      {
          /* FLASH 起始地址放置中断向量表 */
          .my_vertor :
          {
              /* 中断向量表的数据一般是由CPU中断自动调用的, 在程序中有可能没被引用, 
              容易被GCC的优化程序当成垃圾回收, 因此使用 KEEP 保持原样不变 */
              . = ALIGN(4);
              KEEP(*(.isr_vector))
              . = ALIGN(4);
          } >FLASH
      
          /* 代码段紧随其后 */
          .text :
          {
              . = ALIGN(4);
              *(.text)
              . = ALIGN(4);
          } >FLASH
      }
      

      上述定义了两个段, 分别是代码段和中断向量表段。在 STM32 中 FLASH的起始地址为0x08000000, 可以通过设置BOOT0引脚来设置程序从FLASH启动, 这时复位后 MCU 在执行完内固化 程序后将会从0x08000000 地址加载 sp 指针, 并从 0x08000004 加载 pc 指针并转跳。
      因此上面定义中断向量表段, 包含了栈指针初值和入口地址, 这个栈指针初值其实就是RAM内存的末 尾, 因为 ARM 的入栈时sp指针自减, 放在末尾保证最开始初始化时有足够大的栈空间。

    • 编译文件: Makefile

      ######################################
      # 项目设置
      ######################################
      TARGET = demo
      OPT = -g -gdwarf-2 -Og
      BUILD_DIR = build
      
      ######################################
      # 源代码区
      ######################################
      C_SOURCES =  
      ASM_SOURCES =  start.s
      
      #######################################
      # GNU 工具链
      #######################################
      PREFIX = arm-none-eabi-
      
      CC = $(PREFIX)gcc
      AS = $(PREFIX)gcc -x assembler-with-cpp
      CP = $(PREFIX)objcopy
      SZ = $(PREFIX)size
      
      HEX = $(CP) -O ihex
      BIN = $(CP) -O binary -S
      
      #######################################
      # 编译参数
      #######################################
      # MCU
      CPU = -mcpu=cortex-m7
      FPU = -mfpu=fpv5-d16
      FLOAT-ABI = -mfloat-abi=hard
      MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
      
      # 宏定义
      AS_DEFS = 
      C_DEFS =  
      
      # 头文件路径
      AS_INCLUDES = 
      C_INCLUDES =  
      
      # 编译标志
      ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
      CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
      # 生成C语言的头文件依赖信息
      CFLAGS += -MMD -MP -MF $(BUILD_DIR)/$*.d
      -include $(wildcard $(BUILD_DIR)/*.d)
      
      #######################################
      # 链接信息
      #######################################
      LDSCRIPT = link.ld
      LIBS = -lc -lm -lnosys 
      LIBDIR = 
      LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
      
      
      #######################################
      # 构建程序
      #######################################
      
      # 列出所有目标文件
      OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
      vpath %.c $(sort $(dir $(C_SOURCES)))
      OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
      vpath %.s $(sort $(dir $(ASM_SOURCES)))
      
      # 默认动作: 编译所有
      all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
      
      $(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
        @echo "$< -> $@"
        @$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
      
      $(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
        @echo "$< -> $@"
        @$(AS) -c $(CFLAGS) $< -o $@
        
      $(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
        @$(CC) $(OBJECTS) $(LDFLAGS) -o $@
        $(SZ) $@
      
      $(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
        $(HEX) $< $@
        
      $(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
        $(BIN) $< $@
        
      $(BUILD_DIR):
        @mkdir $@
      
      clean:
        -rm -fR $(BUILD_DIR)
      

      Makefile 这么长是为了向后面程序兼容, 后面写多文件的话只需设置C_SOURCES 和 ASM_SOURCES 文件就可以了.
      Makefile 详情参考

    • 下载和调试文件 openocd.cfg

        # 设置调试器和MCU, openocd 已经给我们写好了
        source [find interface/stlink.cfg]
        source [find target/stm32h7x.cfg]
      
        # 复位设置, 对于 ST-link SWD 模式, 必须设置才能软复位
        reset_config none separate
      
        # 下载程序
        program  build/demo.hex verify
      

      openocd 的使用详情参考

    编译

    $ ls
    link.ld  Makefile  openocd.cfg  start.s
    $ make
    start.s -> build/start.o
    arm-none-eabi-size build/demo.elf
       text    data     bss     dec     hex filename
         28       0       0      28      1c build/demo.elf
    arm-none-eabi-objcopy -O ihex build/demo.elf build/demo.hex
    arm-none-eabi-objcopy -O binary -S build/demo.elf build/demo.bin
    

    下载和开启调试服务

    $ openocd
    ...
    ** Programming Started **
    ...
    ** Programming Finished **
    ** Verify Started **
    ** Verified OK **
    ...
    Info : Listening on port 6666 for tcl connections
    Info : Listening on port 4444 for telnet connections
    

    使用GDB进行调试:

    $ arm-none-eabi-gdb build/demo.elf
    ...
    Reading symbols from build/demo.elf...
    (gdb) target remote localhost:3333
    reset () at start.s:10
    10          mov r0, #0x66
    (gdb) n
    halted: PC: 0x0800000a
    11          mov r1, #255
    

    使用 VSCode

    使用 VSCode 进行调试需要设置 launch.json

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "(gdb) 启动",
                "type": "cppdbg",
                "request": "launch",
                "program": "${workspaceFolder}/build/demo.elf",
                "args": [],
                "stopAtEntry": true,
                "cwd": "${fileDirname}",
                "environment": [],
                "externalConsole": false,
                "MIMode": "gdb",
                "miDebuggerPath": "arm-none-eabi-gdb",
                "setupCommands": [
                        {"text": "set remotetimeout 5"},
                        {"text": "target extended-remote localhost:3333"},
                        {"text": "monitor reset halt"},
                ]
            }
        ]
    }
    

    确保使用 openocd 打开调试服务, 然后开始调试如图所示


    image.png

    发现 VSCode 没能在汇编上打断点, 不过可以在左边栏手动添加断点标签, 比如上面在reset标签打了断点, 所以程序就在入口出停了下来, 这时观察左边栏的寄存器信息, 发现 sp指针正是 0x20020000, pc 指针是 0x8000008, 因为中断向量表只放了2 个word数据, 也就是8字节, 在 link.ld 中 中断向量表后面立刻是程序了, 所以程序复位后pc值就是0x8000008。
    同时我们可以在下面的输出栏的调试控制台对GDB发送命令来查看更多信息, 比如打印FLASH初始地址的内容 -exec x /4x 0x8000000, 查看某个程序的汇编内容 -exec disassemble reset
    成功进入调试后, 单步运行查看寄存器和pc、sp指针的变化, 理解这个过程

    相关文章

      网友评论

          本文标题:ARM GNU 编译与链接01: 工程创建, 程序烧写和调试

          本文链接:https://www.haomeiwen.com/subject/dvzodrtx.html