STM32 尝试打印数字 >= 10 时出现硬故障

Posted

技术标签:

【中文标题】STM32 尝试打印数字 >= 10 时出现硬故障【英文标题】:STM32 hard faults when trying to printf numbers >= 10 【发布时间】:2014-12-07 13:16:55 【问题描述】:

临时解决方法

我用小 printf 解决了这个问题:

http://www.sparetimelabs.com/tinyprintf/tinyprintf.php https://github.com/cjlano/tinyprintf

可能 newlib printf 只是占用了太多内存。 PC在这之后发生了一些奇怪的事情,理论上应该是数组char[100]的结尾。

cp = buf + BUF

稍后它会尝试执行

*--cp = something

然后它崩溃了。内存错误?有很多事情我不明白。例如,我不确定链接描述文件是否正确,或者是否是系统调用函数。在进一步了解之前,我必须坚持使用微小的 printf。


原创

我有一块 STM32F103RB 板 (Nucleo),我刚刚让 USART1 工作。另外,我测试了 Newlib 函数,puts() 按预期工作。但是,当我尝试将 printf() 用于整数时,如下所示:

printf("ADC: %d\r\n", test);

如果test < 10 程序可以工作,但如果test >= 10 会产生一个硬故障。经过一些 GDB 调试后,我发现它是从 vfprintf 生成的:

#0  HardFault_Handler () at main.c:136
#1  <signal handler called>
#2  0x08005af4 in _vfprintf_r (data=<optimized out>, fp=0x20000384 <impure_data+852>, fmt0=fmt0@entry=0x20004f57 " \254\264", ap=...,
    ap@entry=...) at ../../../../../../newlib/libc/stdio/vfprintf.c:1601
#3  0x08004fd0 in printf (fmt=0x800b4ac "ADC: %d\n\r") at ../../../../../../newlib/libc/stdio/printf.c:52
#4  0x080004e8 in main () at main.c:168

我想不出任何解决方案。这是我的_sbrk()

caddr_t _sbrk(int nbytes) 
  static caddr_t heap_ptr = NULL;
  caddr_t base;

  if (heap_ptr == NULL)
    heap_ptr = (caddr_t) &_end;
  

  if ((caddr_t) _stackend > heap_ptr + nbytes) 
    base = heap_ptr;
    heap_ptr += nbytes;
    return (base);
  
  else 
    errno = ENOMEM;
    return ((caddr_t) -1);
  

我的最小堆栈大小配置为 1024 字节。我试图增加到 10k,但它仍然会产生这个硬故障。我不知道如何调试或找到问题。我该如何解决?

我注意到一件事。失败出现在vfprintf.c:1601,在那里我检查了 GDB 中的指针cp

(gdb) x 0x20004f57
0x20004f57:     0x00b4ac20
(gdb) x 0x00b4ac20
0xb4ac20:       0x20004f80
(gdb) x 0x20004f58
0x20004f58:     0x0800b4ac
(gdb)

我不知道为什么地址0x20004f57指向一个不存在的地址。

另外,p _stackend 给了

$6 = (caddr_t) 0xb33ea8a6 <error: Cannot access memory at address 0xb33ea8a6>

显然不存在

似乎在执行_VFPRINTF_R() 时,在执行goto number 时,指针cp 已损坏:

3: cp = <optimized out>
2: fmt = 0x800b453 "\n\r"
(gdb) n
1057                            base = DEC;
3: cp = <optimized out>
2: fmt = 0x800b453 "\n\r"
(gdb)
1400    number:                 if ((dprec = prec) >= 0)
3: cp = <optimized out>
2: fmt = 0x800b453 "\n\r"
(gdb)
1409                            if (_uquad != 0 || prec != 0) 
3: cp = 0x20004b58 "\245ۊ\256\211W\325\326\377Y\352\224\t x\207\220\230&-\031\032~\337\032\371\024\254\"(\214\354\363\b\241\365\022\035\037\252\026\243\206\235P\005OZn\245c\n\352\244E^ά\246\301Ӕ\271L\264"
2: fmt = 0x800b453 "\n\r"
(gdb)

链接脚本:

/*
Linker script for STM32F10x_128K_20K

modified from

http://www.codesourcery.com/archives/arm-gnu/msg02972.html
http://communities.mentor.com/community/cs/archives/arm-gnu/msg02972.html
*/

