Is there a way to bypass va_copy when using vsnprintf?

Viewed 456

I'm introducing a 3rd party protocol stack to on an old embeded platform where all va_* stuff are implemented except va_copy. The problem I face is, in the 3rd party stack, vsnprintf() is used:

int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
  va_list ap_copy;
  int len;
  /* first call*/
  va_copy(ap_copy, ap);
  len = vsnprintf(*buf, buf_size, fmt, ap_copy);
  va_end(ap_copy);
  if(len >= buf_size)
  {
      /* 2nd call*/
      va_copy(ap_copy, ap);
      len = vsnprintf(*buf, len + 1, fmt, ap_copy);
      va_end(ap_copy);
  }  
}  

Luckily the 3rd party stack provedes its own vsnprintf function(call it new_vsnprintf), but without va_copy, only the first call works, i.e, when the len is small than buf_size. Below is the way I call it:

#define vsnprintf new_vsnprintf
int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
  //va_list ap_copy;
  int len;
  /* first call*/
  va_start(ap, fmt);
  len = vsnprintf(*buf, buf_size, fmt, ap);
  va_end(ap);
  if(len >= buf_size)
  {
      /* 2nd call*/
      va_start(ap, fmt);
      len = vsnprintf(*buf, len + 1, fmt, ap);  //new_vsnprintf()
      va_end(ap);
  }  
}   

Problem occurs in the 2nd call of new_vsnprintf(), when trying to get the actual value of placeholders by va_arg(). I assume the inner pointer of (va_list) ap points to wrong memory address. Then how to correct it?

2 Answers

You shouldn't be calling either va_start or va_end in your function fun. They should only be called in functions that have ... in the argument list to indicate that there are a variable number of arguments. My version of gcc (4.8.4) even throws up an error if I try and compile your code as it currently exists.

test.c: In function ‘fun’:
test.c:14:3: error: ‘va_start’ used in function with fixed args
   va_start(ap, fmt);

It seems likely that calling them in functions with fixed args is causing undefined behaviour and it's luck that the first call to vsnprintf is actually working.

Since you're not calling va_arg inside fun the value of ap shouldn't be changing and you should be able to call vsnprintf as many times as you require.

This answer may work if your platform has a simple varargs handling, like most platform do. If your platform do not permit va_list object to be trivially copied this cannot work. On most platforms va_copy is a simple macro that does some thing like:

#define va_copy(dest, src) dest = src

... this is the case for x86, ppc and many other targets. Anyway here is the code, I hope it works, I've no way to test or confirm it works without knowing the platform you are using it on!

int fun(char **buf, size_t buf_size, const char *fmt, va_list ap) {
  va_list ap_copy = ap;
  int len;
  /* first call*/
  va_start(ap_copy, fmt);
  len = vsnprintf(*buf, buf_size, fmt, ap_copy);
  va_end(ap_copy);
  if(len >= buf_size)
  {
      /* 2nd call*/
      va_start(ap, fmt);
      len = vsnprintf(*buf, len + 1, fmt, ap);  //new_vsnprintf()
      va_end(ap);
  }  
}   
Related