0

I'm working on the AVR platform. avr-libc does not provide asprintf(). A library I'm attempting to bring into my project requires it. Helpfully, that same library includes an implementation (below). Unfortunately, it is providing odd results. Specifically, I've determined that the return code of vsnprintf() is never correct; rather than random results, I seem to always see the same progression of incorrect values (5, 1, etc.) upon successive calls.

The call to this function is: asprintf(&str, "%s[%d]", name, val);. str is a char* on the stack within the calling function. name is a simple short text descriptor that I've verified is not null and not zero length. val is a simple index that is incremented within the loop that calls asprintf(). The resulting string this call builds should be 7 characters long (not including null terminator). asprintf() is called throughout the library. My use of the containing library only ever executes it in this single loop (twice). The library does its job as expected if I remove this call and replace the results with dummy values. Execution seemingly crashes at assigning the buffer to the dereferenced ret pointer within the implementation of asprintf().

Despite lots of experiments with buffers, pointer dereferencing, and managing var args I cannot get this function to operate properly. However, cross-compiled on OS X it works just fine.

I know that the behavior of vsnprintf() is not necessarily identical across all platforms. That said, as far as I can tell this usage should work as expected.

Any ideas? Could it be something outside the function itself? Some kind of stdio initialization or linker library option that is causing the trouble?

