Howto debug Ceedling Unit-Tests

Hintergrund

Ceedling ist ein (zu Recht) weit verbreitetes und in Ruby entwickeltes Build-System für C-Projekte.   Zusammen mit Unity und CMock wird es in der Embedded Software-Entwicklung gerne eingesetzt. Nach einer gewissen Einarbeitungszeit sind Unit-Tests normalerweise recht zügig erstellt. Idealerweise erstellt man die Tests zusammen mit der eigentlichen Implementierung. (Auf Test-Driven-Development will ich hier allerdings nicht näher eingehen.)

Es passiert allerdings immer wieder mal, dass ein Test “abraucht” – sprich: sich sang- und klanglos wegen einer Exception verabschiedet.

Hier ein Beispiel einer solchen wenig hilfreichen Fehlermeldung:

Linking test_adcCtrl.out…
Running test_adcCtrl.out…

ERROR: Test executable “test_adcCtrl.out” failed.
> Produced no output to $stdout.
> And exited with status: [0] (count of failed tests).
> This is often a symptom of a bad memory access in source or test code.

rake aborted!

C:/testprj/tools/vendor/ceedling/lib/ceedling/generator_helper.rb:36:in `test_results_error_handler’
C:/testprj/tools/vendor/ceedling/lib/ceedling/generator.rb:173:in `generate_test_results’
C:/testprj/tools/vendor/ceedling/lib/ceedling/rules_tests.rake:55:in `block in <top (required)>’
C:/testprj/tools/vendor/ceedling/lib/ceedling/task_invoker.rb:107:in `invoke_test_results’
C:/testprj/tools/vendor/ceedling/lib/ceedling/test_invoker.rb:124:in `block in setup_and_invoke’
C:/testprj/tools/vendor/ceedling/lib/ceedling/test_invoker.rb:51:in `each’
C:/testprj/tools/vendor/ceedling/lib/ceedling/test_invoker.rb:51:in `setup_and_invoke’
C:/testprj/tools/vendor/ceedling/lib/ceedling/rules_tests.rake:70:in `block (2 levels) in <top (required)>’
../../tools/vendor/ceedling/bin/ceedling:346:in `block in <main>’
../../tools/vendor/ceedling/bin/ceedling:333:in `<main>’
Tasks: TOP => build/test/results/test_adcCtrl.pass
(See full trace by running task with –trace)

Ursache sind z.B. Aufrufe von SDK-Funktionen, die auf Hardware-Adressen zugreifen und die es in der Simulation natürlich nicht gibt. In solchen Fällen die Fehlerursache zu finden, kann ohne weitere Analysemöglichkeiten auf längliches printf-Debugging hinauslaufen. Schön ist anders!

Hinweis: Im Folgenden gehe ich davon aus, dass bereits Unittests (unter Windows) mit Ceedling laufen; für das Einrichten von Ceedling gibt es gute Anleitungen ;-)

Wir verwenden in verschiedenen Projekten Ruby mit DevTools (Ruby-Installation https://rubyinstaller.org/downloads/, z.B. rubyinstaller-devkit-2.7.5-1-x86.exe). Der Compiler gcc kommt mit den MSYS2-Tools (https://www.msys2.org/) und ist da schon inklusive.

Back to the roots: gdb

Es ist schon seeehr lange her, dass ich den GNU Debugger gdb direkt verwendet habe. Zum Applikations-Debugging verwendet man normalerweise eine IDE (Segger/CubeIDE) oder ein anderes graphisches Debugger-Frontend (xgdb, DDD, KDbg,…) um sich das Leben nicht unnötig zu erschweren.

Wer Cygwin verwendet, hat den gdb normalerweise schon installiert. Wer Ruby mit MSYS2 verwendet, muss den gdb als optionales Packet installieren. Tipps dazu finden Sie am Ende („MSYS2 gdb-Installation“).

Die old-school-Version, um die Absturzursache eines Tests zu finden ist

  • eine Konsole (cmd) öffnen und den gdb mit dem absoluten Pfad zum Test-Executable starten:

    C:\testprj\scripts>gdb C:\testprj\Unittest\build\test\out\test_adcCtrl.out

  • gdb lädt das Executable und liest die darin enthaltene Symbolinformation:

    Reading symbols from C:\testprj\Unittest\build\test\out\test_adcCtrl.out…
    (gdb)

  • Mit “run” (oder kurz “r“) starten man das Executable:

    (gdb) rStarting program: C:\tesprj\Unittest\build\test\out\test_adcCtrl.out
    [New Thread 29368.0x700c]

    Thread 1 received signal SIGSEGV, Segmentation fault.
    test_adcCtrl_daqComplete () at test/test_adcCtrl.c:68
    68          adc_handler.Instance->CR = 0xFF;(gdb)

  • Falls man’s noch etwas genauer haben möchten – den Stacktrace erhält man mittels „where“:

    (gdb) where
    #0 test_adcCtrl_daqComplete () at test/test_adcCtrl.c:68
    #1  0x00f816f0 in run_test (func=0xf818b4 <test_adcCtrl_daqComplete>, name=0xfc8053 “test_adcCtrl_daqComplete”, line_num=57) at build/test/runners/test_adcCtrl_runner.c:93
    #2  0x00f81755 in main () at build/test/runners/test_adcCtrl_runner.c:109(gdb)

Mit diesen Informationen kommt man zumeist schon sehr viel weiter. (In diesem konkreten Fall greift der Applikationscode auf einen Pointer in der Datenstruktur ADC_HandleTypeDef  zu – und der ist NULL).

Etwas mehr Komfort bitte

Hardcore-gdb-ing ist ok – aber im Alter mag man’s gern auch etwas bequemer. Als gelegentlich VS Code-Nutzer lag die Suche nach einem Plug-In nahe, welches den Ceedling Unit-Tests Workflow unterstützt. Dabei bin auf über mehrere Blog-Beiträge etc. gestoßen. Am Ende des Artikels finden Sie eine Liste der verwendeten Quellen. Die Quintessenz ist in diesem Artikel zusammengefasst.

Jetzt geht’s also um die Zutaten die’s braucht, um einen Testcase zu Debuggen – und das möglichst komfortabel.

VS Code – mehr als ein Editor

VS Code ist ein Open-Source-Projekt (MIT-Lizenz) und wird manchmal auch nur als Editor verwendet. Damit bleibt man aber weit unter seinen Möglichkeiten. Mit den zahlreichen Erweiterungen wird er schnell zur eierlegenden Wollmilchsau – und dem diesem Fall zur „Ceedling-IDE“ :-)

