10

I am running a bare metal embedded system with an ARM Cortex-M3 (STM32F205). When I try to use snprintf() with float numbers, e.g.:

float f;

f = 1.23;
snprintf(s, 20, "%5.2f", f);

I get garbage into s. The format seems to be honored, i.e. the garbage is a well-formed string with digits, decimal point, and two trailing digits. However, if I repeat the snprintf, the string may change between two calls.

Floating point mathematics seems to work otherwise, and snprintf works with integers, e.g.:

snprintf(s, 20, "%10d", 1234567);

I use the newlib-nano implementation with the -u _printf_float linker switch. The compiler is arm-none-eabi-gcc.

I do have a strong suspicion of memory allocation problems, as integers are printed without any hiccups, but floats act as if they got corrupted in the process. The printf family functions call malloc with floats, not with integers.

The only piece of code not belonging to newlib I am using in this context is my _sbrk(), which is required by malloc.

caddr_t _sbrk(int incr)
{
  extern char _Heap_Begin; // Defined by the linker.
  extern char _Heap_Limit; // Defined by the linker.

  static char* current_heap_end;
  char* current_block_address;

  // first allocation
  if (current_heap_end == 0)
      current_heap_end = &_Heap_Begin;

  current_block_address = current_heap_end;

  // increment and align to 4-octet border
  incr = (incr + 3) & (~3);
  current_heap_end += incr;

  // Overflow?
  if (current_heap_end > &_Heap_Limit)
    {
    errno = ENOMEM;
    current_heap_end = current_block_address;
    return (caddr_t) - 1;
    }

  return (caddr_t)current_block_address;
}

