Ashley Duncan
Ashley Duncan

Reputation: 1029

Stdio initialisation - embedded, newlib, freeRTOS

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.

Upvotes: 3

Views: 676

Answers (0)

Related Questions