int asprintf(char **ret, const char *fmt, ...) {
  va_list ap1;
  va_list ap2;
  int count;

  va_start(ap1, fmt);
  va_copy(ap2, ap1);
  count = vsnprintf(NULL, 0, fmt, ap1);
  va_end(ap1);

  if(count > 0) {
    char* buffer;

    if (!(buffer = (char*)malloc(count+1))) {
      return -1;
    }
    count = vsnprintf(buffer, count+1, fmt, ap2);
    *ret = buffer;
  }
  va_end(ap2);
  return count;
}
  • `progression of incorrect values (5, 1, etc.) upon successive calls` What are the inputs in those succesive calls? It helped if you showed the actual `asprintf` calls. – dxiv Jul 21 '16 at 22:34
  • Good point. Question updated to describe how this function is called. – Michael Karlesky Jul 22 '16 at 01:13
  • 2
    Is `asprintf` declared (with the correct prototype) in a header included by both modules that implement, respectively call, it? – dxiv Jul 22 '16 at 01:26
  • @dxiv I had to dig a little. No. I could not find the `asprintf()` declared with a prototype in _any_ header. The means for including this function in the build on platforms that lacked it did not also extend to a prototype in the appropriate header file. When I declared the prototype where it should be, lo and behold, the bug disappeared. Can you please explain what triggered your thinking to identify this as the possible issue? Without a proper prototype was the code corrupting memory in its handling of the var args? – Michael Karlesky Jul 22 '16 at 02:18
  • Rather corrupting the stack, most likely. – dxiv Jul 22 '16 at 02:44
  • Yes. That's what I meant. Thanks for the catch on that. I would not have thought to look. – Michael Karlesky Jul 22 '16 at 02:45
  • You shouldn't use dynamic allocation in small Harvard embedded devices (and you often can't anyway, because there are [no MMUs in those MCUs](http://www.atmel.com/devices/atmega2560.aspx?tab=parameters)). And [don't cast the result of `malloc` in C](http://stackoverflow.com/q/605845/995714) – phuclv Jul 22 '16 at 03:30
  • @lưu-vĩnh-phúc We carefully use dynamic allocation throughout this project and have a goodly sized heap to support that. We avoid it as much as possible because of the pitfalls of memory leaks, but it serves our purposes and is well tested. That said, the use of dynamic allocation is really not relevant to the question. I didn't write this `asprintf()` implementation; a library in use requires it. Also, this C code is built by a C++ compiler so casting the result of `malloc()` is appropriate. – Michael Karlesky Jul 22 '16 at 23:47

4 Answers4

1

Per previous comments, it turns out that asprintf was called without having a visible prototype in scope. Adding the necessary declaration prior to the call fixed the issue.

The underlying reason is that C requires variadic functions to have the proper prototype declared before being used.

This is mentioned for example at Are prototypes required for all functions in C89, C90 or C99?.

any call to a variadic function (like printf or scanf) must have a visible prototype

Some insight as to "why" is given at comp.lang.c FAQ list - Question 15.1.

Q: I heard that you have to #include <stdio.h> before calling printf. Why?

A: So that a proper prototype for printf will be in scope.

A compiler may use a different calling sequence for functions which accept variable-length argument lists. (It might do so if calls using variable-length argument lists were less efficient than those using fixed-length.) Therefore, a prototype (indicating, using the ellipsis notation ``...'', that the argument list is of variable length) must be in scope whenever a varargs function is called, so that the compiler knows to use the varargs calling mechanism.

Community
  • 1
  • 1
dxiv
  • 16,984
  • 2
  • 27
  • 49
0

Did you check the definition of the

va_start

macro?

Probably this could cause the problem, as the address of parametr could point somewhere "behind" the format pointer if it is not properly defined. If it is OK, than as you say, the problem could be in vsnprintf implementation than.

Dom
  • 532
  • 1
  • 9
  • 23
  • The authors of the library in which `asprintf()` resides have used var args elsewhere in the code necessitating use of `va_list`, `va_start`, etc. These other functions appear to work as expected. I've been able to narrow down the crash of the library calls to the contents of `asprintf()`. Or, rather, the trouble seems to show up here. It could be a confluence of external factors that show up in some odd way in this function. – Michael Karlesky Jul 22 '16 at 01:16
0

There's no standard initialization needed for stdio. If your implementation needs initialization, that information would hopefully be in the documentation.

If your vnsprintf is broken, you can use vsprintf. Here's a version cribbed from FreeTDS:

int
vasprintf(char **ret, const char *fmt, va_list ap)
{
    FILE *fp;
    if ((fp = fopen(_PATH_DEVNULL, "w")) == NULL)
            return -1;
    if ((fp == NULL) && ((fp = fopen(_PATH_DEVNULL, "w")) == NULL))
            return -1;

    len = vfprintf(fp, fmt, ap);

    if (fclose(fp) != 0)
            return -1;

    if (len < 0)
            return len;

    if ((buf = malloc(len + 1)) == NULL) {
            errno = ENOMEM;
            return -1;
    }
    if (vsprintf(buf, fmt, ap) != len)
            return -1;
    *ret = buf;
    return len;
}

Depending on your needs, you might be able to re-use the file descriptor.

James K. Lowden
  • 7,574
  • 1
  • 16
  • 31
  • In avr-libc the implementation of `vnsprintf()` is mostly a wrapper around `vsprintf()`. When I tried modifying `asprintf()` to call `vsprintf()` directly I see the same behavior. – Michael Karlesky Jul 22 '16 at 01:18
  • In that case, I would file a bug report. If you're right, the problem is deep and fundamental, and the project will want to fix it. Turn your minimal example into a program that prints `count` *N* times, where *N* comes from the command line. That will either illustrate your mistake (if any) or demonstrate the problem amply. – James K. Lowden Jul 22 '16 at 14:36
  • The issue turned out to be a lack of a function prototype for this implementation of `asprintf()` meant to cover platforms lacking it. Another commenter suggested it as a hunch and was correct. Handling of the var args in the parameter list without a proper prototype was likely corrupting the stack. – Michael Karlesky Jul 22 '16 at 23:36
0

I ran into this just today, and as dxiv answered, the key is the visible prototype. But I'd like to expand on this: it's not just the variadic arguments that won't work properly. In my case, the project built and the function was called, but none of the arguments worked properly. Here is a very simple example to demonstrate. (The function uprintf() is a custom function for printing out via UART.)

    void log_console(  const char  * fmtstring,... )
{
    uprintf("Start of  log_console\n");
    uprintf(fmtstring);
}

Call from main() with prototype hidden:

//void log_console(  char const * fmtstring,... );
log_console("Test message to console  =======\n");
uprintf("After test message to console\n");

In this case, the output is:

Start of  log_console
After test message to console

This shows the function is being called, but fmtstring is not properly defined. But with the prototype visible:

void log_console(  char const * fmtstring,... );
log_console("Test message to console  =======\n");
uprintf("After test message to console\n");

the function now has access to the incoming argument and the strings written to the UART are as expected:

Start of  log_console
Test message to console  =======
After test message to console

Once the prototype was in place, this simple demo example worked, and so did my full actual function, which made use of the (...) arguments.