Installation

Folgende Tools sind erforderlich

  1. VS Code (https://code.visualstudio.com/download – ich verwende die 64bit Version)
  2. Die VS Code-Erweiterungen
    • Ceedling Test Explorer: Startet Ceedling und die generierten Test-Runner im Hintergrund, parst deren Ausgaben, listet die Testcases, …
    • C/C++ : Notwendig zum Debuggen (setzen von Breakpoints, Single stepping, etc.)
      Am Ende des Blogs finden Sie eine Liste der installierten Plug-Ins.
  3. gdb – der GNU-Debugger. Wie anfangs erwähnt, ist er in einer Cygwin-Toolchain üblicherweise enthalten. Verwendet man MSYS, wird’s unter Umständen knifflig. Dazu finden Sie Am Ende noch zwei Links, mit Hilfe dere ich den gdb installieren konnte.

Die Installation sollte keine Schwierigkeit darstellen. Falls doch – bitte melden.

Bringing everything together

  1. Zunächst öffnet man in VC Code das Verzeichnis, in dem sich das project.yaml befindet: File -> Open Folder oder in der „Explorer View“
  2. Für diesen “Workspace” muss zunächst eine “launch”-Konfiguration erstellt werden.
    Dazu wechseln Sie in die “Run View“: Auf der “Activity Bar” (am linken Fensterrand) klicken Sie auf das Run-Icon  (Dreieck mit Käfer). Nun klicken Sie auf “create a launch.json file“.

    Jetzt erscheint eine Auswahlliste mit verschiedenen Konfigurationsmöglichkeiten. Sie können irgendeine Option auswählen. Es spielt keine Rolle, denn den Inhalt der im Verzeichnis .vscode erzeugten Datei ersetzen wir mit dem folgenden.
  3. Den folgenden Text – die sogenannte Launch-Konfiguration – kopieren Sie in die geöffnete launch.json-Datei:
    
    {
        "version": "0.2.0",
        "configurations": [
     
            {
                "name": "ceedlingExplorer",
                "type": "cppdbg",
                "request": "launch",
                "program": "${workspaceFolder}/build/test/out/${command:ceedlingExplorer.debugTestExecutable}",
                "args": [],
                "stopAtEntry": false,
                "cwd": "${workspaceFolder}",
                "environment": [],
                "externalConsole": false,
                "MIMode": "gdb",
                "miDebuggerPath": "gdb.exe",
                "setupCommands": [
                    {
                        "description": "Enable pretty-printing for gdb",
                        "text": "-enable-pretty-printing",
                        "ignoreFailures": true
                    }
                ],
                "debugConfiguration": {
                    "description": "Debug configuration to run during test debug.",
                    "type": "string",
                    "scope": "resource"
                  },
                "problemMatching": {
                    "mode": "gcc"
                }
            }
        ]
    }

    Mit der Konfiguration teilt man dem Ceedling Test Explorer mit

      • wo die Test-Sourcen liegen.
        Den Pfad „build/test/out“ im Eintrag “program” müssen Sie ggf. an Ihre Ceedling-Konfiguration (siehe project.yml) angleichen.
      • welcher Debugger verwendet werden soll.
        Hiefür müssen Sie den Parameter für miDebuggerPath an Ihre Buildumgebung anpassen. Ist gdb.exe nicht im Suchpfad, können Sie hier auch einen absoluten Pfad angeben, z.B.:
        “miDebuggerPath”: “C:/Ruby27/msys32/mingw32/bin/gdb.exe“,

    Die weiteren Parameter muss man normalerweise nicht anfassen.

  4. In den VS Code-Settings (via File->Preferences->Settings) unter “Extensions > Ceedling Test Explorer configuration” muss der Name der Konfiguration, wie im Launch.json angegeben, eingetragen werden:
  5. Damit der Ceedling-Test-Explorer die Testausgaben parsen kann, muss in der Projekt-Datei project.yml noch das Plug-In “xml_test_report” ergänzt werden::plugins:
    :load_paths:
      - ../../tools/vendor/ceedling/plugins
    :enabled:
      - stdout_pretty_tests_report
      - xml_tests_report
  1. Nun noch in die “Test”-Ansicht wechseln, auf “Refresh” klicken (zweites Icon neben “TESTING”) – und voila :-)

