Reimplementation of printf() using printf() in C - c

I'm making a embedded linux project and I want to do a simple debug messages library where I can disable my debug messages (using pre-compilation directives) when my code is in production phase and substitute for some type of log in a database (in the future). I'm having trouble with reimplementation of printf() because of va_list. This is my code until now:
My source file:
#include "debug_msgs.h"
#define ENABLE_DEBUG_MSGS 1U
#if ENABLE_DEBUG_MSGS
int print(const char *fmt, ...) {
int n = -1;
va_list ap;
va_start(ap, fmt);
n = printf(fmt, ap);
va_end(ap);
return n;
}
#else
int print(const char *fmt, ...) {
return 0;
}
#endif
My header file:
#ifndef DEBUG_MSGS_H
#define DEBUG_MSGS_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
int print(const char *fmt, ...);
#endif
My problem is:
My implementation compiles (I know this doesn't mean nothing), but when I do a simple test like:
int num1 = 10;
int num2 = 100;
int num3 = 1000;
float floating = 3.14;
char str[] = {"Please work, please"};
int number=-1;
number = print("\nTesting %d %d %d %f %s\n", num1, num2, num3, floating, str);
printf("\nnumber = %d\n",number); //Just to check the return
I get this output:
Testing -1095005212 100 -1095005212 -0.000002 H*�
number = 52
Complementary informations:
This is my platform info:
Linux mdm9607 3.18.44 #1 PREEMPT Tue Sep 13 19:45:33 UTC 2022 armv7l GNU/Linux
My compiler info:
arm-oe-linux-gnueabi-gcc
My $CFLAGS:
-O2 -fexpensive-optimizations -frename-registers -fomit-frame-pointer -MMD -MP
My $LDFLAGS:
-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed
LDFLAGS += -lumdp -lumdpcommon
LDFLAGS += -lpthread -lxml2 -lrt
LDFLAGS += -lm

Instead of printf, you should be using vprintf or vfprintf with a va_list argument:
#include <stdarg.h>
#include <stdio.h>
int print(const char *fmt, ...) {
int n;
va_list ap;
va_start(ap, fmt);
n = vprintf(fmt, ap);
va_end(ap);
return n;
}
The erroneous code compiles because printf, as a vararg function, can accept any argument types after the format string. Yet you can avoid type mismatches for printf arguments by enabling more compiler warnings: gcc -Wall -Wextra -Werror would at least complain about using printf with a non constant format string and would perform compile time type checking if the format string is a string literal.

Related

How to properly write asnprintf without format string compiler warnings?

I'd like to write a asnprintf function -- which is a wrapper around snprintf, but it mallocs the string according to its output size. Unfortunately when I compile I get a warning (promoted to error on my system) format string is not a string literal [-Werror,-Wformat-nonliteral].
I looked up the warning and apparently there are security concerns with passing a non-literal to printf functions, but in my case, I need to take in a format pointer, and pass that on.
Is there a good way around this that does not expose the same security vulnerability?
My function as is is as follows:
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap,ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if ( len > max_len)
len = max_len;
*strp = malloc(len+1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len+1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}
If you are targeting only GCC and Clang as compilers, you can get around this pretty easily by temporarily disabling the warning for that specific function:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
// Function definition here...
#pragma GCC diagnostic pop
Clang should recognize #pragma GCC too. You may also need to ignore -Wformat-security as I did above depending on your compiler flags.
Godbolt link to working example with Clang 11.
I'm wondering if it's possible for example to require that my function take only string literals
As Craig Estey suggests above, you could make use of the format function attribute to make the compiler perform this check for you:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int __attribute__((format(printf, 3, 4))) asnprintf(char **strp, int max_len, const char *fmt, ...) {
// ... implementation ...
}
#pragma GCC diagnostic pop
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
You could also do this, with a bit of trickery, using a macro and some compiler built-ins:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int internal_asnprintf(char **strp, int max_len, const char *fmt, ...) {
return printf(fmt);
}
#pragma GCC diagnostic pop
#define asnprintf(strp, maxlen, fmt, ...) ({ \
_Static_assert(__builtin_constant_p(fmt), "format string is not a constant"); \
internal_asnprintf(strp, maxlen, fmt, __VA_ARGS__); \
})
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
Note that the above code makes use of statement expressions (({...})) which are a non-standard extension and may or may not be available depending on your compiler flags.
From my top comments ...
Just add __attribute__((__format__(__printf__,3,4))) to your asnprintf declaration and/or definition.
This will cue the compiler to not complain.
And, the further benefit is that it will check the variadic arguments passed to asnprintf against the format string.
So:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
// put this in a .h file!?
int __attribute__((__format__(__printf__,3,4)))
asnprintf(char **strp, int max_len, const char *fmt, ...);
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if (len > max_len)
len = max_len;
*strp = malloc(len + 1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len + 1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}

