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:

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 0 Bewertung(en) mit einer Durschnittsbewertung von 0.

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:

Usability Engineering für Medizinprodukte

Einleitung Was zeichnet ein gutes Produkt aus? Ich persönlich dachte vor meiner Zeit im SW-Engineering immer, dass sich die Anforderungen an ein System im Wesentlichen auf die Funktionalität desselben konzentrieren würden. Tatsächlich ist es aber die Kombination aus Funktionalität und Gebrauchstauglichkeit, welche die Qualität eines Produktes aus der Sicht eines…

Schreibe einen Kommentar

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