Cygwin下编译ARM Makefile报ld returned 1 exit status错误
错误根因
collect2.exe: error: ld returned 1 exit status是链接阶段的通用报错,本质是链接器找不到代码中引用的符号实现,日志里的未定义引用对应问题点非常明确:
_exit:ARM裸机环境默认没有操作系统提供的系统调用桩,链接newlib标准库时会找不到这个退出函数实现SystemInit:CMSIS标准规定的内核初始化函数,一般由芯片SDK的系统初始化文件、启动文件提供Cy_SysEnableApplCore/Cy_GPIO_Pin_Init/Cy_SysTick_DelayInUs:这三个是Traveo系列官方PDL外设驱动库的接口函数,说明没有把对应驱动源文件加入编译列表,也没有正确链接预编译的PDL库__cm4_vector_base_linker_symbol:这个是Cortex-M4核专属的链接脚本符号,说明要么误用了CM4核的链接脚本/启动文件,要么没打开对应CM0+核的编译宏,导致驱动代码里引用了CM4核的相关逻辑
具体修复步骤
- 补全必须的编译源文件
当前源文件清单漏了裸机开发必须的三类文件,从Traveo官方SDK对应所用芯片型号的目录里找出来,加到Makefile的编译列表里:- 对应CM0+核的启动汇编文件(文件名一般为
startup_<芯片型号>.s),包含中断向量表、复位入口逻辑 - 系统初始化源文件(文件名一般为
system_<芯片型号>.c),SystemInit函数就在这个文件里实现,负责内核时钟、内存的初始化配置 - PDL驱动源文件:对应GPIO、SysTick、系统控制模块的c文件,一般存放在PDL库的
drivers/src目录下,至少要包含cy_gpio.c、cy_systick.c、cy_syslib.c、cy_sysclk.c
- 对应CM0+核的启动汇编文件(文件名一般为
- 补全标准库链接配置
在链接参数LDFLAGS里加入--specs=nano.specs --specs=nosys.specs,其中nosys.specs会自动提供_exit等所有newlib依赖的系统调用空实现,不需要自己手动写桩函数;nano.specs会链接精简版C库,大幅减小固件体积。 - 修正链接脚本与编译宏配置
- 删掉所有CM4核相关的链接配置,找到SDK中对应CM0+核的链接脚本(后缀为.ld),在链接参数中用
-T <链接脚本路径>指定 - 在编译参数
CFLAGS中加入CM0+核对应的编译宏,比如-DCORE_M0P、对应芯片型号的宏,关闭双核场景下CM4核的相关代码逻辑,避免驱动代码引用CM4专属符号 - 确认编译架构参数匹配CM0+核:
CFLAGS和LDFLAGS里都要加-mcpu=cortex-m0plus -mthumb,不要误用CM4的-mcpu=cortex-m4参数
- 删掉所有CM4核相关的链接配置,找到SDK中对应CM0+核的链接脚本(后缀为.ld),在链接参数中用
- 验证编译流程
Cygwin环境下先确认arm-none-eabi工具链路径已经加入Cygwin的PATH变量,不要混用Windows原生工具链和Cygwin版make,避免路径识别错误。调整完参数后先执行make clean清除旧的编译产物,再执行make,链接阶段如果还有未定义符号,就顺着符号名找对应的源文件/库文件,加到编译链接列表里即可。链接生成elf文件后,用arm-none-eabi-objcopy就能转换出hex、bin格式的固件。
Makefile优化建议
针对ARM裸机开发的场景,Makefile可以做以下调整,大幅降低维护成本:
- 用通配符自动收集源文件,不要手动逐个列.o文件规则,参考写法:
# 工具链定义,方便后续切换版本 TOOLCHAIN_PREFIX ?= arm-none-eabi- CC := $(TOOLCHAIN_PREFIX)gcc AS := $(TOOLCHAIN_PREFIX)gcc LD := $(TOOLCHAIN_PREFIX)gcc OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy SIZE := $(TOOLCHAIN_PREFIX)size # 目录定义 SRC_DIR := src DRIVER_DIR := drivers BUILD_DIR := build TARGET_NAME := firmware # 自动收集所有需要编译的c、s文件 SRC_C := $(wildcard $(SRC_DIR)/*.c) $(wildcard $(DRIVER_DIR)/src/*.c) SRC_S := startup_<芯片型号>.s OBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRC_C)) OBJS += $(patsubst %.s,$(BUILD_DIR)/%.o,$(SRC_S)) # 通用编译规则,自动把c/s文件编译到build目录下对应路径 $(BUILD_DIR)/%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -c $< -o $@ $(BUILD_DIR)/%.o: %.s @mkdir -p $(dir $@) $(AS) $(ASFLAGS) -c $< -o $@ - 拆分编译、链接参数,把通用配置归类:
CFLAGS中加入头文件路径、警告等级、优化等级、函数数据段分离参数:-Os -Wall -ffunction-sections -fdata-sections -I$(DRIVER_DIR)/includeLDFLAGS中加入段回收、map文件生成参数:-Wl,--gc-sections -Wl,-Map=$(BUILD_DIR)/$(TARGET_NAME).map,配合-ffunction-sections -fdata-sections可以自动删掉没被调用的函数,减小固件体积
- 增加固件生成、大小统计规则:
.PHONY: all clean all: $(BUILD_DIR)/$(TARGET_NAME).elf $(BUILD_DIR)/$(TARGET_NAME).hex $(BUILD_DIR)/$(TARGET_NAME).bin $(SIZE) $< $(BUILD_DIR)/$(TARGET_NAME).elf: $(OBJS) $(LD) $(LDFLAGS) $^ -o $@ $(BUILD_DIR)/$(TARGET_NAME).hex: $(BUILD_DIR)/$(TARGET_NAME).elf $(OBJCOPY) -O ihex $< $@ $(BUILD_DIR)/$(TARGET_NAME).bin: $(BUILD_DIR)/$(TARGET_NAME).elf $(OBJCOPY) -O binary $< $@ clean: rm -rf $(BUILD_DIR) - 不要硬编码文件路径,所有路径、芯片型号、工具链前缀都用变量定义,后续换芯片型号、换目录结构只需要修改变量值即可。
内容的提问来源于stack exchange,提问作者mrm
相关产品推荐
相关产品推荐

