Efficient debug log

I usually prints some messages on a free UART used as a debug port. They are very useful during debugging to understand the flow of execution.

Most of the time, I don't compile the debug log facility in release build.

I sometimes need to disable messages from some modules and enable messages from other modules under debugging. Moreover I sometimes need to enable/disable less important messages.

Do you suggest some efficient implementation of debug log macros that include all the features above?

Reply to
pozz
Loading thread data ...

Most logging systems use a priority parameter which can be used to mask less important messages. If you have fewer modules than bits in a word, you could also pass a module id parameter, and efficiently mask messages per-module.

-a

Reply to
Anders.Montonen

On 2017-07-20 pozz wrote in comp.arch.embedded:

If I have a output stream available for this purpose, I often use the following (assuming you use C):

#ifdef DEBUG_ENABLE #define DEBUG_PRINTF(level, ...) ((level)

Reply to
Stef

We used to use a 32-bit trace-control word with bit-fields assigned for different subject areas. In large systems, 26 such words under letters of the alphabet, so you could enable tracing e.g. "t0-3,c6", etc.

The trace function took a first argument that packed a letter and a number (0..31) into 16 bits, which made it quick to check whether that trace flag was enabled. printf-style string & varargs followed. It also returned the "enabled" boolean, so you could avoid a code path whose only purpose is to generate detailed tracing.

More recently (2005) I implemented an even more powerful system, but it would only suit the largest embedded systems. It had an inlined test for the trace class being enabled (less than 2ns if disabled, after the first pass that bound the control variable), then queued the control strings and parameters into a large shared memory buffer. The buffer used lock-free synchronisation, so on normal Intel hardware could achieve >1GBps. One or more trace consumer threads could format the messages and write to a file, or send them over a TCP link. This consumer thread also listened for local connection requests so you could remotely enable and listen to tracing on a program while it was already running.

So yeah, the sky is the limit as far as sophistication goes ;)

Clifford Heath.

Reply to
Clifford Heath

.
.

different #define for each module

#define UART_DEBUG #define TIMER_DEBUG etc.

and the in the module

#ifdef UART_DEBUG printf("NO PANIC!! error on uart module") #endif

You enable only the ones you want. Or instead of defining in source code, you can use the -D directive of the compiler to enable/disable the debug parts. Depending on your toolchain one could be easier than the other.

Bye Jack

Reply to
Jack

Yes, something like that is the way to do it. That means in the code itself, you avoid having any extra "#ifdef DEBUG_ENABLE" conditional compilation that /really/ messes up the readability of the code. You just use DEBUG_PRINTF, safe in the knowledge that it does nothing when debugging is disabled.

The only unpleasant thing about this method is that if you have local data that is only used for debugging, you are going to get warnings about it when you disable debugging. Since I like to have most of my compiler warnings enabled, and like my code to be warning-free when compiled, this bugs me.

So I have this definition:

static inline int emptyFunctionX(int x, ...) { (void) x; return 0; } #define emptyFunction(...) emptyFunctionX(0, ## __VA_ARGS__)

Then when disabling debugging, I have:

#define debugPrintf emptyFunction

(I don't like defining a function as an expression in the way you did, and I hate all-caps macro names for constants or function-like macros.)

"emptyFunction" uses a gcc extension here, but that's a common compiler for many people. It swallows any arguments without generating code, and without requiring the arguments to be calculated at all (assuming you have optimisation enabled), but considers the arguments "used" as far as warnings are concerned.

Reply to
David Brown

As usual, thanks for the interesting tricks, David.

If I understood correctly:

#include "debug.h" // Where is debugPrintf, emptyFunction, ... static int var_used_only_in_debug; ... debug_printf(10, "The value is %d\n", var_used_only_in_debug);

The compilation doesn't emit warnings (regarding unused static variables) and doesn't really allocate space for var_used_only_in_debug (thanks to optimization).

This should work for functions too:

#include "debug.h" static const char *fun_used_only_in_debug(int arg); ... debug_printf(10, "The name is %s\n", fun_used_only_in_debug(value)); ... static const char *fun_used_only_in_debug(int arg) { if (arg == 3) return "Three"; if (arg == 3) return "Four"; ... }

In this case, the function isn't included in the output file (thanks to optimization).

I have another situation. I want to include a member in a structure, but it's only used in debug. This member is used only in debug_printf calls, so it can be omitted in the Release build. If I use #ifdef DEBUG_ENABLE #endif to avoid including the member, I will have an error during build (because debug_print refers to a member that doesn't exist). If I leave the member in the structure in Release build, I will waste some space.

