Debugging the Analyzer (GNU Compiler Collection (GCC) Internals)

### 28.2 Debugging the Analyzer ¶

When debugging the analyzer I normally use all of these options together:

    ./xgcc -B. \
      -S \
      -fanalyzer \
      OTHER_GCC_ARGS \
      -wrapper gdb,--args \
      -fdump-analyzer-stderr \
      -fdump-ipa-analyzer=stderr

where:

- `./xgcc -B.` is the usual way to invoke a self-built GCC from within the `BUILDDIR/gcc` subdirectory.
- `-S` so that the driver (`./xgcc`) invokes `cc1`, but doesn’t bother running the assembler or linker (since the analyzer runs inside `cc1`).
- `-fanalyzer` enables the analyzer, obviously.
- `-wrapper gdb,--args` invokes `cc1` under the debugger so that I can debug `cc1` and set breakpoints and step through things.
- `-fdump-analyzer-stderr` so that the logging interface is enabled and goes to stderr, which often gives valuable context into what’s happening when stepping through the analyzer
- `-fdump-ipa-analyzer=stderr` which dumps the GIMPLE IR seen by the analyzer pass to stderr

Other useful options:

- `-fdump-analyzer-supergraph` which dumps `SRC.supergraph.N.KIND.dot` GraphViz files that I can look at (with python-xdot)
- `-fdump-analyzer-exploded-graph` which dumps a `SRC.eg.dot` GraphViz file
- `-fdump-analyzer-exploded-nodes-2` which dumps a `SRC.eg.txt` file containing the full `exploded_graph`.
- `-fdiagnostics-add-output=experimental-html:show-state-diagrams=yes` which writes out the diagnostics in HTML form, and generates SVG state diagrams visualizing the state of memory at each event (inspired by the "ddd" debugger). These can be seen by pressing ’j’ and ’k’ to single-step forward and backward through events. Note that these SVG diagrams are created from an intermediate SARIF directed graph representation generated from `program_state` objects. The SARIF representation can be easier to read - for example, rather than storing the contents of memory via byte offsets, it uses fields for structs and element indexes for arrays, recursively. However it is a different representation, and thus bugs could be hidden by this transformation. Generating the SVG diagrams requires an invocation of "dot" per event, so it noticeably slows down diagnostic emission, hence the opt-in command-line flag. The SARIF and “dot” representations can be seen by `__analyzer_dump_xml` and `__analyzer_dump_dot` below (writing them to stderr), or by adding `show-state-diagrams-sarif=yes` and `show-state-diagrams-dot-src=yes` to the html sink, which shows them within the generated HTML next to the generated SVG.

Assuming that you have the python support scripts for gdb installed (which you should do, it makes debugging GCC much easier), you can use:

    (gdb) break-on-saved-diagnostic

to put a breakpoint at the place where a diagnostic is saved during `exploded_graph` exploration, to see where a particular diagnostic is being saved, and:

    (gdb) break-on-diagnostic

to put a breakpoint at the place where diagnostics are actually emitted.

#### 28.2.1 Special Functions for Debugging the Analyzer ¶

The analyzer recognizes various special functions by name, for use in debugging the analyzer, and for use in DejaGnu tests.

The declarations of these functions can be seen in the testsuite in `analyzer-decls.h`. None of these functions are actually implemented in terms of code, merely as `known_function` subclasses (in `gcc/analyzer/kf-analyzer.cc`).

`__analyzer_break`

  Add:

        __analyzer_break ();

  to the source being analyzed to trigger a breakpoint in the analyzer when that source is reached. By putting a series of these in the source, it’s much easier to effectively step through the program state as it’s analyzed.

`__analyzer_describe`

  The analyzer handles:

      __analyzer_describe (0, expr);

  by emitting a warning describing the 2nd argument (which can be of any type), at a verbosity level given by the 1st argument. This is for use when debugging, and may be of use in DejaGnu tests.

`__analyzer_dump`

      __analyzer_dump ();

  will dump the copious information about the analyzer’s state each time it reaches the call in its traversal of the source.

