Let's start with an example, I think this will demonstrate the problem I am dealing with immediately. This is a simple test program, far from realistic but it does illustrate the problem very well
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct first {
5 int i_value;
6 };
7
8 struct second {
9 float f_value;
10 };
11
12 #define DEFINE_FUNCTION(type, struct_name, field_name) \
13 void my_ ## type ## _function(struct struct_name *object, type value) \
14 { \
15 /* Deliberately read an uninitialized value to make valgrind */ \
16 /* report the issue */ \
17 if (object->field_name == -1) \
18 return; \
19 object->field_name = value; \
20 }
21
22 DEFINE_FUNCTION(int, first, i_value);
23 DEFINE_FUNCTION(float, second, f_value);
24
25 void
26 my_test_function(struct first *object, int value)
27 {
28 /* Deliberately read an uninitialized value to make valgrind */
29 /* report the issue */
30 if (object->i_value == -1)
31 return;
32 object->i_value = value;
33 }
34
35 int
36 main(void)
37 {
38 struct first frst;
39 struct second scnd;
40
41 my_test_function(&frst, -5);
42 my_int_function(&frst, -2);
43 my_float_function(&scnd, 3.0);
44
45 return 0;
46 }
If you compile this code and use
valgrind --show-origins=yes ./compiled-program
you will see an ouput like
==25304== Memcheck, a memory error detector
==25304== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==25304== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==25304== Command: ./macro-valgrind
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40056F: my_test_function (macro-valgrind.c:30)
==25304== by 0x400597: main (macro-valgrind.c:41)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40053A: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x400547: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304==
==25304== HEAP SUMMARY:
==25304== in use at exit: 0 bytes in 0 blocks
==25304== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==25304==
==25304== All heap blocks were freed -- no leaks are possible
==25304==
==25304== For counts of detected and suppressed errors, rerun with: -v
==25304== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
As you can see in the valgrind output above, the first uninitialized read reported is from the my_test_function()
function and it displays the exact line where the problem occurred. This way it's fairly easy to fix the code. The other reports are impossible to understand obviously. The best you can do with them is know which function it was, but that's all.
I understand that the generated code is confusing valgrind and that's why my actual question is,
Using Valgrind and GDB togetherStart up two terminal windows so that you can interact with Valgrind and GDB simultaneously. In one terminal, run Valgrind with the --vgdb-error=0 option. When running with --vgdb-error= n, Valgrind waits for n errors to occur before pausing and waiting for a connection from GDB.
Valgrind works by doing a just-in-time (JIT) translation of the input program into an equivalent version that has additional checking. For the memcheck tool, this means it literally looks at the x86 code in the executable, and detects what instructions represent memory accesses.
The GNU Debugger (GDB) allows you to pause a running program and inspect its state. Valgrind's memcheck monitors a program's memory accesses and prints warnings if the program accesses invalid locations or attempts to read values that the program never set (initialized).
I solve this style of macro induced debugging problem by:
1/ comment out standard library / third party #includes
2/ pass through gcc -E -C -P, which expands the macros
3/ put the #includes back
4/ pass through clang-format, which breaks up the very long lines
5/ compile with debugging information
The program is just the same as before, but gdb and valgrind refer to the expanded source. It's then reasonably easy to find the bug, then trace it back to the original source using a diff tool.
The above sounds like a pain, but steps 1 through 4 are just as scriptable as step 5, so the actual overhead during development is minimal. The reason this isn't my default is that jump to error in an ide takes me to the generated code, which is usually irritating.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With