Can I use two level of variable length argument functions...? - c

I have one problem with variable length argument debug log print function. I will just simulate the code here.
void SecondPrint(int level, const char* format,...)
{
//Printing the log here
va_list arg;
va_start(arg, format);
vprintf(format, arg);
va_end(arg);
}
void FirstPrint(int level, const char* format,...)
{
SecondPrint(level,format);
}
void main()
{
int level = 100;
FirstPrintf("Log level = %d message = %s \n",level,"Error message");
}
"SecondPrint" is supposed to print "100 Error message" as expected, But its not printing like that its printing " Error message".
I am not getting whats wrong with this one. I am suspecting the way of calling "SecondPrint" from "FirstPrint" function. FirstPrint is receiving remaining arguments through ... but its invoking SecondPrint with level and format arguments.
I can't use SecondPrint from main directly. I have to use FirstPrint and FirstPrint has to call SecondPrint to print the log. So how can I achieve this one.. I thought to use VA_ARGS but it is for only macro definitions not in function definition.
And one more thing I can't do like *#define FirstPrint(a,b,...) SecondPrint(a,b,...)*
any help is highly appreciated thanks in advance.

C varargs are not designed to be passed more than one level; the kind of necessary stack manipulation required is too deep for the language. Typically, in a case like this, you would have a version of SecondPrint analagous to vprintf -- SecondPrintV or similar, and you would have FirstPrint invoke SecondPrintV after extracting the varargs, rather than invoking SecondPrint directly (and, for consistency, usually have SecondPrint invoke SecondPrintV internally).

You need to pass a va_list to SecondPrint, so it can access the arguments to FirstPrint
void SecondPrint(int level, const char* format, va_list args)
{
//Printing the log here
vprintf(format, arg);
}
void FirstPrint(int level, const char* format,...)
{
va_list args;
va_start(args, format);
SecondPrint(level, format, args);
va_end(args);
}
void main()
{
int level = 100;
FirstPrintf("Log level = %d message = %s \n",level,"Error message");
}

It may be possible to do it in specific platform-specific ways, or (since the arguments are exactly the same in the same order) use assembly to "replace" the current function frame with the function to call. GCC also has an extension to do it with the __builtin_apply_args() and __builtin_apply() functions (although you must somehow figure out how to compute the size of the stack argument data).

Related

What causes vsprintf to throw a segmentation fault?

