ESP8266 sprintf causes exception

I'm compiling some arduino code for ESP8266 (Wemos)

This code works fine

char url[256],station[13],bssid[13];

sprintf(url, "/update/secret_key?secs=%lu&in=%lu:", millis()/1000,
            clients_known.last_heard );
strcat(url,format_mac_address(station,clients_known.station));
strcat(url,":");
strcat(url,format_mac_address(bssid,clients_known.bssid));

char *format_mac_address( char *result ,uint8_t ssid[] )
{

  result[0]='\0';
  for (int i = 0; i < ETH_MAC_LEN; i++)
    sprintf(&result[strlen(result)], "%02x", ssid[i]);
  return result;  
}

But if I try to combine it into a single sprintf statement it causes an "Exception (28)" error with stack dump and a hard reset every time its called.

sprintf(url, "/update/secret_key?secs=%lu&in=%lu:%s:%s", millis()/1000,
            clients_known.last_heard,
            format_mac_address(station,clients_known.station),
            format_mac_address(bssid,clients_known.bssid)
 );

Appreciate if anyone with more coding skill than I could tell me if I made an obvious mistake or if this is a bug in the ESP8266 sprintf() function.

rw950431:
Appreciate if anyone with more coding skill than I could tell me if I made an obvious mistake or if this is a bug in the ESP8266 sprintf() function.

try the safer snprintf() it will prevent writing beyond the end of the char array. Perhaps you are overflowing the url array?

Tried

snprintf(url,sizeof(url), "/update/secret_key?secs=%lu&ap=%lu:%s:%s", 
          millis()/1000,clients_known.last_heard,
          format_mac_address(station,clients_known.bssid),
          format_mac_address(station,clients_known.station)
          );

With exactly the same result (the url string is only about 70 characters long and I allowed 256 so was pretty confident that wasnt an issue)

However changing the type of last_heard from uint64_t to uint32_t with corresponding modifications to the format string makes it work OK

sprintf(url, "/update/secret_key?secs=%u&in=%u:%s:%s", millis()/1000,
            clients_known.last_heard,
            format_mac_address(station,clients_known.station),
            format_mac_address(bssid,clients_known.bssid)
          );

So I guess there is a subtle bug in the implementation of sprintf() involving long int values.

This is OK (same length with fixed strings instead of variables, 64 bit value for last_heard)

sprintf(url, "/update/secret_key?secs=%lu&in=%lu:ff1b77ffddff:ff1b7ffaddd7", millis()/1000,
            clients_known.last_heard
          );

Problem seems to only occurs when mixing long ints with strings in the format.

Exact text of error message, if its of any help to someone in the future

Exception (28):
epc1=0x4020aa1c epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

ctx: cont 
sp: 3fff4670 end: 3fff4bb0 offset: 01a0
sprintf(url, "/update/secret_key?secs=%lu&in=%lu:", millis()/1000,

clients_known.last_heard );



changing the type of last_heard from uint64_t to uint32_t with 
corresponding modifications to the format string makes it work OK


sprintf(url, "/update/secret_key?secs=%u&in=%u:%s:%s", millis()/1000,
            clients_known.last_heard,

I strongly suspect that %lu is NOT the correct format specifier for a 64bit integer. On most processors, %lu is a "long", which is still 32bits, and you'd want %llu for 64bits (if the printf implementation supports 64bit ints at all.) (I'm not sure about the ESP8266 chip itself, though.)
If I'm right, in your original code, printf fetches 32bits of the 64bit int for printing last_heard, which leaves it out-of-sync with the remaining arguments. It probably fetches the next 32bits of last_heard and tries to use it as the string pointer, causing the exception.

printf() is normally unable to type-check its arguments, which is one of the reasons that "computer scientists" really dislike it...

Thanks! Thats it
Changing the type to uint32_t ( which is unsigned long) works perfectly.

snprintf(url, sizeof(url), "/update/secret_key?secs=%lu&in=%lu:%s:%s", 
            millis()/1000, clients_known.last_heard,
            format_mac_address(station,clients_known.station),
            format_mac_address(bssid,clients_known.bssid)
            );

According to the long reference page unsigned long is 32 bit and therefore equivalent to uint32_t. Since I'm using that field to store a value from millis() I dont even need 64 bits! So easy to shoot oneself in the foot with one's extreme cleverness :confused:

Thanks again.

BTW, gcc now checks for mismatched printf arguments, and WOULD have printed a warning, if the Arduino environment didn't turn OFF all the warnings!

./xtensa-lx106-elf-gcc -Wall -I ../../1.20.0-26-gb404fb9-2/xtensa-lx106-elf/include/ /tmp/bar.c 

/tmp/bar.c: In function 'main':
/tmp/bar.c:9:5: warning: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'uint64_t' [-Wformat=]
     printf("32bits %u 64bits %lu string %s", my32, my64, s);
     ^
/tmp/bar.c:9:5: warning: format '%lu' expects argument of type 'long unsigned int', but argument 3 has type 'uint64_t' [-Wformat=]