This page will give some examples for debugging C programs and debugging distributed C programs.


GDB is the GNU project debugger. To debug a program that is compiled by the GCC, it is necessary to pass the -ggdb option to the compiler. It is recommended to use unoptimised code, i.e. replace the -O<X> flag with -O0, otherwise the compiler might remove variables and optimize the order.

Debugging a program and passing the command line arguments to GDB:

gdb --args ./program args

To stop the program at certain line use:

> b <line_number>

Now you can run the program:

> run

There is a way to look up some code lines around your breakpoint:

> list


>  print <var name to print>

to look up the value of a variable.

You can step in a function by step (or just s) or go to the next line by next (or just n)

With the command backtrace or bt you can get a summary of how your program got to there where it is now.

Print q to quit gdb.

For more information see man gdb


Valgrind contains some very powerful and helpful tools for debugging, like memcheck, callgrind, cachegrind and some more.

memcheck can detect and warn about using of uninitialized memory, reading/writing off the end of malloc'd blocks, illegal reading/writing memory after it has been freed and various memory leaks. To use memcheck, you have to set the valgrind tool:

valgrind --tool=memcheck ./program

cachegrind is a cache profiler, which identifies cache misses in the CPU.

To use cacheacheck, you have to set the valgrind tool:

valgrind --tool=cachecheck ./program

Warning: be careful by using any valgrind tools. The program could run up to many times slower. Chose appropriate parameters.


An example session is like:

$ spack load -r mpi scorep
$ scorep mpicc -g mpi-speedup.c -o mpi-speedup
# Score-P environment variables
$ export SCOREP_ENABLE_TRACING=TRUE # enable tracing
$ export SCOREP_TOTAL_MEMORY=10000000 # trace buffer size
$ mpiexec -np 2 ./mpi-speedup

This will trace MPI calls and allow to distinguish application from communication calls. To understand internal behavior one has to use:

$ scorep --pdt mpicc -g mpi-speedup.c -o mpi-speedup

ScoreP comes with a few command line tools to explore the performance, for example, scorep-score:

$ scorep-score scorep-20170509_1259_6935968279002828/profile.cubex 
Estimated aggregate size of event trace:                   2277 bytes
Estimated requirements for largest trace buffer (max_buf): 1139 bytes
Estimated memory requirements (SCOREP_TOTAL_MEMORY):       4097kB
(hint: When tracing set SCOREP_TOTAL_MEMORY=4097kB to avoid intermediate flushes
 or reduce requirements using USR regions filters.)
flt     type max_buf[B] visits time[s] time[%] time/visit[us]  region
       ALL      1,138     52   10.24   100.0      196829.81  ALL
       MPI        852     30    0.03     0.3        1046.47  MPI
       USR        260     20    0.00     0.0           1.32  USR
       COM         26      2   10.20    99.7     5101864.89  COM

Additionally, the tool cube can be used to explore profiles and vampir can be used to investigate traces. Both are graphical tools – require X11 forwarding.


Cube is a GUI program to analyze profiles of parallel applications created using ScoreP.


 $ spack load -r cube
 $ TODO GUI ???

Further information: http://www.scalasca.org/software/cube-4.x/documentation.html


Vampir is a commercial tool to analyze traces of parallel applications created by ScoreP.


$ module load vampir
$ vampir scorep-20170509_1259_6935968279002828/traces.otf2

Further information: Slides


Amongst others, Likwid toolsuite allows to retrieve hardware counter information for runs.


$ spack load -r likwid
$ salloc -N 1 -p west
$ srun likwid-perfctr -a  # show available counters
$ srun likwid-perfctr -C 0 -g MEM ./hello-world-mpi # pin the application onto one core and measure the memory group
CPU name:	Intel(R) Xeon(R) CPU           X5650  @ 2.67GHz
CPU type:	Intel Core Westmere processor
CPU clock:	2.67 GHz
Hello world from process 0 of 1
Group 1: MEM
|              Event             | Counter |  Core 0 |
|        INSTR_RETIRED_ANY       |  FIXC0  | 3015861 |
|      CPU_CLK_UNHALTED_CORE     |  FIXC1  | 3447668 |
|      CPU_CLK_UNHALTED_REF      |  FIXC2  | 4552580 |
|    UNC_QMC_NORMAL_READS_ANY    |  UPMC0  |  64118  |
|     UNC_QMC_WRITES_FULL_ANY    |  UPMC1  |  52604  |
|                  Metric                  |    Core 0    |
|            Runtime (RDTSC) [s]           |    1.0135    |
|           Runtime unhalted [s]           |    0.0013    |
|                Clock [MHz]               |   2019.5326  |
|                    CPI                   |    1.1432    |
|     Memory read bandwidth [MBytes/s]     |    4.0491    |
|        Memory data volume [GBytes]       |    0.0041    |
|     Memory write bandwidth [MBytes/s]    |    3.3219    |
|        Memory data volume [GBytes]       |    0.0034    |
|        Memory bandwidth [MBytes/s]       |    7.3710    |
|        Memory data volume [GBytes]       |    0.0075    |
|  Remote memory read bandwidth [MBytes/s] |    0.0425    |
|  Remote memory read data volume [GBytes] | 4.307200e-05 |
| Remote memory write bandwidth [MBytes/s] |    0.0003    |
| Remote memory write data volume [GBytes] | 2.560000e-07 |
|    Remote memory bandwidth [MBytes/s]    |    0.0428    |
|    Remote memory data volume [GBytes]    | 4.332800e-05 |