`__analyzer_dump_capacity`

      extern void __analyzer_dump_capacity (const void *ptr);

  will emit a warning describing the capacity of the base region of the region pointed to by the 1st argument.

`__analyzer_dump_dot`

      __analyzer_dump_dot ();

  will dump GraphViz .dot source to stderr reaches the call in its traversal of the source. This .dot source implements a diagram describing the analyzer’s state.

`__analyzer_dump_escaped`

      extern void __analyzer_dump_escaped (void);

  will emit a warning giving the number of decls that have escaped on this analysis path, followed by a comma-separated list of their names, in alphabetical order.

`__analyzer_dump_path`

      __analyzer_dump_path ();

  will emit a placeholder “note” diagnostic with a path to that call site, if the analyzer finds a feasible path to it. This can be useful for writing DejaGnu tests for constraint-tracking and feasibility checking.

`__analyzer_dump_exploded_nodes`

  For every callsite to `__analyzer_dump_exploded_nodes` the analyzer will emit a warning after it finished the analysis containing information on all of the exploded nodes at that program point.

        __analyzer_dump_exploded_nodes (0);

  will output the number of “processed” nodes, and the IDs of both “processed” and “merger” nodes, such as:

      warning: 2 processed enodes: [EN: 56, EN: 58] merger(s): [EN: 54-55, EN: 57, EN: 59]

  With a non-zero argument

        __analyzer_dump_exploded_nodes (1);

  it will also dump all of the states within the “processed” nodes.

`__analyzer_dump_named_constant`

  When the analyzer sees a call to `__analyzer_dump_named_constant` it will emit a warning describing what is known about the value of a given named constant, for parts of the analyzer that interact with target headers.

  For example:

      __analyzer_dump_named_constant ("O_RDONLY");

  might lead to the analyzer emitting the warning:

      warning: named constant 'O_RDONLY' has value '1'

`__analyzer_dump_region_model`

         __analyzer_dump_region_model ();

  will dump the region_model’s state to stderr.

`__analyzer_dump_state`

      __analyzer_dump_state ("malloc", ptr);

  will emit a warning describing the state of the 2nd argument (which can be of any type) with respect to the state machine with a name matching the 1st argument (which must be a string literal). This is for use when debugging, and may be of use in DejaGnu tests.

`__analyzer_dump_sarif`

      __analyzer_dump_sarif ();

  will dump the copious information about the analyzer’s state each time it reaches the call in its traversal of the source.

`__analyzer_eval`

      __analyzer_eval (expr);

  will emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the truthfulness of the argument. This is useful for writing DejaGnu tests.

`__analyzer_get_unknown_ptr`

      __analyzer_get_unknown_ptr ();

  will obtain an unknown `void *`.

`__analyzer_get_strlen`

      __analyzer_get_strlen (buf);

  will emit a warning if PTR doesn’t point to a null-terminated string. TODO: eventually get the strlen of the buffer (without the optimizer touching it).

#### 28.2.2 Other Debugging Techniques ¶

To compare two different exploded graphs, try `-fdump-analyzer-exploded-nodes-2 -fdump-noaddr`. This will dump a `SRC.eg.txt` file containing the full `exploded_graph`. I use `diff -u50 -p` to compare two different such files (e.g. before and after a patch) to find the first place where the two graphs diverge. The option `-fdump-noaddr` will suppress printing pointers withihn the dumps (which would otherwise hide the real differences with irrelevent churn).

The option `-fdump-analyzer-json` will dump both the supergraph and the exploded graph in compressed JSON form.

One approach when tracking down where a particular bogus state is introduced into the `exploded_graph` is to add custom code to `program_state::validate`.

The debug function `region::is_named_decl_p` can be used when debugging, such as for assertions and conditional breakpoints. For example, when tracking down a bug in handling a decl called `yy_buffer_stack`, I temporarily added a:

      gcc_assert (!m_base_region->is_named_decl_p ("yy_buffer_stack"));

to `binding_cluster::mark_as_escaped` to trap a point where `yy_buffer_stack` was mistakenly being treated as having escaped.