Is there a trick to solve this problem?

Reply to
pozz

Yes.

In most such debug calls, you are just showing existing data, so it makes little difference. But sometimes you might want to manipulate something or make new local data before printing it.

Yes.

Note that this will only apply if the compiler knows it is safe to skip the function call - either because it has the definition of the function on hand and can see it (such as in this case), or because you have told it is safe (such as by using the "const" or "pure" function attributes in gcc).

That is a very interesting question. I hadn't thought about this before, but I've got an answer for you:

#define enableDebugging

static inline int emptyFunctionX(int x, ...) { (void) x; return 0; } #define emptyFunction(...) emptyFunctionX(0, ## __VA_ARGS__)

typedef struct {} empty_t;

#ifdef enableDebugging extern int debugPrintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); #define debugField(T) T #else #define debugPrintf emptyFunction #define debugField(T) empty_t #endif

typedef struct { int a; int b; debugField(int) debug; } data_t; data_t data;

int sizeofData(void) { return sizeof(data_t); }

int foo(void) { data.a += data.b; debugPrintf("%i %i %i", data.a, data.b, data.debug); return data.a; }

I tested this using the online compiler at . Copy and paste it in, using whatever compiler version you like (gcc or clang, because of the __attribute__).

Use "-Wall -Wextra -std=c11 -O1 -x c" as the command line options ("-x c" makes gcc compile C, rather than the default C++ for the tools used by the site).

You can see the real size of the "data_t" struct from the assembly code generated for "sizeofData" - a field of type "empty_t" takes no space, but avoids the error when it is used in the disabled debugPrintf call.

However, note that a struct with no members is invalid in strict ISO C. gcc is happy with it, but if you use -Wpedantic or use another compiler you may get warnings or errors.

The code here also shows how to use the "format" attribute to get checking of the types in the debugPrintf call. Try changing one of the %i to %s, and look at the warnings (they will only be checked when debugging is enabled, but you can't have everything!)

Reply to
David Brown

This is true, but you could also argue that data only used for debugging should be removed as well when debugging is disabled. That will require inserting some additional #ifdefs in the code in places where debug-only data is used. But mostly, i use the construct to print values of variables that are in use in normal code. And most often de DEBUG_PRINTF is not disabled at all, only the level lowered to a point where only things like startup and critical messages are emitted.

But this requires you to write an actual debugPrintf function to handle the level comparison before calling the actual printf? But I look into it, might be a better solution is some situations. And i love all-caps macro names for constants or function-like macros ;-)

Indeed a common compiler for me as well, but I have a few projects that use other compilers.

--
Stef    (remove caps, dashes and .invalid from e-mail address to reply by mail) 

Predestination was doomed from the start.
Reply to
Stef

On 2017-07-26 pozz wrote in comp.arch.embedded:

I often find this "waste of space" argument a bit irrelevant. On large targets, the few extra bytes usually don't matter at all. On small targets, the space argument may seem valid. But often, you want to be able to run the debug version on the same target as the release version. This means the target must have space for this data and that this space will simply be vacant if you have a release version with the data removed.

There is even a danger of expanding the code in release mode and then finding it does not fit anymore when you need to switch back to debug.

But there may be situations where you have a special (larger) target for debug than is used for the release. Or situations where you have to store or transmit binaries and space is tight or expensive.