How to update a vargs to send to printf

I have the following code where I format a message that has been passed via a logger:
va_list args;
va_start(args, level);
// pop the msg and do some string replaces
char *msg = va_arg(args, char*);
ssize_t len = str_replace(tmp, "%(msg)s", msg, output, 1024);
// now how to get it 'back in' so I can print it?'
// msg = output
vfprintf(stderr, output, args);
The above code prints nothing, as it seems to have 'popped' the msg without me placing the updated msg back in there. What would be a possible way to do this?
Instead of fiddling with the format string, you could print the variadic arguments passed to the log function to a string first, then print that string with a regular fprintf via the %s format.
You haven't posted your function signature, but it looks as if you passed the format string as first variadic argument. That's unusual. I've changed the signature so that the format string is the last non-variadic arg. That's common and it also opens up the possibility to chech your format strings for correctness. (See below.)
Here's an example implementation:
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
enum {
FATAL, ERROR, WARNING, INFO, NLEVEL
};
void say(int level, const char *fmt, ...)
{
static const char *templates[NLEVEL] = {
"\033[1m!!! FATAL: %s !!!\033[0m\n",
"ERROR: %s\n",
"Warning: %s\n",
"%s Just sayin'.\n"
};
const char *tmp;
char output[1024];
va_list args;
// print arguments to temporary string
va_start(args, fmt);
vsnprintf(output, sizeof(output), fmt, args);
va_end(args);
// find and print template string with message
if (level < 0) level = 0;
if (level >= NLEVEL) level = INFO;
tmp = templates[level];
fprintf(stderr, tmp, output);
va_end(args);
if (level == FATAL) exit(1);
}
Use it like this:
int main(int argc, char *argv[])
{
int a = 6;
int b = 7;
double Tmin = -273.15;
say(INFO, "%d * %d == %d.", a, b, a * b);
say(INFO, "The %s is %s.", "apple", "red and juicy");
say(WARNING, "Temperature falls below Tmin (%gC)", Tmin);
say(ERROR, "That should %s have happened.",
(rand() % 2) ? "never" : "not really");
say(FATAL, "Out of coffee!");
return 0;
}
Finally, you can make your logging functions safer and let the compiler check the validity of format strings. GCC and Clang do this with arguments, the Microsoft compiler does it with SAL, the Source-code Annotation Language. Other compilers may have their own method to do such checks. (More likely, they just emulate one of the GC or MS systems.)
With a little macro dance in the header you can cater for both variants:
#if defined(_MSC_VER)
#define PRINTF_FMT _Printf_format_string_
#endif
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_ARGS(IFMT, IARGS) __attribute__((format(printf, IFMT, IARGS)))
#endif
#ifndef PRINTF_ARGS
#define PRINTF_ARGS(IFMT, IARGS)
#endif
#ifndef PRINTF_FMT
#define PRINTF_FMT
#endif
void say(int level, PRINTF_FMT const char *fmt, ...) PRINTF_ARGS(2, 3);
Now a call like this:
say(WARNING, "Meta: Wrong formt for %d.", ~0ull);
warns you about the wrong format specifier.

Passing va_list to other functions