/*
There will be a link error if there is not this amount of RAM free at the
end.
*/

/* _Minimum_Stack_Size = 256; */
_Minimum_Stack_Size = 1024;

ENTRY(Reset_Handler)


/* Memory Spaces Definitions */

MEMORY

  RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 20K
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K


__ram_start__ = ORIGIN(RAM);
__ram_size__  = LENGTH(RAM);
__ram_end__   = __ram_start__ + __ram_size__;
_estack = __ram_end__;
/* highest address of the user mode stack */



PROVIDE ( _Stack_Limit = _estack - _Minimum_Stack_Size );

/* Sections Definitions */

SECTIONS

    .text :
    
        KEEP(*(.isr_vector))            /* Startup code */
        *(.text)                   /* code */
        *(.text.*)                 /* remaining code */
        *(.rodata)                 /* read-only data (constants) */
        *(.rodata.*)
        *(.glue_7)
        *(.glue_7t)
        *(.vfp11_veneer)
        *(.v4_bx)
        *(.ARM.extab* .gnu.linkonce.armextab.*)
     >FLASH

    /* for exception handling/unwind - some Newlib functions (in
    common with C++ and STDC++) use this. */
    .ARM.extab :
    
        *(.ARM.extab* .gnu.linkonce.armextab.*)

     > FLASH

     __exidx_start = .;
    .ARM.exidx :
    
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
     > FLASH
        __exidx_end = .;

    . = ALIGN(4);
     _etext = .;
    /* This is used by the startup in order to initialize the .data secion
*/
    _sidata = _etext;

    /* This is the initialized data section
    The program executes knowing that the data is in the RAM
    but the loader puts the initial values in the FLASH (inidata).
    It is one task of the startup to copy the initial values from FLASH to
RAM. */
    .data  : AT ( _sidata )
    
        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .data
secion */
        _sdata = . ;

        *(.data)
        *(.data.*)

        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .data
secion */
        _edata = . ;
     >RAM


    /* This is the uninitialized data section */
    .bss :
    
        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .bss
secion */
        _sbss = .;
    __bss_start__ = _sbss;
        *(.bss)
        *(.bss.*)
        *(COMMON)

        . = ALIGN(4);
        /* This is used by the startup in order to initialize the .bss
secion */
        _ebss = . ;
    __bss_end__ = _ebss;
     >RAM

    PROVIDE ( end = _ebss );
    PROVIDE ( _end = _ebss );
    PROVIDE ( _exit = _ebss );
    PROVIDE (_stackend = ORIGIN(RAM) + LENGTH(RAM) - _Minimum_Stack_Size);

    /* This is the user stack section
    This is just to check that there is enough RAM left for the User mode
stack
    It should generate an error if it's full.
     */
    ._usrstack :
    
        . = ALIGN(4);
        _susrstack = . ;

        . = . + _Minimum_Stack_Size ;

        . = ALIGN(4);
        _eusrstack = . ;
     >RAM



    /* after that it's only debugging information. */

    /* remove the debugging information from the standard libraries */
/*
    DISCARD :
    
     libc.a ( * )
     libm.a ( * )
     libgcc.a ( * )
     
*/

    /* Stabs debugging sections.  */
    .stab          0 :  *(.stab) 
    .stabstr       0 :  *(.stabstr) 
    .stab.excl     0 :  *(.stab.excl) 
    .stab.exclstr  0 :  *(.stab.exclstr) 
    .stab.index    0 :  *(.stab.index) 
    .stab.indexstr 0 :  *(.stab.indexstr) 
    .comment       0 :  *(.comment) 
    /* DWARF debug sections.
       Symbols in the DWARF debugging sections are relative to the beginning
       of the section so we begin them at 0.  */
    /* DWARF 1 */
    .debug          0 :  *(.debug) 
    .line           0 :  *(.line) 
    /* GNU DWARF 1 extensions */
    .debug_srcinfo  0 :  *(.debug_srcinfo) 
    .debug_sfnames  0 :  *(.debug_sfnames) 
    /* DWARF 1.1 and DWARF 2 */
    .debug_aranges  0 :  *(.debug_aranges) 
    .debug_pubnames 0 :  *(.debug_pubnames) 
    /* DWARF 2 */
    .debug_info     0 :  *(.debug_info .gnu.linkonce.wi.*) 
    .debug_abbrev   0 :  *(.debug_abbrev) 
    .debug_line     0 :  *(.debug_line) 
    .debug_frame    0 :  *(.debug_frame) 
    .debug_str      0 :  *(.debug_str) 
    .debug_loc      0 :  *(.debug_loc) 
    .debug_macinfo  0 :  *(.debug_macinfo) 
    /* SGI/MIPS DWARF 2 extensions */
    .debug_weaknames 0 :  *(.debug_weaknames) 
    .debug_funcnames 0 :  *(.debug_funcnames) 
    .debug_typenames 0 :  *(.debug_typenames) 
    .debug_varnames  0 :  *(.debug_varnames) 

