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

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指针的变化, 理解这个过程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值