I have been trying to pass variable arguments to other function in C but it is producing inconsistent result in different runtime environment as well as in different runs in same environment:
int main()
{
int result = myprintf("Something \n %d", 9);
return result;
}
int myprintf(const char *format, ...){
printf("Something \n %d", 9);
printf("\n");
va_list args;
va_start(args, format);
int result = printf(format,args);
printf("\n");
va_end(args);
return result;
}
And the result produced is:
WWW.FIRMCODES.COM
9
WWW.FIRMCODES.COM
438656664
I could not find the reason for "438656664".
You cannot pass the variadic arguments to a variadic function. Instead, you must call a function that takes a va_list as argument. The standard library provides variants of printf and scanf that take a va_list; their names have the prefix v.
Your example should look like:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
int printfln(const char *format, ...)
{
int result;
va_list args;
va_start(args, format);
result = vprintf(format, args);
printf("\n");
va_end(args);
return result;
}
int main()
{
int result = printfln("Something \n %d", 9);
printf("(%d)\n", result);
return 0;
}
There are some gotchas, for example when you want to call two v... function for printing to the screen and a log file: The v... function may exhaust the va_list, so you must pass in a fresh one to each call if your code should be portable.
For the C++ fellow also reading this. You can actually do it using pack expansion without using vprintf.
This trick is quite handy when you need to wrap a method that takes the ellipsis (...)
and not a va_list.
For instance:
template <class ... Args>
void foo(const char *format, Args ... args)
{
printf(format, args...);
}
Here class ... Args is template parameter pack, Args ... args is function parameter pack, and args... is function parameter pack expansion.
Alternatively, you can simply use a wrapper macro:
#include <stdio.h>
#define myprintf(fmt, ...) ( printf("Something \n %d\n", 9), printf(fmt, __VA_ARGS__) )
int main (void)
{
int result = myprintf("Something \n %d\n", 9);
printf("%d\n", result);
}
Note the use of the comma operator to preserve the returned value of the right-hand printf call to the caller.
This isn't any less type safe than the (equally dangerous) stdarg.h variadic functions.
Just a simple demonstration and worked example with "a fresh one va_list" when you need to print/output-as-string a template string like constexpr const char* example = R"(template "%s")"; .
std::string print_templ(const char* format, ...)
{
va_list args1;
va_start(args1, format);
va_list args2;
va_copy(args2, args1);
std::vector<char> str(std::vsnprintf(nullptr, 0, format, args1) + 1);
va_end(args1);
const int ret = std::vsnprintf(str.data(), str.size(), format, args2);
va_end(args2);
return std::string(str.begin(), str.begin()+ret);
}

Custom print function with _TIME_, _FILE_, _FUNCTION_, _LINE_

