STM32用GCC编译C++时成员变量存储异常引发硬故障问题
STM32 GCC编译C++代码时成员变量写入触发硬故障问题
问题现象
在STM32(Cortex-M0+)上用GCC编译C++代码,设备可加载执行,但写入任何类成员变量时都会触发硬故障。通过GDB调试发现,成员变量被分配到内存起始位置(如0x7、0xb),而STM32写入该区域必然触发硬故障。
观察到异常现象:仅在main中声明普通变量时才会生成BSS段,类成员变量未被分配到BSS或RAM区域。
编译链接配置
编译参数:-nostdlib -mcpu=cortex-m0plus -fno-exceptions -O0 -g
链接脚本:
ENTRY(start_of_memory); MEMORY { rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K ram (xrw) : ORIGIN = 0x20000000, LENGTH = 2K } SECTIONS { .text : { *(.text) } > rom .data : { *(.data) *(.data.*) } > ram .bss : { *(.bss) *(.bss.*) *(COMMON) } > ram }
ELF文件分析(仅使用对象,无main内普通变量声明)
用readelf查看输出,未发现.bss和.data段被加载到RAM:
ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: ARM Version: 0x1 Entry point address: 0x8000000 Start of program headers: 52 (bytes into file) Start of section headers: 76536 (bytes into file) Flags: 0x5000200, Version5 EABI, soft-float ABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 14 Section header string table index: 13 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 08000000 010000 0005a8 00 AX 0 0 4 [ 2] .rodata PROGBITS 080005a8 0105a8 00005c 00 A 0 0 4 [ 3] .ARM.attributes ARM_ATTRIBUTES 00000000 010604 00002d 00 0 0 1 [ 4] .comment PROGBITS 00000000 010631 000049 01 MS 0 0 1 [ 5] .debug_info PROGBITS 00000000 01067a 000a93 00 0 0 1 [ 6] .debug_abbrev PROGBITS 00000000 01110d 0003b8 00 0 0 1 [ 7] .debug_aranges PROGBITS 00000000 0114c5 000060 00 0 0 1 [ 8] .debug_line PROGBITS 00000000 011525 000580 00 0 0 1 [ 9] .debug_str PROGBITS 00000000 011aa5 000416 01 MS 0 0 1 [10] .debug_frame PROGBITS 00000000 011ebc 000228 00 0 0 4 [11] .symtab SYMTAB 00000000 0120e4 000640 10 12 86 4 [12] .strtab STRTAB 00000000 012724 000344 00 0 0 1 [13] .shstrtab STRTAB 00000000 012a68 00008f 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), y (purecode), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x010000 0x08000000 0x08000000 0x00604 0x00604 R E 0x10000 Section to Segment mapping: Segment Sections... 00 .text .rodata There is no dynamic section in this file. There are no relocations in this file. There are no unwind sections in this file. Symbol table '.symtab' contains 100 entries:
主代码示例
int main(void) { init_platform(SPEED_4_MHz); gpio testpin(GPIO_A, 5); testpin.dir(MODE_OUTPUT); while (1) { testpin.high(); wait(); testpin.low(); wait(); } return 0; }
更新信息
- 向量表位于内存起始位置,SP和MSP初始化成功:
(gdb) p/x *0x00000000 $2 = 0x20000700 (gdb) p/x *0x00000004 $3 = 0x80000f1 (gdb) info registers sp 0x20000700 0x20000700 lr 0xffffffff -1 pc 0x80000f6 0x80000f6 <main()+6> xPSR 0xf1000000 -251658240 msp 0x20000700 0x20000700 psp 0xfffffffc 0xfffffffc
- GPIO类构造函数断点处,
this指针指向0x7,成员变量mypin地址为0xb:
Breakpoint 2, gpio::gpio (this=0x7, port=0 '\000', pin=5 '\005') at gpio.cpp:25 25 mypin = pin; (gdb) p/x &mypin $6 = 0xb
- 将
mypin改为public成员变量后问题无变化,曾怀疑C++必须使用动态分配。
解决方案
核心原因
问题根源是栈初始化不完整:使用-nostdlib时GCC不会提供默认启动代码,而自定义的start_of_memory入口仅设置了MSP,未完成栈空间的规则配置、BSS段清零、DATA段复制等关键步骤,导致编译器错误地将栈上的局部对象(testpin)分配到了不可写的系统向量表地址区域。
C++局部对象默认分配在栈上,和动态分配无关,之前的怀疑不成立。
修复步骤
编写完整启动代码
启动代码需完成以下操作:- 设置MSP栈顶
- 清零BSS段
- 将DATA段从ROM复制到RAM
- 跳转到main,main返回后进入死循环
示例汇编启动代码(
startup.s):.section .text.start_of_memory .global start_of_memory start_of_memory: /* 设置MSP栈顶 */ ldr r0, =_estack mov sp, r0 /* 清零BSS段 */ ldr r0, =_sbss ldr r1, =_ebss mov r2, #0 bss_init_loop: cmp r0, r1 beq bss_init_done str r2, [r0], #4 b bss_init_loop bss_init_done: /* 复制DATA段到RAM */ ldr r0, =_sdata ldr r1, =_edata ldr r2, =_sidata data_init_loop: cmp r0, r1 beq data_init_done ldr r3, [r2], #4 str r3, [r0], #4 b data_init_loop data_init_done: /* 跳转到main函数 */ bl main /* main返回后进入死循环 */ loop: b loop更新链接脚本
添加供启动代码使用的符号,明确段的加载地址:ENTRY(start_of_memory); MEMORY { rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K ram (xrw) : ORIGIN = 0x20000000, LENGTH = 2K } SECTIONS { .text : { /* 启动代码放在.text段最前面 */ KEEP(*(.text.start_of_memory)) *(.text) } > rom .rodata : { *(.rodata) *(.rodata.*) } > rom .data : { _sdata = .; *(.data) *(.data.*) _edata = .; } > ram AT > rom _sidata = LOADADDR(.data); .bss : { _sbss = .; *(.bss) *(.bss.*) *(COMMON) _ebss = .; } > ram /* 定义栈顶地址 */ _estack = ORIGIN(ram) + LENGTH(ram); }编译时包含启动代码
将startup.s加入编译命令,确保链接时被包含进去。
额外说明
- 局部对象
testpin分配在栈上,栈从_estack(0x20000700)向下增长,正确初始化后,对象地址应落在0x20000000~0x20000700的RAM区域内。 - 之前在main中声明普通变量时生成BSS段,只是间接触发了栈的正确分配,并非根本解决方法,核心还是缺失完整的启动代码。
内容的提问来源于stack exchange,提问作者Alessandro Rossetti
相关产品推荐
相关产品推荐

