5

Temporary Workaround

I worked around it with tiny printf:

Probably newlib printf is just taking too much memory. The PC changes to some strange things after this, which in theory should be the end of a array char[100].

cp = buf + BUF

Later it tries to execute

*--cp = something

and it crashes. Memory bug? There are many things I don't understand. For example, I am not sure whether the linker script is correct, or are the syscall functions. Before I can get further understanding, I have to stick with the tiny printf.


Original

I have an STM32F103RB board (Nucleo), and I just got USART1 working. Also, I tested Newlib functions, and puts() works as expected. However, when I try to use printf() for integers, like this:

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

If test < 10 the program works, but if test >= 10 a hard fault is generated. After some GDB debugging I found that it generates from 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

I cannot think of any solutions. This is my _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);
  }
}

My minimum stack size is configured to be 1024 bytes. I tried to increase until 10k, but it still generates this hard fault. I have no idea how to debug this or find the problem. How can I fix it?

I noticed one thing. The failure comes at vfprintf.c:1601, where I examined the pointer cp in GDB:

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

I don't know why the address 0x20004f57 points to a non-existent address.

Also, p _stackend gives

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

Which apparently does not exist

It seems that when _VFPRINTF_R() is executed, while the goto number is executed, the pointer cp is corrupted:

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)

The Linker Script:

/*
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) }
}

I didn't understand this linker script very well, and I modified the one that comes with "Discovering the STM32 Microcontroller".

artless noise
  • 21,212
  • 6
  • 68
  • 105
Carl Dong
  • 1,299
  • 15
  • 26
  • Can we see your linker script please? If you've got the sizes all declared at the top then that's the most important fragment. – Andy Brown Oct 14 '14 at 09:41
  • Linker script looks OK. Is `_stackend` corrupted before the printf call or afterwards? You can see what it should be by looking at the calculation in the linker script. Also, what's `_end` set to? This is also calculated by the linker script based on your program characteristics. – Andy Brown Oct 14 '14 at 12:37
  • Actually I don't quite understand that script, so I don't know what is changed during execution. Will `_stackend` change? I think they are fixed, right? – Carl Dong Oct 14 '14 at 16:44
  • Yes they're fixed, but the point is you're seeing them corrupted. They should be in SRAM space (0x2000xxxx). It would be useful to know if they're set incorrectly at entry to `main()` or if they become incorrect after the `printf` call. – Andy Brown Oct 15 '14 at 10:14
  • So do I just use debugger to display that and try to find out? I will try that. Also, is the `_stackend` a variable? Or a symbol located in memory? Because if they are fixed then why they would be changed? – Carl Dong Oct 15 '14 at 11:32
  • No, on startup `_stackend` gives `-90 '\246'`, after crash it is the same value. – Carl Dong Oct 15 '14 at 15:22
  • Have you verified that this isn't because your string goes from 8 to 9 characters long? Most uart/usart send buffers I've seen on arm chips are 8 bytes long. Can you printf a string of 12 characters? – escrafford Oct 15 '14 at 18:25
  • @escrafford I will try as soon as I get back. However, I have printed "Hello World" using `puts()` before – Carl Dong Oct 15 '14 at 18:47
  • Actually that might be the problem. `printf` cannot take long strings, but `puts` can. I don't know why. – Carl Dong Oct 15 '14 at 20:08
  • is it because it now has to do a divide? where before it didnt? – old_timer Oct 15 '14 at 20:22
  • The problem lies somewhere in my toolchain... – Carl Dong Oct 15 '14 at 22:16

3 Answers3

2

OK, after all sorts of testing, I found that it was the toolchain I used. I built the toolchain myself using GCC 4.8, and according to http://forum.chibios.org/phpbb/viewtopic.php?f=16&t=2241 , GCC 4.8 has some problems. I switched back to CodeSourcery toolchain and it works again.

Carl Dong
  • 1,299
  • 15
  • 26
  • Have a +1 for being persistent and fixing it yourself. Well done! – Andy Brown Oct 16 '14 at 08:31
  • 1
    The toolchain may make the problem go away because memory layout has changed slightly. Tread carefully - this smells of stack overflow (how appropriate!!) or writing past the end of a buffer. Possibly mitigations are to put the stack at the bottom of RAM so that it will not clobber the other regions and will hopefully throw an exception when it tries to write below 0x20000000. – SilverCode Oct 22 '14 at 17:47
  • @SilverCode The problem is, I use the same LD script for both toolchains... I think the stack is configured at the end of RAM. – Carl Dong Oct 22 '14 at 18:44
2

I had the same problem (also arm-none-eabi-gcc 4.8), after some debugging I realised that _vfprintf_f used 64 bit division which was causing Hardfaults. And it turned out that compiler was linking against wrong libgcc because execution state (thumb, ARM) was specified during compiling, but not linking.

After specifying -mcpu=cortex-m3 -mthumb or -mcpu=cortex-m4 -mthumb depending on your processor, for both compiler and linker the problem went away.

The reason for successfully printing digits less than 10 is that _vfprintf_r function has a special case for single digit numbers when converting to decimal format which does not use 64bit division and therefore no HardFault.

1

Building with ARM gcc toolchain on an ATMEL E70, I solved the issue it by adding -Dprintf=iprintf to the compiler (and linker) arguments. Atmel examples seemed to use this and it worked for me. I had tried to augment the stack size (to 32k) and made sure -mcpu=cortex-m7 -mthumb was passed to both the compiler and the linker but the issue remained.

ben
  • 11
  • 2