I am writing a simple wrapper for syslog to make logging from my program a bit easier and allow dumping log entries to the console when selected. I have the following log function defined
void logDebugFunction (int lineNumber, char* filename, const char* functionName, char* format, ...)
{
if (LOG_DEBUG >= argPtr->logLevel)
{
char buffer[1000];
char *entry;
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
va_end(args);
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
syslog(LOG_MAKEPRI(0, (LOG_DEBUG)), "%s", entry);
if (argPtr->verbose)
{
// Print to stdout too
printf( "%s", entry);
printf("\n");
}
}
}
Which is called through the following macro:
#define logDebug(format,...) logDebugFunction(__LINE__, __FILE__, __func__, format, __VA_ARGS__)
From the main function, which is as follows:
int main(int argc, char *argv[])
{
// Set up syslog connection
openlog("ARController", LOG_CONS|LOG_PID|LOG_NDELAY, LOG_DAEMON);
// Set up our global arguments
struct arguments arguments;
argPtr = &arguments;
// set default values
arguments.verbose = 0;
arguments.foreground = 0;
arguments.logLevel = LOG_WARNING;
// Send a test debug message
logDebug("Test Debug message %d %s", 5, "a string");
// Close our syslog connection
closelog();
}
Now, when I try to run the only output I get is Segmentation fault (core dumped), obviously not what I want.
I've done some investigation using gdb and the --save-temps flag to verify the following:
In main.i I can see that the logDebug call in main has been replaced with logDebugFunction(72, "src/main.c", __func__, "Test Debug message %d %s", 5, "a string"); which is what I'd expect to see here.
When running, the segfault happens at the first vsprintf line in logDebugFunction
Just before the call to vsprintf all the mandatory arguments of the function are correct:
Breakpoint 2, logDebugFunction (lineNumber=72, filename=0x401450 "src/main.c", functionName=0x4014d3 <__func__.4035> "main", format=0x401437 "Test Debug message %d %s")
The va_list entries are what I'd expect them to be as shown by the following gdb commands (found here)
(gdb) p *(int *)(((char*)args[0].reg_save_area)+args[0].gp_offset)
$5 = 5
(gdb) p *(char * *)(((char*)args[0].reg_save_area)+args[0].gp_offset+8)
$6 = 0x40142e "a string"
When I step into the vsprintf call it seems like the arguments are correct: __IO_vsprintf (string=0x7ffffffedb40 "\200V", format=0x401437 "Test Debug message %d %s", args=0x7ffffffedb28) at iovsprintf.c:32`
So as everything seems to be in order I'm a bit lost as to what the issue is and what steps I can take next.
I don't see anything wrong (ignoring that there are no sanity checks) with the way you use va_list & vsprintf, so it could be that it needs more than 1000 charcaters and buffer is simply not large enough or your passing the argumnts in the wrong way? Have you tried using vprintf for debug purposes?
But I see a definitive problem in the next lines:
char *entry;
...
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
entry is a unitialized pointer, pointing to nowhere. If you try to read/write through that pointer, then you get an undefined behaviour. A segfault is the result of that.
With snprintf you can get the length of the expression and then with malloc dynamically allocate memory for it (fon't forget to free it afterwards). Or you can do
char entry[1024];
...
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
assuming that no entry will be longer than 1023 characters.
EDIT request from the comment to elaborate on getting length from snprintf
Let's start with the signature of the function
#include <stdio.h>
int snprintf(char *str, size_t size, const char *format, ...);
The man page description of says:
man page printf(3)
The functions snprintf() and vsnprintf() write at most size bytes
(including the terminating null byte ('\0')) to str.
If you want to just get the length, set size to 0 and str to NULL
int msglen = snprintf(NULL, 0, fmt, exp1, exp2, exp3,...);
Bear in mind that this behaviour is conform to C99. Compiling with an older compilier or older C standard might give you unspecified return value.
there is no checks that format does match passed arguments (see __attribute__ ((format (printf);
there are no checks that pointers are not null;
there is no check that buffer is large enough to hold the given string (use functions taking buffer size such as snprintf);
sprintf(entry, uses uninitialized variable entry instead of suitable buffer causing Undefined Behavior, attempt to write at random location pointed to by entry is the most likely reason for a segfault.
In my case I encountered this when I accidentally returned in a function that was marked _Noreturn in a header (but not in function itself) when writing C11.
This mistake did not cause a compilation error, didn't emit a warning (with -Wall) and wasn't caught by neither address sanitizer (asan) or thread sanitizer (tsan), but code execution after that return was bonkers and it gave me misleading call traces.

How to use varargs for small logger?

I am looking to make a small logger to be used like this:
logger log;
const char* text = "World";
log.write("Hello %s", text);
log.write("Step %d", 1);
This is my code, which doesn't work correctly:
class logger
{
public:
void write(const char* msg, ...)
{
FILE* file = fopen("c:/test.txt", "a");
if(file != NULL)
{
va_list args;
va_start(args, msg);
fprintf(file, "%s\n", msg, args);
va_end(args);
fclose(file);
}
}
};
This is what I get:
Hello %s
Step %d
I never used varargs before so I am not sure if I am using it correctly.
You're thinking that C behaves in a manner similar to Java or Python or other languages that can "splat" an array argument to a function that accepts varargs, but C isn't that sophisticated. When you pass args to fprintf, C literally pushes the value of args (a variable of type va_list) onto the stack. What you need instead is to push the contents of args onto the stack.
Or, instead, you could use a function that accepts a va_list as a parameter. That function is vprintf (and friends vsprintf, vfprintf, etc.).
You have another issue, which is that you're fprintf-ing the caller's "msg" parameter using "%s" but then apparently expecting fprintf to recursively use the result as a format string and fprintf the args too. That's not going to work. Instead just use the caller's msg as the format string.
va_list args;
va_start(args, msg);
vfprintf(file, msg, args);
fputc('\n', file);
va_end(args);

Pass a function with arguments as argument

I want to pass a function with arguments as argument into some other function, but I don't know how (or if) I can do that.
What I want to do is:
#include <stdio.h>
void getInput(char* request, int (*func)());
int main()
{
double input;
getInput("Input: ", scanf("%lf", &input));
}
void getInput(char* request, int (*func)())
{
// do stuff
func()
// do more stuff
}
Unfortunately this doesn't work. I think the scanf is getting executed when I'm trying to pass it as an argument.
How do I get this to run in getInput?
You can't do that in C, the language doesn't support such a construct.
You can often solve this exact type of problem using variable-arguments functions again:
void getInput(const char *request, const char *fmt, ...)
{
va_list args;
printf("%s: ", request);
fflush(stdout);
va_start(fmt, args);
vscanf(fmt, args);
va_end(args);
}
Usage is like this:
int main(void)
{
double input;
getInput("Input", "%lf", &input);
return 0;
}
Of course you might want to add proper return-value handling, otherwise it's impossible to know if the embedded vscanf() succeeded so the variable is OK to use.

c/c++ va_list about hook

void foo(int fmt, ...)
{
}
//I hook foo
static void (*original_foo)(int fmt, ...);
void replaced_foo(int fmt, ...)
{
printf("Hooking");
va_list args;
va_start(args, fmt);
//do something
va_end(args);
//But I want to call the original_foo function,
//I do not know how to invoke it ...
}
//Hook Function not include ...
Hook(foo, replaced_foo, (void **)&original_foo);
If you have a corresponding original_foo_v() which takes a va_args, you are lucky: you can use that.
If you don't (such as if you use DbgPrintf() or LStrPrintf() for interfacing with LabVIEW), you'll have to craft something on your own.
Essentially, you'll have to
examine the va_list you get,
find its stack frame by walking along the stack,
allocate as much memory on the stack as you need, assuming that you need the whole area between where the va_list points to and the next stack frame,
calling the non-va-aware function.
Of course, you'll have to do this for each and every platform you intend to support...
Good luck and have fun.

How can I temporarily redirect printf output to a c-string?

I'm writing an assignment which involves adding some functionality to PostgreSQL on a Solaris box. As part of the assignment, we need to print some information on the client side (i.e.: using elog.)
PostgreSQL already has lots of helper methods which print out the required information, however, the helper methods are packed with hundreds of printf calls, and the elog method only works with c-style strings.
Is there I way that I could temporarily redirect printf calls to a buffer so I could easily send it over elog to the client?
If that's not possible, what would be the simplest way to modify the helper methods to end up with a buffer as output?
If you define your own version of printf and link to it prior to the libc version, your version will supersede the standard version.
You should also be able to supersede the standard version by using LD_PRELOAD to load a library that has printf defined.
To write your own printf, you will want to use stdarg functionality:
int printf(const char *fmt, ...)
{
int rv;
va_list ap;
va_start(ap, fmt);
if (redirect_printf)
{
#ifdef HAVE_VLOG
// If you have a vlog function that takes a va_list
vlog(fmt, ap);
rv = ...;
#else
char buffer[LARGESIZE];
rv = vsnprintf(buffer, sizeof(buffer), fmt, ap);
log(buffer);
#endif;
}
else
{
rv = vprintf(fmt, ap);
}
return rv;
}
This simple version will truncate data when the final formatted output is greater than LARGESIZE. If you don't want that, you can also call vsnprintf first with a NULL buffer to get the final size, do a dynamic allocation and then a second call to vsprintf to format the buffer.
You're wrong — elog supports format strings just like printf. Here's an example from Postgres source code:
elog(DEBUG4, "TZ \"%s\" gets max score %d", tzname, i);
So all you need is to add elog where there is printf using the same parameters.
The simplest way is to modify the helper methods to call sprintf(). Whether or not you can hack that in easily, I don't know. Maybe
#define printf(...) sprintf(buffer, __VA_ARGS__)
Will do it for you. You'll still need to define buffer for each helper function, and get its contents returned to whoever cares about them.
If you can tolerate the use of a temporary file you could redirect standard out with the freopen() call:-
newstdout = freopen("/tmp/log", "w", stdout);
This will force all the printf's to be written to /tmp/log instead of the console output. At some convenient point later in your program you could then open the same file for reading:-
readfd = fopen("/tmp/log", "r");
and forward the contents that have been added using something like this:-
void forward_to_elog(void)
{
int bytesread;
char buf[100];
memset(buf,0,100);
do {
memset(buf,0,100);
bytesread = fread(buf, sizeof(buf)-1, 1, readfd);
/* call elog(buf) */ ;
} while(bytesread);
}
If you keep the file open you can call forward_to_elog() multiple times to incrementally forward the contents that have been added.
The tmpnam() function can be used to get a name for the temporary file if you don't want to have to statically code one.

Resources