2

I am not actually sure where the best place to post this is as it is a combination of newlib code, FreeRTOS and a custom implementation. Application is embedded ARM using GCC (arm-eabi...), newlib from standard GCC for ARM installation, FreeRTOS on embedded target (STM32).

The root issue is that calling fprint(stderr, "error\n") fails the first time it is called. Calling printf before solves the issue.

Fails

int err = fprint(stderr, "error\n");   // err = -1, failed!

OK

int err = printf("hi\n");              // err = 3, OK
err = fprint(stderr, "error\n");       // err = 6, OK

I have found the cause of this issue, its a bit long winded to explain, but it comes down to threads reent structure being not fully initialised until the first call of a std out function that implicitly uses stdout, stderr or stdin. e.g. printf, puts etc.

I will try explain what is going on:

When a thread is created, FreeRTOS initiallises the threads reent struct using the default initialiser. This initialises the std file pointers to point to the FILE structures in the reent struct itself.

FreeRTOS tasks.c initialises the tasks reent struct:

#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
    /* Initialise this task's Newlib reent structure. */
    _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif

_REENT_INIT_PTR sets the std file pointers in the reent struct to the file descriptors in the reent struct itself (https://github.com/eblot/newlib/blob/master/newlib/libc/include/sys/reent.h):

#define _REENT_INIT_PTR(var) \
  { memset((var), 0, sizeof(*(var))); \
    (var)->_stdin = &(var)->__sf[0]; \
    (var)->_stdout = &(var)->__sf[1]; \
    (var)->_stderr = &(var)->__sf[2]; \

The file descriptors in the reent struct are by default zeroed (first line memset) so invalid.

Calling printf causes a call to initialise the reent struct. This is done by calling __sinit (via _REENT_SMALL_CHECK_INIT) if the structure has not been initialised before (https://github.com/eblot/newlib/blob/master/newlib/libc/stdio/printf.c).

int
_DEFUN(printf, (fmt),
       const char *fmt _DOTS)
{
  int ret;
  va_list ap;
  struct _reent *ptr = _REENT;

  _REENT_SMALL_CHECK_INIT (ptr);
  va_start (ap, fmt);
  ret = _vfprintf_r (ptr, _stdout_r (ptr), fmt, ap);
  va_end (ap);
  return ret;
}

(https://github.com/eblot/newlib/blob/master/newlib/libc/include/sys/reent.h)

# define _REENT_SMALL_CHECK_INIT(ptr)       \
  do                        \
    {                       \
      if ((ptr) && !(ptr)->__sdidinit)      \
    __sinit (ptr);              \
    }                       \
  while (0)

__sinit does a whole lot of work including initialising the global reeent structure (if it has not been initialised), and copying the global reent structures stdio file pointers to the tasks local stdio file points, thereby making them valid. __sinit definiton is in https://github.com/eblot/newlib/blob/master/newlib/libc/stdio/findfp.c.

Note that fprintf does not call __sinit, so in this case, the stderr file pointer is used uninitialised if fprintf is called before printf (https://github.com/eblot/newlib/blob/master/newlib/libc/stdio/fprintf.c).

int
_DEFUN(fprintf, (fp, fmt),
       FILE *fp _AND
       const char *fmt _DOTS)
{
  int ret;
  va_list ap;

  va_start (ap, fmt);
  ret = _vfprintf_r (_REENT, fp, fmt, ap);
  va_end (ap);
  return ret;
}

So, while I am not claiming that anything is broken, I am not sure how the tasks local reent structure is meant to get initialised before any calls to fprintf. A simple printf at the start of the thread would resolve this. But it would mean I need to do that at the start of every task that might use fprintf. By default assert calls fprintf, so I would need to have a printf at the start of any thread that might use assert (actually how this issue was found).

I am interested to hear any feedback or advice on this one. I already have a workaround for this application (custom assert function), but I would like to understand and learn a bit more about what is going on here.

Ashley Duncan
  • 825
  • 1
  • 8
  • 22
  • 2
    Looks like a bug for me. `_REENT_SMALL_CHECK_INIT(ptr)` should have been there. Or maybe it is expected that on SMALL you will never use `fprintf`. `learn a bit more about what is going on here.` This is a question&answer forum. What questions do you want to ask? What do you want to learn? You pretty much figured it. Ask on newlib mailing list. `__sinit` is an external function, so you can just call it explicitly `__sinit(_REENT)` or something like that. – KamilCuk Jan 12 '23 at 21:40
  • My thoughts were that _REENT_SMALL_CHECK_INIT(ptr) was not called on fprintf as its most likely use would be for output to files that are not stdio ones. But that said, it is the only way to write to stderr. I was intending to suggest on the FreeRTOS forum that they should call __sinit at task creation after initialising reent. But, I was hoping some people smarter than me woulkd give me some better insight before I started suggesting there was issues on newlib or FreeRTOS groups. – Ashley Duncan Jan 12 '23 at 22:30

0 Answers0