As far as I have been able to track, this should work. It seems that no-one ever calls it with negative increments, but I guess that is due to the design of the newlib malloc. The only slightly odd thing is that the first call to _sbrk has a zero increment. (But this may be just malloc's curiosity about the starting address of the heap.)

The stack should not collide with the heap, as there is around 60 KiB RAM for the two. The linker script may be insane, but at least the heap and stack addresses seem to be correct.

DrV
  • 22,637
  • 7
  • 60
  • 72
  • 2
    Note that those are `double`s, not `float`s. No idea if it matters, you can't pass a `float` to `snprintf()` anyway. – unwind Feb 26 '15 at 15:25
  • 4
    The prototype for [`snprintf()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html) is `int snprintf(char *restrict s, size_t n, const char *restrict format, ...);` You're not calling the function according to the prototype. Did you `#include ` **and compiled with all warnings enabled**? – pmg Feb 26 '15 at 15:27
  • 1
    Sure your true code is using `snprintf()` and not `sprintf()`? – chux - Reinstate Monica Feb 26 '15 at 16:14
  • @unwind: A good point, but AFAIK `printf` is a variadic function automatically promoting floats to doubles. (At least `gcc` does not complain about the formats with its pedantic settings.) With my original example (literal constant `1.23`) the argument is a double anyway, but in the revised example it is a single-precision float. I am not sure about `newlib nano`'s innards, I would guess it rather keeps floats as floats because doubles are quite laborious in embedded systems (but this is just a guess). – DrV Feb 26 '15 at 19:48
  • @chux: Yep. If I have enough performance to use any `printf` in an embedded system, I have enough performance to count the characters to avoid any stupid accidents. IMHO, `sprintf` should not exist; it is just plain dangerous. – DrV Feb 26 '15 at 19:50
  • It is just that you had `snprintf(s, "%.2f", 1.23)` and I was suspecting that instead of not getting/ignoring compiler warnings, there was a code cut/paste error. – chux - Reinstate Monica Feb 26 '15 at 19:53
  • @pmg: Thank you for spotting the typo! Yes, almost all warnings are on, as well as `-pedantic`. I have been forced to drop the `-Werror`, as I use the STM32 HAL, which unfortunately triggers an awful number of padding and conversion warnings. – DrV Feb 26 '15 at 19:57
  • 2
    I'm not too familiar with the implementation details, but the following raises some mild suspicions: `%f` normally involves converting `float` to `double`; the EABI mandates 8-byte alignment for `double`; your `_sbrk()` only enforces 4-byte alignment on what it hands out. How much that matters probably depends on the guts of `printf()` and `malloc()`, but it should be straightforward to experiment with. – Notlikethat Feb 26 '15 at 23:50
  • @Notlikethat: That sounds possible. I did not mention it in my question, but I actually tried an 8-octet aligned heap without any change. However, as the arguments are passed in stack, this might be a stack alignment problem. I will have to check that path as well, thanks! – DrV Feb 27 '15 at 07:54
  • Can you tell us how much extra flash space is occupied by adding floating point support to printf with `-u _printf_float`? I'm trying to compile for a 16kB part (STM32F030F4P6) and the binary seems to be too large (about 20 kB). – Christoph Jun 18 '15 at 08:29

2 Answers2

14

As it may happen that someone else gets bitten by the same bug, I post an answer to my own question. However, it was @Notlikethat 's comment which suggested the correct answer.

This is a lesson of Thou shall not steal. I borrowed the gcc linker script which came with the STMCubeMX code generator. Unfortunately, the script along with the startup file is broken.

The relevant part of the original linker script:

_estack = 0x2000ffff;

and its counterparts in the startup script:

Reset_Handler:  
  ldr   sp, =_estack     /* set stack pointer */
...

g_pfnVectors:
  .word  _estack
  .word  Reset_Handler
...

The first interrupt vector position (at 0) should always point to the startup stack top. When the reset interrupt is reached, it also loads the stack pointer. (As far as I can say, the latter one is unnecessary as the HW anyway reloads the SP from the 0th vector before calling the reset handler.)

The Cortex-M stack pointer should always point to the last item in the stack. At startup there are no items in the stack and thus the pointer should point to the first address above the actual memory, 0x020010000 in this case. With the original linker script the stack pointer is set to 0x0200ffff, which actually results in sp = 0x0200fffc (the hardware forces word-aligned stack). After this the stack is misaligned by 4.

I changed the linker script by removing the constant definition of _estack and replacing it by _stacktop as shown below. The memory definitions were there before. I changed the name just to see where the value is used.

MEMORY
{
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128K
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
}

_stacktop = ORIGIN(RAM) + LENGTH(RAM);

After this the value of _stacktop is 0x20010000, and my numbers float beautifully... The same problem could arise with any external (library) function using double length parameters, as the ARM Cortex ABI states that the stack must be aligned to 8 octets when calling external functions.

DrV
  • 22,637
  • 7
  • 60
  • 72
  • Same problem with STM32 sample on CubeMX for Nucleo F401,You save me a lot of time. Thanks – rom1nux Jun 10 '19 at 20:38
  • I got the same problem with my psoc, which also uses newlib-nano. Is there a chance that you can elaborate on how you fixed it. I haven't worked with adjusting linker scripts before – Typhaon Nov 06 '20 at 00:42
1

snprintf accepts size as second argument. You might want to go through this example http://www.cplusplus.com/reference/cstdio/snprintf/

/* snprintf example */
#include <stdio.h>

int main ()
{
  char buffer [100];
  int cx;

  cx = snprintf ( buffer, 100, "The half of %d is %d", 60, 60/2 );

  snprintf ( buffer+cx, 100-cx, ", and the half of that is %d.", 60/2/2 );

  puts (buffer);

  return 0;
}
theadnangondal
  • 1,546
  • 3
  • 14
  • 28
  • Good answer, and yes, I could have really made that mistake... But in that case `gcc` would have (once again) told me I am stupid. So, this time the problem seems to lie somewhere deeper in `newlib` - or possibly in linker scripts. – DrV Feb 26 '15 at 20:01