Der Debugger DDT (Distributed Debugging Tool) ist geeignet zum parallelen Debugging und ist auf dem Cluster verfügbar. Über das graphische User Interface läßt sich das Tool einfach und intuitiv nutzen. Dazu ist X11 forwarding nötig.

DDT kann danach mit


oder gleich mit Angabe der zu untersuchenden Applikation

ddt ./Applikation

gestartet werden.

Die Applikation muss Debug-Informationen enthalten bzw. mit Debug-Informationen kompiliert sein, üblicherweise mit der -g-Option.

cc -g

Erste Schritte mit DDT

  1. Nach dem Starten öffnet sich folgendes Fenster, siehe 1. Schritt
    1. Help: Hier geht es zum User Guide.
    2. Run and Debug a program
  2. Run and Debug a program, DDT - Run, siehe 2. Schritt
    1. Application: Angabe der Applikation mit Auswahlfunktion.
    2. Arguments: Angabe mit welchen Argumenten der Lauf ausgeführt werden soll.
    3. Run Without MPI Support für serielle Applikationen aktivieren.
    4. Options Change: Ändern der Settings, hier wird auch die gewünschte Anzahl von parallelen Prozessen pro Knoten angegeben (siehe unter Run and Debug a program, DDT - Options).
    5. Queue Submissions Parameters sind der Regel ausreichend.
    6. Number of Nodes: Angabe der gewünschten Knoten.
    7. Number of OpenMP threads: Angabe der gewünschten OpenMP threads.
    8. Advanced: In der Regel sind hier keine Anpassungen nötig.
    9. Submit: Startet das Debugging.
  3. Run and Debug a program, DDT - Options, siehe 3. Schritt.
    1. System: System Settings sind in der Regel nicht anzupassen.
    2. Job Submission: Job Submission Settings hier sind in der Regel nur die Anzahl der gesamten parallelen Prozesse mit NUM_PROCS_TAG zu aktivieren oder die Anzahl der parallelen Prozesse pro Knoten mit NUM_NODES_TAG and PROCS_PER_NODE_TAG zu aktivieren, wobei hier zusätzlich die gewünschte Anzahl von parallelen Prozessen pro Knoten angegeben wird.
    3. Remote Launch: Remote Launch Settings werden in der Regel nicht benötigt.
    4. Appearance: Appearance Settings werden in der Regel nicht benötigt.
  4. DDT - Job Submitted, siehe 4. Schritt
  5. Connecting to your program, siehe 5. Schritt
  6. Debugging Session, siehe 6. Schritt
    1. Nach dem Starten der Session zeigt DDT automatisch den Quellcode des Programmes im Source Code Viewer an. Quellcode- und Header-Dateien werden unter Project Files als Baumstruktur angezeigt. Immer wenn ein Prozess angehalten wird, wird im Source Code Viewer automatisch der entsprechende Quellcode angezeigt.
    2. Variablen und Daten
      1. Current Line(s): Die Variablen der ausgewählten Zeile im Source Code Viewer werden mit ihren Werten angezeigt.
      2. Locals, siehe 6a. Schritt: Die lokalen Variablen des aktiven Prozesses werden angezeigt.
      3. Current Stack, siehe 6b. Schritt: Vom aktiven Prozess wird der Stack angezeigt und es kann auf die einzelnen Stack Frames gewechselt werden. Wird ein Stack Frame ausgewählt, so wird dessen entsprechender Quellcode mit dessen lokalen Variablen angezeigt.
      4. Evaluate - Beliebige Ausdrücke und globale Variablen, siehe 6c. Schritt: Beliebige Ausdücke und globale Variablen können hier angezeigt werden. Diese können im Evaluate Fenster über die rechte Maustaste hinzugefügt werden.
    3. Standard Aus- und Eingabe
      1. Input/Output, siehe 6d. Schritt: Im Input/Output Tab wird die Standard Aus- und Eingabe angezeigt, die für einzelne Ranks oder für alle Ranks ausgewählt werden kann.
    4. Breakpoints
      1. Breakpoints tab: Im Breakpoint tab werden die relevanten Breakpoints für die aktuelle Gruppe, Prozess und Thread angezeigt, siehe 6e. Schritt 6f. Schritt 6g. Schritt. Ein Breakpoint kann z.B. per Doppelklick auf die betreffende Zeile im Quellcode gesetzt werden.
    5. Session menu: Start, Stop, Sicherung und erneutes Starten eines Programmes.
    6. Control menu: Ausführung des Programmes, z. B. Run, Stop, Step into, Step over, Step out, Breakpoints, usw.
    7. Search menu: Goto line, find, find files.
    8. Shortcut menu: Ausführung des Programmes. Die Funktionen werden bei Maus darüber angezeigt.
    9. Current Group Shortcut menu: Der Fokus des Debuggings wird hier gesetzt, z. B. auf die ausgewählte Gruppe, Prozess oder Thread. Neue Gruppen können mit der rechten Maustaste eingerichtet werden. Die default Gruppe All zeigt alle Prozesse und Threads an.
  7. Zu diesem und alles andere siehe User Guide unter Help.
  8. Beispiele stehen unter /opt/ddt/3.2/examples zur Verfügung.
teaching/ressourcen/debugging.txt · Last modified: 2019-04-15 15:38 by Michael Kuhn