So for these situations (or others, or just for the fun of it), I do find Davids exercise in the other reply very interesting.

--
Stef    (remove caps, dashes and .invalid from e-mail address to reply by mail) 

"Can you program?"  "Well, I'm literate, if that's what you mean!"
Reply to
Stef

When working with very small systems, it is not uncommon to use a larger or faster device for prototyping, testing, etc., and then a cost-optimal device for mass production. Sometimes that is just devices in the same families with different memory sizes, but it could easily be that your development and software testing is done mainly on an evaluation board with a large chip, extra UARTs for debug output, etc., and the final version will run on a different device.

Even when you have just a single device for everything, you may have different balances. Perhaps the release system will hold a log of the last 24 hours of sensor values, while for debugging it is fine for the log to be 6 hours and use the extra space for debugging information.

There are lots of possibilities here - even though you are right that the space cost of an extra debug data item or two is rarely significant.

Reply to
David Brown

You certainly /can/ argue that. It is not a good idea, usually, to have code or data that is not in use. It is a balance of choices, and you have to pick the method that is clearest, most maintainable, and easiest to be sure is correct.

I often have something like "debugPrintf" which would use snprintf to put the formatted data into a buffer, then send it out on a UART. It is typically a good deal more efficient than using a library printf with a fake file/stream output.

Many people use them. Actually, I like that /other/ people use them, such as in microcontroller peripheral definition headers - it means the names don't conflict with my identifiers!

And I use all-caps for any macros that are passed via compiler command lines (I don't use these much, but they can be a fine way to do parallel builds of different variations of the same code).

But I have never been able to comprehend why someone would think you should use all caps for:

#define SQRT2 1.4142135623730951

but not for

static const double sqrt2 = 1.4142135623730951;

Or why you should write:

#define VALID_PERCENT(x) (((x) >= 0) && ((x) = 0) && (x >

Yes, me too. I use gcc of preference whenever there is a choice - it is one of the criteria I have for picking a microcontroller for a project. It is, in many ways, the best choice of tool for most of my work (I am fully aware that one of the reasons gcc is a good choice for me is that I use gcc so much).

If I have to use a different compiler, I will often also use gcc (for any target) in parallel to do extra error checking. Things like __attributes__ are wrapped in conditional checks on __GNUC__.

Reply to
David Brown

For any system designed here, some sort of debug and syslog is included at the design stage. To start, always an led or two hanging off spare port lines, then a simple class / value pair syslog facility, with an output to leds or a uart.

A simple syslog can be implemented in less than a page of code, with an array of structures for the subsystem class and value pairs. Everything in the system that can have an error condition or other useful info has an entry.

Trivial stuff really, but gets the job done...

Chris

Reply to
Chris

Do you have a facility to disable (not present in the final binary) syslog messages from single modules?

Do you disable (as before) all the syslog feature in Release production?

Do you create messages in textual form (i.e. do you use printf-like functions)?

Reply to
pozz

Typically, no, since it can be useful for finding faults and form the basis of a diagnostic for production and in field testing. If you work it right, the overhead is minimal. If you want selective reporting, define another member of the structure for each item to enable or disable that or define the level, say from none to verbose. Typically, the structure just carries a U32 count, but can be changed to include strings etc. if i'm bringing a board up from cold, the syslog stuff is the first to be built. That then provides built in diagnostics as you bring up the hal layer for the other hardware and peripherals.

Depends on the system, but typically use as little of the C library as possible. All the types are wrong etc. Instead, have a well proven library of simple functions that print strings, convert between number bases, read input etc. It gets added to over the years to provide functionality. Of course, text output assumes a uart or other means of accessing the messages, but the same ideas can be used to provide flashing led sequences, whatever.

I guess what i'm saying is that I try to build some level of on board diagnostics into every project from the start, whether or not it's in the spec. It just makes life so much easier...

Chris

Reply to
Chris

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.