我对这个linker script不是很了解,修改了“发现STM32单片机”自带的那个。

【问题讨论】:

我们可以看看你的链接器脚本吗?如果您在顶部声明了所有尺寸,那么这是最重要的片段。 链接器脚本看起来不错。 _stackend 是在 printf 调用之前还是之后损坏?您可以通过查看链接描述文件中的计算来了解它应该是什么。另外,_end 设置为什么?这也是链接描述文件根据您的程序特征计算出来的。 其实我不是很懂那个脚本,所以不知道在执行过程中有什么变化。 _stackend 会改变吗?我认为它们是固定的,对吗? 是的,它们已修复,但关键是您看到它们已损坏。它们应该在 SRAM 空间 (0x2000xxxx) 中。了解它们是否在进入main() 时设置不正确或者在调用printf 后它们是否变得不正确会很有用。 那么我是否只是使用调试器来显示并尝试找出答案?我会试试的。另外,_stackend 是变量吗?还是位于内存中的符号?因为如果它们是固定的,那么为什么它们会被更改? 【参考方案1】:

好的,经过各种测试,我发现它是我使用的工具链。我使用 GCC 4.8 自己构建了工具链,根据 http://forum.chibios.org/phpbb/viewtopic.php?f=16&t=2241 ,GCC 4.8 存在一些问题。我切换回 CodeSourcery 工具链,它又可以工作了。

【讨论】:

对坚持不懈并自己修复它有一个 +1。干得好! 工具链可能会解决问题,因为内存布局略有变化。小心行事 - 这闻起来有堆栈溢出的味道(多么合适!!)或写入超过缓冲区的末尾。可能的缓解措施是将堆栈放在 RAM 的底部,这样它就不会破坏其他区域,并希望在尝试写入 0x20000000 以下时抛出异常。 @SilverCode 问题是,我对两个工具链使用相同的 LD 脚本...我认为堆栈配置在 RAM 的末尾。【参考方案2】:

我遇到了同样的问题(也是 arm-none-eabi-gcc 4.8),经过一些调试后,我意识到 _vfprintf_f 使用了 64 位除法,这导致了硬故障。结果发现编译器链接错误的 libgcc,因为在编译期间指定了执行状态(thumb,ARM),但没有链接。

根据您的处理器指定-mcpu=cortex-m3 -mthumb-mcpu=cortex-m4 -mthumb 后,对于编译器 链接器,问题就消失了。

成功打印小于 10 的数字的原因是 _vfprintf_r 函数在转换为不使用 64 位除法的十进制格式时对单个数字有特殊情况,因此没有 HardFault。

【讨论】:

【参考方案3】:

在 ATMEL E70 上使用 ARM gcc 工具链构建,我通过在编译器(和链接器)参数中添加 -Dprintf=iprintf 解决了这个问题。 爱特梅尔的例子似乎使用了这个,它对我有用。我曾尝试增加堆栈大小(至 32k)并确保 -mcpu=cortex-m7 -mthumb 已传递给编译器和链接器,但问题仍然存在。

【讨论】:

以上是关于STM32 尝试打印数字 >= 10 时出现硬故障的主要内容,如果未能解决你的问题,请参考以下文章

将全局声明的缓冲区写入 FLASH 时出现 STM32 Hardfault 异常

在 STM32 上调用 memset 时出现硬故障异常

STM32初学Keil4编译时出现 Error:Failed to execute 'BIN40/Armcc'

STM32F030 启动时出现硬故障,__libc_init_array

stm32编译时出现 error: #35: #error directive: "Please select first the target STM32F10x device used

keil 4.22 MDK 建工程时出现的重定义错误