I have following code in debug.h:
#ifndef __DEBUG_H__
#define __DEBUG_H__
#ifdef DEBUG
int al_debug(const char *format,
const char * time,
const char * file,
const char * function,
int line,
...);
#define debug(fmt, args...) al_debug(fmt, __TIME__, __FILE__, __FUNCTION__, __LINE__, args...)
#else /* IF DEBUG NOT DEFINED*/
#define debug(fmt, ...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
#endif /* END OF __DEBUG_H__ */
In debug.c:
#include <stdarg.h>
#include <string.h>
#define __WRAP_FUNCTION__
#include "debug.h"
#ifdef DEBUG
int debug(const char *format,
const char * time,
const char * file,
const char * function,
int line,
...)
{
int done=0;
va_list arg;
va_start(arg, format);
done = vfprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
done += vfprintf(stdout, format, arg);
va_end(arg);
return done;
}
#endif
And after compiling it I am getting following errors:
gcc -g -Wall -DDEBUG -c debug.c -o d_debug.o
debug.c:16:1: error: expected declaration specifiers or ‘...’ before string constant
debug.c:16:1: error: expected declaration specifiers or ‘...’ before string constant
debug.c:11:5: error: expected declaration specifiers or ‘...’ before ‘__FUNCTION__’
debug.c:16:1: error: expected declaration specifiers or ‘...’ before numeric constant
How I can fix this problem? What is problem here? Thank you in advance.
1) Change function name
// int debug(
int al_debug(
2) Use regular fprintf()
// done = vfprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
done = fprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
3) Start at line. Use parameter before ...
// va_start(arg, format);
va_start(arg, line);
4) Pedantic note: add done check for < 0 after each print.
done = fprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
if (done >= 0) {
int done_former = done
done = vfprintf(stdout, format, arg);
if (done >= 0) done += done_former;
}
va_end(arg);
return done;
5) Change args...) to args) #leeduhem
[Edit] to work with no args after format.
int al_debug(const char * time, const char * file, const char * function,
int line, const char *format, ...);
#define debug(...) al_debug(__TIME__, __FILE__, __func__, __LINE__, __VA_ARGS__)
// #leeduhem for __VA_ARGS__
#else /* IF DEBUG NOT DEFINED*/
#define debug(...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
int al_debug(const char * time, const char * file, const char * function,
int line, const char *format, ...) {
int done = 0;
va_list arg;
va_start(arg, format); // change to `format`
...
Fixed version:
debug.h
#ifndef __DEBUG_H__
#define __DEBUG_H__
#ifdef DEBUG
int al_debug(const char *time, const char *file, const char *func, int line, const char * format, ...);
#define debug(...) al_debug(__TIME__, __FILE__, __func__, __LINE__, __VA_ARGS__)
#else /* IF DEBUG NOT DEFINED*/
#define debug(...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
#endif /* END OF __DEBUG_H__ */
debug.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#define __WRAP_FUNCTION__
#include "debug.h"
#ifdef DEBUG
int al_debug(const char *time, const char *file, const char *func, int line, const char *format, ...)
{
int done=0;
va_list arg;
va_start(arg, format);
done = fprintf(stdout, "%s :%s:%s:%d: ", time, file, func, line);
done += vfprintf(stdout, format, arg);
va_end(arg);
return done;
}
#endif
test.c
#include <stdio.h>
#include <stdlib.h>
#include "debug.h"
void foo(void)
{
debug("debugg...\n");
debug("%s and %d\n", "debug information", 42);
}
int
main(int argc, char *argv[])
{
foo();
exit(EXIT_SUCCESS);
}
Testing:
$ gcc -g -Wall -I. -DDEBUG debug.c test.c
test.c: In function ‘main’:
test.c:12:10: warning: unused parameter ‘argc’ [-Wunused-parameter]
test.c:12:22: warning: unused parameter ‘argv’ [-Wunused-parameter]
$ ./a.out
10:46:40 :test.c:foo:8: debugg...
10:46:40 :test.c:foo:9: debug information and 42
$ gcc -g -Wall -I. debug.c test.c
test.c: In function ‘main’:
test.c:12:10: warning: unused parameter ‘argc’ [-Wunused-parameter]
test.c:12:22: warning: unused parameter ‘argv’ [-Wunused-parameter]
$ ./a.out
$
A few links that may be helpful:
6.20 Macros with a Variable Number of Arguments
6.47 Function Names as Strings

GDB cannot tell different source files when compiling them within #include?

Just a small program of stdarg.h, when I organizing source file in a single source file as following:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void
print(const char *, ...);
void
print(const char *format, ...)
{
va_list vlist;
va_start(vlist, format);
vfprintf(stdout, format, vlist);
va_end(vlist);
return;
}
int
main(int argc, char **argv)
{
const char *message = "Just a test";
print("==> %s <==\n", message);
return EXIT_SUCCESS;
}
The below code in a single file works well under gdb when debugging, it stopped where I expected.
But when I organizing the code in 3 files: print.c main.c test.c
/* print.c */
void
print(const char *, ...);
void
print(const char *format, ...)
{
va_list vlist;
va_start(vlist, format);
vfprintf(stdout, format, vlist);
va_end(vlist);
return;
}
/* main.c */
int
main(int argc, char **argv)
{
const char *message = "Just a test";
print("==> %s <==\n", message);
return EXIT_SUCCESS;
}
/* test.c */
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "print.c"
#include "main.c"
I use command gcc -g test.c, and run it under linux. When breaking at print, it refused to break and continue to run to the end, within a warning "error removing breakpoint 0".
I suppose that maybe it related to stdarg.h, because I am always organizing code in that way, but the first time met the problem.

Resources