I create a simple logger library in C.I have three files
My example file:
example.c
Library files:
loglib.h
loglib.c
My main logger function is called logme. I defined different macros as wrappers to indicate different log levels:
eg
WARN
ERROR
etc..
In example.c I call the macros:
int int_arg = 55;
WARN(1, "Warn message with level 1");
WARN(1, "Warn message with level %d", int_arg);
The WARN macro is defined in loglib.h:
#define WARN(LEVEL, ...) \
logme(LEVEL, 8, " <%s:%d> inside %s() -- "__VA_ARGS__, __FILE__, __LINE__, __func__);
And lastly here is the logme function:
void slog(int level, int flag, const char *msg, ...)
{
char string[10000];
bzero(string, sizeof(string));
va_list args;
va_start(args, msg);
vsprintf(string, msg, args);
va_end(args);
// .. do other things
}
When I run the example file this is what I get:
inside main() -- Warn message with level 1
Segmentation fault (core dumped)
I get segmentation fault when I call WARN with formated strings.
The segmentation fault appears in vsprintf(string, msg, args);
Is it something wrong with my macro?
Here is the make file of my lib:
CFLAGS = -g -O2 -Wall -lpthread
LIB = -lrt
OBJS = loglib.o
LIBINSTALL = /usr/local/lib
HEADINSTALL = /usr/local/include
.c.o:
$(CC) $(CFLAGS) -c $< $(LIB)
libslog.a: $(OBJS)
$(AR) rcs liblog.a $(OBJS)
#echo [-] Syncing static library
sync
install:
#test -d $(LIBINSTALL) || mkdir $(LIBINSTALL)
#test -d $(HEADINSTALL) || mkdir $(HEADINSTALL)
#install -m 0664 liblog.a $(LIBINSTALL)/
#install -m 0664 loglib.h $(HEADINSTALL)/
#echo [-] Done
loglib.o: loglib.h
.PHONY: clean
clean:
$(RM) liblog.a $(OBJS)
This can't work because you attempt string concatenation of __VA_ARGS__ here:
#define WARN(LEVEL, ...) \
logme(LEVEL, 8, " <%s:%d> inside %s() -- "__VA_ARGS__, __FILE__, __LINE__, __func__);
This messes up your sequence of parameters: __FILE__ will not go with the <%s, etc. You must redefine your WARN() macro so that __VA_ARGS__ is last. You'll have to write a proper variadic function if you want to prepend all that information. Note that __func__ is not a string literal, it references the appropriate string.
The ordering is incorrect when it hits the logme function.
You can do something like the following.
#include <strings.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
void logme(int level, int flag, const char *msg_preamble, const char* file, int line, const char* function, const char* msg, ... )
{
char buffer1[10000];
bzero(buffer1, sizeof(buffer1));
sprintf(buffer1, msg_preamble, file, line, function);
char buffer2[10000];
bzero(buffer2, sizeof(buffer2));
va_list args;
va_start(args, msg);
vsprintf(buffer2, msg, args);
va_end(args);
strcat(buffer1, buffer2);
printf("%s\n", buffer1);
}
#define WARN(LEVEL, ...) \
logme(LEVEL, 8, " <%s:%d> inside %s() -- ", __FILE__, __LINE__, __func__, __VA_ARGS__);
int main(int argc, char** argv)
{
int int_arg = 55;
WARN(1, "Warn message with level 1");
WARN(1, "Warn message with level %d", int_arg);
}
Here is the working solution:
/*
* SOURCE_THROW_LOCATION macro returns string which
* points to the file, as well as, the corresponding line
* of the caller function.
*/
#define LVL1(x) #x
#define LVL2(x) LVL1(x)
#define SOURCE_THROW_LOCATION "<"__FILE__":"LVL2(__LINE__)"> -- "
#define WARN(LEVEL, ...) \
logme(LEVEL, FWARN, SOURCE_THROW_LOCATION __VA_ARGS__);
The argument order is incorrect, hence the %s format does not correspond to the actual argument provided and the program invokes undefined behavior.
Here is a corrected version of the WARN macro:
#define WARN(LEVEL, ...) \
logme(LEVEL, 8, __FILE__, __LINE__, __func__, __VA_ARGS__);
And the corresponding logme function, using vsnprintf() instead of vsprintf() to avoid potential buffer overflows:
void slog(int level, int flag, const char *filename, in lineno,
const char *funcname, const char *msg, ...) {
char string[10000];
int prefix;
va_list args;
// check level, flag..
...
// format the message if needed
prefix = snprintf(string, sizeof string, " <%s:%d> inside %s() -- ",
filename, lineno, funcname);
va_start(args, msg);
vsnprintf(string + prefix, sizeof(string) - prefix, msg, args);
va_end(args);
// do other things
...
}
Related
I tried to use LD_PRELOAD to hook sprintf function , so I will print to file the result of buffer:
#define _GNU_SOURCE
#include <stdio.h>
#include<dlfcn.h>
int sprintf (char * src , const char * format , char* argp)
{
int (*original_func)(char*,const char * , char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src ,format,argp);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
When I compile this code gcc -Wall -fPIC -shared -o my_lib.so test_ld.c -ldl
I got error
test_ld.c:5:5: error: conflicting types for ‘sprintf’
int sprintf (char * src , const char * format , char* argp)
^
In file included from test_ld.c:2:0:
/usr/include/stdio.h:364:12: note: previous declaration of ‘sprintf’ was here
extern int sprintf (char *__restrict __s,
How can I fix that?
The main problem you're having is that your prototype for sprintf doesn't match the official one. Your function has this signature:
int sprintf (char * src , const char * format , char* argp);
While the official one has:
int sprintf(char *str, const char *format, ...);
You'll need to change your function to have this signature. Once you do that, you'll need to use a va_list to get the variadic argument. Then you would use that to call vsprintf which takes an argument of this type instead of using dlsym to load sprintf.
#include <stdio.h>
#include <stdarg.h>
int sprintf (char * src , const char * format , ...)
{
va_list args;
va_start(args, format);
int ret = vsprintf(src, format, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
Alex's first solution nicely addresses one problem: the conflicting declarations of sprintf (although there is no reason to not use the same signature as in stdio.h, see dbush's answer) . However, even then there remains one large elephant in the room: sprintf is a variadic function.
That means that, whenever the wrapped program calls sprintf with anything other than one char * third argument, your output may not be correct (and may even depend on your compiler's -O level)
Calling variadic functions from variadic functions (what is essentially what you're doing here) is a known problem. Any solution will be non-portable. With gcc, you can use __buitlin_apply and tap into gccs own private way of handling argument lists:
/* sprintf.c, compile with gcc -Wall -fPIC -shared -o sprintf.so sprintf.c -ldl
and use with LD_PRELOAD=./sprintf.so <program> */
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
# define ENOUGH 100 /* how many bytes of our call stack
to pass to the original function */
int sprintf (char *src) /* only needs the first argument */
{
void *original_func = dlsym(RTLD_NEXT,"sprintf");
void *arg = __builtin_apply_args();
void *ret = __builtin_apply((void *)original_func, arg, ENOUGH);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
__builtin_return(ret);
}
A few remarks:
In well-designed libraries variadic functions will have a non-variadic counterpart that uses one va_list argument instead of a variable number of arguments. In that case (and sprintf - vsprintf is such a case) you can use Alex's (portable) second solution with the va_* macros. If not, the solution with __builtin_apply() is the only possible, albeit gcc-specific, one.
See also: call printf using va_list
Possibly depending on the compiler version, when compiling main.c with the -O2 flag, main() will actually call __sprintf_chk() instead of sprintf() (regardless of -fno-builtin) and the wrapper won't work. To demonstrate the wrapper, compile main.c with -O0. Changing the main program to get the wrapper to work is the tail wagging the dog, of course. This shows the fragility of building wrappers: programs often don't call the library functions you expect. A ltrace <program> beforehand can save a lot of work....
You can rename the symbol in stdio, but then there's another problem, compilers like gcc use built-in implementations, unless you pass a flag like -fno-builtin, the compiler will generate the code inline in the executable, it won't link any library for functions like sprintf.
sprintf.c:
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
int sprintf (char * src , const char * format , char* argp)
{
int (*original_func)(char*,const char * , char*);
original_func = dlsym(RTLD_NEXT,"sprintf");
int ret = (*original_func)(src ,format,argp);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n",src);
fclose(output);
return ret;
}
main.c:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buffer[80];
sprintf(buffer, "hello world");
puts(buffer);
return 0;
}
Makefile:
all: libsprintf.so main
main: main.c
gcc -Wall -O2 -fno-builtin -o main main.c
libsprintf.so: sprintf.c
gcc -Wall -O2 -shared -fPIC -fno-builtin -o libsprintf.so sprintf.c -ldl
.PHONY: clean
clean:
-rm -f main libsprintf.so
usage:
make
LD_PRELOAD=./libsprintf.so ./main
edit
sprintf.c with variadic implementation (it doesn't call sprintf):
#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
#include <stdarg.h>
int sprintf (char * src , const char * fmt , ...)
{
va_list args;
va_start(args, fmt);
int ret = vsprintf(src, fmt, args);
va_end(args);
FILE* output = fopen("log.txt","a");
fprintf(output,"%s \n", src);
fclose(output);
return ret;
}
I am trying to define a global logger one time in a header file doing the following:
// log.c
#include <stdio.h>
#include "log.h"
void log_add_file(const char* filename)
{
printf("Adding file %s", filename);
}
void log_log(const char* filename, const char* function, int line, int level, ...)
{
printf("LOG");
}
// logmain.c
#include "log.h"
int main(void)
{
log_add_file("log.txt");
log_info("Hello");
}
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#define DEFAULT_FORMAT "[%(levelname)] %(time) %(name) - %(filename):%(funcName):%(lineno) - %(msg)"
enum LogLevel {
DEBUG=10,
INFO=20,
WARN=30,
ERROR=40,
};
struct logger {
int level;
const char* format;
FILE** handlers;
int num_handlers;
};
struct logger Logger = {INFO, DEFAULT_FORMAT, NULL, 1};
#define log_debug(...) log_log(__FILE__, __func__, __LINE__, DEBUG, __VA_ARGS__)
#define log_info(...) log_log(__FILE__, __func__, __LINE__, INFO , __VA_ARGS__)
#define log_warn(...) log_log(__FILE__, __func__, __LINE__, WARN , __VA_ARGS__)
#define log_error(...) log_log(__FILE__, __func__, __LINE__, ERROR , __VA_ARGS__)
void log_add_file(const char* filename);
void log_log(const char* filename, const char* function, int line, int level, ...);
#endif
The offending line is:
struct logger Logger = {INFO, DEFAULT_FORMAT, NULL, 1};
And the compiler says:
duplicate symbol _Logger____ in:
/var/folders/b1/mmc6r_jj00ldzr2prvqzy56m0000gp/T/log-9f31fa.o
/var/folders/b1/mmc6r_jj00ldzr2prvqzy56m0000gp/T/logmain-220d4d.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
What is the compiler saying in this case? First, why would a duplicate symbol matter here if logmain.c doesn't call log.c and vice versa? And secondly, what would be a way to resolve this?
Do I just add in the word static and call it a day? Or can that lead to other unintended consequences? Basically, I want all files to have the exact same Logger to use that includes it (a singleton).
I have the below program. It passes a macro as optional arg to variadic function. Within that function definition, shall we able to get that macro without expansion. I have used type as 'char *' and is showing the macro expanded string. Any ways to get macro as it is.
#include <stdio.h>
#include <stdarg.h>
#define CONN_DISALLOW 3201008
#define SYSLOG_CONN_DISALLOW \
"%d: Disallowing new connections. Reason: %s.\n", CONN_DISALLOW
void cp_syslog(int syslogid, ...);
void cp_syslog(int syslogid, ...)
{
char *syslog_disallow;
va_list ap;
va_start(ap, syslogid);
syslog_disallow = va_arg(ap, char *);
printf("String is %s", syslog_disallow);
printf("Macro is %s", SYSLOG_CONN_DISALLOW);
va_end(ap);
}
int main()
{
int id = 2;
cp_syslog(id, SYSLOG_CONN_DISALLOW);
return 0;
}
Now got the output as:
String is %d: Disallowing new connections. Reason: %s.
Macro is %d: Disallowing new connections. Reason: %s.
Expecting as:
String is SYSLOG_CONN_DISALLOW
My expectation here is how to process a particular macro if different macros are passed as optional argument to the same variadic func.
like below:
cp_syslog(id, SYSLOG_CONN_DISALLOW);
cp_syslog(id, SYSLOG_CONN_ALLOW);
cp_syslog(id, SYSLOG_ICMP_DISALLOW);
A first possibility is to just do
cp_syslog(id, "SYSLOG_CONN_DISALLOW");
Execution :
pi#raspberrypi:/tmp $ ./a.out
String is SYSLOG_CONN_DISALLOWMacro is %d: Disallowing new connections. Reason: %s.
pi#raspberrypi:/tmp $
(a newline is missed in the first printf or do cp_syslog(id, "SYSLOG_CONN_DISALLOW\n"))
But this is artificial and even the definition of cp_syslog is artificial because that one knows the macro is SYSLOG_CONN_DISALLOW being in its definition.
If you want to give to 'something else' both the name of a macro and its 'value' just use an intermediate macro like :
#define NAME_VALUE(x) #x, x
The expansion of NAME_VALUE(SYSLOG_CONN_DISALLOW) is "SYSLOG_CONN_DISALLOW", "%d: Disallowing new connections. Reason: %s.\n", 3201008
That time cp_syslog does not have to know it applies on SYSLOG_CONN_DISALLOW :
#include <stdio.h>
#include <stdarg.h>
#define CONN_DISALLOW 3201008
#define SYSLOG_CONN_DISALLOW \
"%d: Disallowing new connections. Reason: %s.\n", CONN_DISALLOW
#define NAME_VALUE(x) #x, x /* ADDED */
void cp_syslog(int syslogid, ...);
void cp_syslog(int syslogid, ...)
{
char *syslog_disallow;
va_list ap;
va_start(ap, syslogid);
syslog_disallow = va_arg(ap, char *);
printf("String is %s\n", syslog_disallow); /* \n ADDED */
printf("Macro is %s", va_arg(ap, char *)); /* MODIFIED */
va_end(ap);
}
int main()
{
int id = 2;
cp_syslog(id, NAME_VALUE(SYSLOG_CONN_DISALLOW)); /* MODIFIED */
return 0;
}
Compilation and executon :
pi#raspberrypi:/tmp $ gcc pp.c
pi#raspberrypi:/tmp $ ./a.out
String is SYSLOG_CONN_DISALLOW
Macro is %d: Disallowing new connections. Reason: %s.
pi#raspberrypi:/tmp $
Programs in C that have --verbose or --debug option, how they actually implement it? Without using 3rd party libraries.
My goal is not to do this all the time:
if(debug) printf("Debug msg\n");
printf("Info msg\n");
The most common I've seen is to print to stderr.
#ifdef DEBUG
#define DEBUG_MESSAGE(fmt, ...) fprintf(stderr, fmt ## "\n", __VA_ARGS__)
#else
#define DEBUG_MESSAGE(fmt, ...)
#endif
Elsewhere...
DEBUG_MESSAGE("VERBOSE: %s", "This is a verbose message.");
EDIT
Something that would work at runtime:
#define DEBUG_MESSAGE(fmt, ...)\
do{\
if(g_debugEnabled) fprintf(stderr, fmt ## "\n", __VA_ARGS__);\
}while(0)
Which can be used similarly.
LAST EDIT
Changed to use arbitrary format string with __VA_ARGS__.
You can refer the below program where a macro is defined and based on the option passed to the executable logging is enabled.
#include <stdio.h>
#include <string.h>
#define STREQ(a, b) !(strcmp((a), (b)))
#define logmsg(fmt, ...) \
do { \
if (debug) \
printf(fmt, ##__VA_ARGS__);\
}while(0)
char debug;
int main(int argc, char *argv[])
{
if (argc > 1) {
if ( STREQ(argv[1], "debug"))
debug = 1;
}
logmsg("%s\n", "hello_world");
return 0;
}
Pass debug as the first argument to the executable to enable logging
Note : This program has been tested on Linux with gcc compiler
I have many functions and while entering each function, I am calling a Macro FUNC_ENTRY, which is in turn calling the default logging macro (LOGGING).
To be specific, this is my FUNC_ENTRY macro:
#define FUNC_ENTRY LOGGING(ONE, "Entry");
My LOGGING macro is defined as below:
#define LOGGING(prio, my_var, ...) \
{\
char priority[6];\
bzero(level,4); \
if (prio == ONE) { sprintf(priority,"ONE");} \
if (prio == TWO) { sprintf(priority,"TWO");} \
if (prio == THREE) { sprintf(priority,"THREE");} \
fprintf(fp,"%s: %s: %s: %d: %s:%s|\n",__DATE__, __TIME__, __FILE__,__LINE__, __func__,level);\
fflush(fp); \
fprintf(fp,my_var,##__VA_ARGS__);\
fflush(fp); \
fprintf(fp,"\n");\
fflush(fp); \
}
OK, now my question is, whenever I enter any function, my LOGGING macro should print "Entry to xyz function". As of now, it only prints "Entry". Any clue how to achieve this?
The way I am calling the FUNC_ENTRY macro is as below.
Suppose I have a function xyz;
void xyz {
FUNC_ENTRY;
/*other statements*/
}
There are a number of issues:
That's two too many fflush(fp) calls for most purposes, unless you think you often write buggy code.
'Tis odd that you zero level, declare and set priority, but print level which is still all zeros.
That fragment is big enough to warrant a function.
You don't want the semi-colon after #define FUNCENTRY.
'Tis also odd that you think you should get Entry to xyz function when you don't include to or function in the strings in the macro.
It also seems pointless reporting the time and date when the file was compiled in your error messages (which is what __DATE__ and __TIME__ are for).
Fixing some of those issues leads to:
#define FUNC_ENTRY LOGGING(ONE, "Entry to %s function\n", __func__)
#define LOGGING(prio, my_var, ...) \
{\
char priority[6];\
memset(priority, '\0', sizeof(priority)); \
if (prio == ONE) { sprintf(priority, "ERR");} \
if (prio == TWO) { sprintf(priority, "TWO");} \
if (prio == THREE) { sprintf(priority, "THREE");} \
fprintf(fp,"%s: %s: %s: %d: %s:%s|\n", __DATE__, __TIME__, __FILE__, __LINE__, __func__, priority);\
fprintf(fp, my_var, ##__VA_ARGS__); \
fprintf(fp,"\n"); \
fflush(fp); \
}
I note that , ##__VA_ARGS__ is a GCC extension over standard C. The bzero() function was marked obsolescent in POSIX 2004 and is missing from POSIX 2008; it is best not to use it.
It is not clear, but judging from the code, you have a global (or, at least, file scope) variable FILE *fp; which is initialized to the log file you want to write to. Ick; and likewise yuck!
Were it my code (and I wanted the Entry to xyz function message spelled thus), I'd probably use:
extern void err_logger(int level, int line, char const *file, char const *func, ...);
and
#define FUNC_ENTRY LOGGING(ONE, "Entry to %s function\n", __func__)
#define LOGGING(prio, ...) \
err_logger(level, __LINE__, __FILE__, __func__, __VA_ARGS)
And the implementation of err_logger() might be:
void err_logger(int level, int line, char const *file, char const *func, ...)
{
char *priority = "";
if (level == ONE) priority = "ERR";
if (level == TWO) priority = "TWO";
if (level == THREE) priority = "THREE";
time_t t = time(0);
char date_time[32];
strftime(date_time, sizeof(date_time), "%Y-%m-%d %H:%M:%S", gmtime(&t));
fprintf(logfp, "%s: %s: %d: %s: %s|\n", date_time, file, line, func, priority);
va_list args;
va_start(args, func);
char *fmt = va_arg(args, char *);
vfprintf(logfp, fmt, args);
va_end(args);
fflush(logfp);
}
Where logfp would be the log file stream and would be private to the file that holds the source for err_logger().
I've not compiled that code; there could be minor errors in it. It does, however, give you the general idea of what to do and how to do it — I think.
Something along these lines:
#include <stdio.h>
#define T printf("Entering %s\n", __func__)
int main(void)
{
T;
return 0;
}
Unfortunately it looks like you're not going to be able to concatenate that to your string at compile time (see here: Can I substitute __func__ into an identifier name in a C macro?). There may be some other compiler specific or C++ specific tricks but otherwise you will need to build your string at run time.