Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use valgrind with a function that is actually the expansion of a macro

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,

  • Is there a way to compile the code with gcc that can help valgrind understand this kind of functions?
like image 249
Iharob Al Asimi Avatar asked Jul 24 '16 20:07

Iharob Al Asimi


People also ask

Can you run GDB with valgrind?

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.

How does valgrind work internally?

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.

What is the difference between valgrind and GDB?

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).


1 Answers

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.

like image 63
Jon Chesterfield Avatar answered Nov 04 '22 04:11

Jon Chesterfield