2
\$\begingroup\$

Are there common causes for sprintf to suddenly stop functioning as expected in an embedded environment? Microcontroller: PIC24FJ128GB204

char temp[4];
sprintf(temp, "%d,", (uint8_t)98);

The code above ideally prints "98\0" to temp array. However, once the problem starts, I see that temp is written as "0,\0\0".

My microcontroller has uart and is receiving from another microcontroller (micro2). The micro2 sends a large number of characters whenever its reset pin is toggled (really, just a string that tells the configurations, firmware version etc). The microcontroller (main) receives these string in uart via circular buffer.

I am able to replicate the problem by repeatedly resetting micro2 (via pushbutton). Once the problem occurs, I see that sprintf no longer behaves normally.

The receive buffer for the uart receive (from micro2) is handled in ISR.

void uart_rx_ring_write (void)
{
    unsigned char dummy;
    if(uart3_generic_rx_byte_cnt < UART3_BUFFER_SIZE)
      {
          uart3_generic_rx_buffer[uart3_generic_rx_tail] = U3RXREG;
          uart3_generic_rx_tail++;
          if(uart3_generic_rx_tail >= UART3_BUFFER_SIZE)
            {
                uart3_generic_rx_tail = 0;
            }
          uart3_generic_rx_byte_cnt++;
      }
    else if (uart3_generic_rx_byte_cnt > UART3_BUFFER_SIZE)
    {
        uart3_generic_rx_byte_cnt = UART3_BUFFER_SIZE;
        dummy = U3RXREG;
    }
    else 
      {
          dummy = U3RXREG; // new data will be disregarded if buffer is full
      }
    
    uart3_generic_rx_timer = 0;
}

void __attribute__((interrupt,auto_psv)) _U3RXInterrupt (void)
{
    IFS5bits.U3RXIF = 0;

    uart_rx_ring_write ();

    interrupts_flags.uartrx_tmr_to = false;
    interrupts_flags.uartrx_tmr_en = true;
}

The following shows the destination array's (actual application code) sample data for the case when problem has occurred. The value of the array was checked via PICKIT 5 programmer/debugger and the memory view in MPLAB X IDE.

GPIO 0000000000 2000-00-00 00:00:00.000\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0

The date format is YYYY-MM-DD so 00-00 should be 11-13 (during normal operations) to correspond to November 13.

\$\endgroup\$
13
  • \$\begingroup\$ Are you always printing the constant value 98, or are you actually printing a single-byte variable? \$\endgroup\$
    – brhans
    Commented Nov 13 at 2:23
  • \$\begingroup\$ In the actual code, strlen of a global array was being printed (via sprintf) onto destination array. However, I I simply added the code in my post to the code during debugging and found that even this simple sprintf line (98 is always printed) stops working as expected when the problem occurs. \$\endgroup\$
    – Shadow
    Commented Nov 13 at 2:27
  • \$\begingroup\$ What's your programming environment? sprintf() and friends are not in general thread-safe, and if you're calling it from multiple threads or from an ISR, it will eventually fail. \$\endgroup\$
    – Dave Tweed
    Commented Nov 13 at 2:30
  • \$\begingroup\$ I am using MPLAB X IDE (v 6.20). My compiler is XC16 (v1.50). \$\endgroup\$
    – Shadow
    Commented Nov 13 at 2:34
  • 1
    \$\begingroup\$ Are you calling sprintf from the interrupt or from main code? Many implementations use a static buffer which can be overwritten if string formatting functions are called from different places at the same time \$\endgroup\$
    – Kieran
    Commented Nov 13 at 13:39

1 Answer 1

1
\$\begingroup\$
char temp[4];
sprintf(temp, "%d,", (uint8_t)98);

comment:

In the actual code, strlen of a global array was being printed (via sprintf) onto destination array.

OK, I hope you realize that this is an invitation for a buffer overflow. As soon as what you print exceeds 99, you are hosed, because the terminating null byte in temp doesn't fit any more and gets written to whatever is in memory behind it; it might be overwritten by something that is not a null byte by whatever uses the memory behind the temp buffer, and the next operation on that string reads off into memory, only to be stopped when out of pure luck the next zero byte happens.

Remember that the string "100," has five bytes: '1', '0', '0', ',', '\0'

Use snprintf(temp, sizeof temp, "%d,", (uint8_t)98) to be safe, or better yet, don't use %d (signed int) when you mean to print an unsigned number, and by all means, use enough bytes to format a single number; you're using sprintf to convert a single number to a string and add a ,, on a tiny microcontroller, so don't come at me saving individual bytes on the stack. Your code is not designed to do that; even if the PIC implementation of sprintf is extraordinarily memory-efficient, if the largest number you can print is 99 and the smallest 0, then add a runtime check that the number is in that range, and use %u to pass an unsigned int, instead of casting down to an unsigned char, which will lose data if the number is > 255, and then have that propagated back to a signed int (because that's what %d dictates happens).

In this particular example: It's probably the very same line of code that overwrites the memory that gets passed as numeric argument to sprintf with the trailing \0 of anything 100,\0 (or larger). It also might be anything else – you writing to memory outside of your buffer is the classical example of undefined behaviour, so anything else might effect that "'0'" gets written there, no guarantees whatsoever.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.