Danach werden alle Testcases aufgelistet.

Nun können Sie wie in jeder anderen IDE auch den Test editieren, Tests starten, Breakpoints setzen und debuggen. Was will man mehr!

Eine Einschränkung gibt es derzeit noch: Man kann einzelne Test-Cases eines Unit-Tests (noch?) nicht separat ausführen oder debuggen. Wollen Sie also einen Test-Case debuggen, müssen Sie dort zuerst einen Breakpoint setzen und dann den Bug-Button  klicken („Debug this test“).

Sollten Sie Fragen oder Anregungen haben, schreiben Sie einen Kommentar. Ich helfe ihnen gerne weiter.

Referenzen

MSYS2 gdb-Installation:

  • GDB installieren: Eine ältere Ruby/MSYS2-Installation hatte sich energisch geweigert, ein Paket zu installieren. Mit Ruby27+MSYS2 war das kein Problem. Einfach die MSYS32-Console starten (in meinem Fall msys2.exe im Verzeichnis C:\Ruby27\msys32) und mittels pacman -S gdb wird gdb installiert.
    Hier eine weitere Anleitung: https://gist.github.com/bd2357/b2d69ab18849c1e2f70959eef426ff09
    Hinweis: Sie können problemlos die 64Bit-Version von gdb installieren (mingw64/mingw-w64-x86_64-gdb), wie in der Anleitung beschrieben, auch wenn Sie die 32-Bit-Version von Ruby/MSYS2 verwenden.
  • Probleme mit Signaturen: https://github.com/msys2/MSYS2-packages/issues/2343

Links:

VS Code-Plugins

Autor

  • Seit Juli 2021 bin ich Teil des MEDtech Ingenieur-Teams – also ganz frisch aber (derzeit noch?) ältester Mitarbeiter. Ein Physiologieseminar gegen Ende des Elektrotechnik-Studiums in Erlangen weckte bei mir das Interesse an der Medizintechnik. Infolgedessen habe ich die Studien- und Diplomarbeit in Kooperation mit einer Firma (Hersteller von Geräten für die Ophthalmologie) bzw. der Universitätsaugenklinik durchgeführt, wo ich später auch ein paar Jahre eine Analyse-Software entwickelte. Zur Embedded-Software-Entwicklung kam ich schließlich bei zwei weiteren Erlanger Unternehmen (ab 1999). Bei letzterem war ich lange Jahre für die Entwicklung von HMIs für z.B. Baumaschinen und Laboranalysegeräte zuständig.

Wie hilfreich war dieser Beitrag?

Durch das Anklicken der Sterne wird ein Cookie in diesem Browser gespeichert, um das mehrfache Abgeben von Bewertungen zu verhindern. Durch das Anklicken der Sterne erlauben Sie medtech-ingenieur.de das Cookie zu speichern.

Es gibt bereits 1 Bewertung(en) mit einer Durschnittsbewertung von 5.

Bisher keine Bewertungen! Seien Sie der Erste, der diesen Beitrag bewertet.

Es tut uns leid, dass der Beitrag für Sie nicht hilfreich war!

Helfen Sie uns, diesen Beitrag zu verbessern!

Wie können wir diesen Beitrag verbessern?

Auch interessant:

Architekturentscheidungen dokumentieren

Warum um Himmels willen haben wir das damals so entschieden? Wer hat sich nicht schon einmal während der Entwicklung gefragt, auf welcher Basis eine Architekturentscheidung gefallen ist? Bei der Entwicklung von komplexen Systemen sind immer wieder Entscheidungen zu treffen. Wichtige Architektur- oder Designentscheidungen sollten immer dokumentiert werden. Doch wie geht…
2 comments on “Howto debug Ceedling Unit-Tests
  1. Daniel sagt:

    Hallo Jürgen,

    vielen Dank fürs Zeigen! Das hätte uns damals einige Male die Arbeit erleichtert! :) Das werde ich mal ausprobieren.

    Viele Grüße
    Daniel

    • Jürgen Welzenbach sagt:

      Die Erfahrung lehrt immer wieder: Es lohnt sich, die Zeit für das schärfen der Axt zu investieren, anstatt mühsam den Wald mit dem stumpfen Beil abzuholzen ;-)

      Viele Grüße
      Jürgen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.