Experience Embedded

Professionelle Schulungen, Beratung und Projektunterstützung

Auf Herz und Nieren

Autor: Jens Braunes, PLS Programmierbare Logik & Systeme GmbH

Beitrag - Embedded Software Engineering Kongress 2017


Bei der Entwicklung von Embedded Systemen kommt es längst nicht mehr nur auf eine hohe Softwarequalität an. Immer häufiger muss darüber hinaus auch die funktionale Sicherheit nachgewiesen werden. Letzteres gestaltet sich mitunter allerdings schwierig, weil die üblichen Werkzeuge für modellbasierte Tests zwar die notwendigen Testfälle bereitstellen können, eine ausreichende Testabdeckung aber nur auf realer Serienhardware unter echten Einsatzbedingungen erzielbar ist. Leistungsfähige Debugger mit COM-basierenden Automatisierungsschnittstellen können hier wertvolle Dienste leisten.

Modellbasierte Tests bieten heutzutage erfreulicherweise die Möglichkeit, eingebettete Software schon vor der Verfügbarkeit der realen Hardware auf ihre funktionale Korrektheit zu überprüfen. Ein Verfahren, das grundsätzlich durchaus Zeit und Kosten sparen hilft. Aber irgendwann kommt der Zeitpunkt der Wahrheit: der Test auf echter Hardware. Vor allem in echtzeit- und sicherheitskritischen Anwendungen muss gewährleistest sein, dass für den Test genau die gleichen Bedingungen wie auch später im Feld vorherrschen, also keine Code-Instrumentierung mehr vorliegt.

Testwerkzeuge sind dafür ausgelegt, die Verwaltung von Testfällen und die Testdokumentation vorzunehmen. Den Zugang zum Zielsystem, welcher für den Test auf der echten Hardware unter Serienbedingungen notwendig ist, muss durch den Debugger bereitgestellt werden. Die enge Kopplung beider Werkzeuge ist daher von essentieller Bedeutung.

Testsysteme im Überblick

Schauen wir uns zunächst einmal zwei typische Beispiele für Testsysteme an. Das erste Produkt stammt von der Firma TraceTronic. Der Name ECU-TEST weist bereits auf die vorrangige Zieldomäne – Steueranwendungen in Fahrzeugen – hin. Wichtig dabei ist die Anbindung an die im Automotive-Umfeld gängigen Testwerkzeuge und Umgebungen, wie beispielsweise Labcars. Das Werkzeug unterstützt den Entwickler bzw. den Testingenieur bei der Erstellung und Verwaltung von Testfällen, die sich leicht aus bereits verfügbaren Testschrittvorlagen ableiten und parametrisieren lassen. Die Testfälle bleiben dabei weitestgehend generisch, also unabhängig von spezifischen Testumgebungen und der echten Zielhardware eines Steuergerätes.

Die modulare Softwarearchitektur von ECU-TEST bietet klare Schnittstellen für die Integration in bestehende Testprozesse und eine effiziente und flexible Toolkopplung, Letztere ist ein entscheidendes Kriterium für den Test der Software auf Serienhardware, doch dazu später mehr.

Einen anderen Ansatz verfolgt die Firma PikeTec mit ihrem Time Partitioning Testing (TPT), einem modellbasierten Testverfahren, mit dem sowohl Matlab/Simulink- oder ASCET-Modelle als auch C-Applikationen getestet werden können. TPT greift auf parallel arbeitende Automaten zurück. Sogenannte Variationspunkte ermöglichen dabei die Modellierung einer großen Anzahl von Testfällen für einen gemeinsamen Automaten. So bleibt auch bei komplexen Tests mit einer Vielzahl von Testfällen die Übersichtlichkeit gewährleistet. Die Kopplung von TPT mit dem Anforderungsmanagement und der integrierten Testauswertung ermöglicht einen durchgängig automatisierten Prozess, angefangen von der Anforderungsspezifikation über die Testmodellierung und -durchführung bis zur Testauswertung und Dokumentation.

Echte Einsatzbedingungen

Richtig interessant wird es bei beiden Verfahren erst, wenn die Tests auf der echten Hardware, beispielsweise auf einem Motorsteuergerät in einer Labcar-Umgebung, ausgeführt werden. Nur bei solchen Tests wird das System nämlich unter vergleichbaren Bedingungen wie später beim Kunden im Feld gestresst.

Um die Unterschiede zwischen Theorie und Praxis zu verdeutlichen, werfen wir zunächst einen Blick auf den ausgeführten Code. Durch dessen Kompilierung für eine bestimmte Prozessorarchitektur bzw. den im Steuergerät verbauten Controllertypen hat der verwendete Compiler natürlich erheblichen Einfluss auf die funktionale Korrektheit der Applikation. Nicht nur echte Compiler-Bugs, auch ungünstig gewählte Optimierungseinstellungen können hier mitunter zu erheblichen Problemen führen. Aus einer ganzen Reihe von dokumentierten Fällen an dieser Stelle nur ein Beispiel dafür; Der Compiler eliminiert automatisch mehrfache Lese-/Schreibzugriffe auf Speicher, die aus seiner Sicht den Wert nicht verändern. Dies ist aber im Embedded-Bereich oft nicht zulässig, da sich hinter Speicherlokationen beispielsweise auch I/O-Register verbergen können, der Wert sich also durchaus zwischen zwei aufeinanderfolgenden Leseoperationen ändern kann. Der jeweilige Entwickler muss sich diesem Umstand bewusst sein und bereits im Vorfeld entsprechende Vorkehrungen treffen.

Der zu testende Code sollte natürlich auch identisch zum Seriencode im Steuergerät sein. Mitunter benötigen die Testwerkzeuge aber instrumentierten Code, also zusätzlichen Testroutinen, um damit beispielsweise Testvektoren zu injizieren und Ergebnisse abzugreifen. Damit ändert sich nicht nur das Laufzeitverhalten und die Codegröße, sondern gegebenenfalls auch das Speicherlayout. Dies wiederum kann sich insbesondere bei sicherheitskritischen Anwendungen oder Systemen mit hohen Echtzeitanforderungen als überaus kritisch erweisen. Um dieser Zwickmühle zu entkommen, bieten sich zwei Optionen an. Eine Variante wäre, die Instrumentierung auch im Seriencode beizubehalten. Dies würde zumindest gewährleisten, dass Seriencode und zu testender Code und damit auch das Laufzeitverhalten im Test und im Feld identisch sind. Oder man nimmt gleich den Debugger zu Hilfe. Er kann dann genau die Aufgaben übernehmen, die eigentlich der Code-Instrumentierung obliegen: die Injektion der Testvektoren und das anschließende Abholen der Ergebnisse.

Die Rolle des Debuggers

Was den Debugger für den Einsatz beim Test auf echter Hardware und unter realen Einsatzbedingungen prädestiniert, wird schnell klar. Man muss sich nur vor Augen führen, dass dieses Tool im gesamten Ecosystem, das für die Entwicklung von eingebetteter Software zur Verfügung steht, die eigentliche Schnittstelle zum Zielsystem bildet. Da er sich dabei die im Umfeld von eingebetteten Systemen vorrangig verfügbaren physischen Debug-Schnittstellen wie das allgemein bekannte JTAG zu Nutze macht, besteht ein typischer Debugger wie die Universal Debug Engine (UDE) von PLS aus einem Zugangsgerät wie dem Universal Access Device 2next (UAD2next), das die Adaption der Debug-Signale des Zielsystems übernimmt, und der eigentlichen Debugger-Software auf dem PC. Zugangsgerät und PC kommunizieren dabei über die für das PC-Umfeld typischen Schnittstellen wie USB oder Ethernet.

Der direkte Zugang zum Mikrocontroller über eine dedizierte Schnittstelle erlaubt dem Debugger neben der Ermittlung des aktuellen Target-Zustandes auch dessen Beeinflussung. Dies alles geschieht mittels gezielter Lese- und Schreibzugriffe auf Register und Speicher über die Debug-Infrastruktur des jeweiligen Controllers und – ganz wichtig – ohne nennenswerten Einfluss auf die Zielapplikation. Damit ist die Hauptanforderung für das Testen auf realer Hardware erfüllt. Aber der Debugger bietet noch mehr. So lassen sich beispielsweise neben globalen Variablen der Applikation, auch lokale Variablen von Funktionen und sogar einzelne Bits im Speicher oder in Registern auslesen oder ändern. Damit kann nicht nur die funkti­onale Korrektheit des zu testenden Systems nachgewiesen bzw. wider­legt werden. Der Entwickler kann so auch gleich nachvollziehen, warum sich das System möglicherweise nicht wie erwartet verhält.

Zudem bieten Debugger die Möglichkeit, durch Breakpoints bei Erreichen von bestimmten Positionen im Code die Programmausführung zu unterbrechen und auch wieder fortzusetzen. Im Testszenario lässt sich diese Basisfunktion hervorragend nutzen, um die funktionale Korrektheit von sogenannten Freischnitten, also bestimmten Code-Sequenzen oder ausgewählten Funktionen, nachzuweisen.

Nun sind der Test und dessen Dokumentation alleine für die Sicherstellung einer hohen Softwarequalität noch nicht ausreichend. Auch die Testgüte spielt hier eine große Rolle. Letztlich nutzt es niemanden, wenn die gewählten Tests nur einen Bruchteil der Funktionen innerhalb der Zielsoftware stressen. Die entscheidende Frage für den Nachweis einer ausreichenden Testabdeckung ist also: Wie hoch der Anteil der Applikation, des Modules oder der einzelnen Funktion ist, die der jeweilige Test gestresst hat, gegenüber dem Anteil, der durch eine ungünstige Wahl von Testfällen gar nicht gestresst werden konnte? Für die Beantwortung wird im Bereich der Softwareentwicklung in der Regel das Code Coverage herangezogen, das sich unter bestimmten Voraussetzungen auch mit Hilfe des Debuggers ermitteln lässt. Neben dem traditionellen Run-Mode-Debugging mit Breakpoints, Speichermanipulationen oder Single-Step-Betrieb bieten moderne Debugger wie die UDE zusätzlich auch Trace-basierte Analysefunktionen. Mit deren Unterstützung lassen sich Programmausführung inklusive aller Verzweigungen ohne vorherige Instrumentierung des Codes und ohne Beeinflussung der Laufzeit beobachten, Voraussetzung dafür ist lediglich die Verfügbarkeit von Trace-Hardware auf dem jeweiligen Mikrocontroller, was aber heutzutage schon fast selbstverständlich ist. Trace eignet sich hervorragend, das Code Coverage während der Testläufe zu bestimmen und damit gleich den Nachweis für eine ausreichende Testgüte zu erbringen.

Entscheidend ist die einfache Tool-Kopplung

In der Praxis nutzt wenig, wenn der Debugger zwar für den Test auf realer Hardware geeignete Funktionen besitzt, er aber für das Testsystem nur bedingt erreich- und nutzbar ist. Damit kommt ein weiterer wichtiger Aspekt ins Spiel: die Tool-Kopplung. Sie hat das Ziel, Testwerkzeugen wie ECU TEST oder TPT die Debugger-Funktionen zum Steuern des Zielsystems sowie zum Manipulieren und Auslesen der Target-Zustände bereitzustellen. Bei einer Kopplung mit der UDE von PLS wird dafür eine von der UDE dafür bereitgestellte Automatisierungsschnittstelle (UDE-Objektmodell) benutzt. Diese basiert auf dem Microsoft® Common Object Model (COM), das sich längst als De-facto-Standard in der Windows-Welt etabliert hat. Auch Microsoft selbst bietet einen großen Teil seiner neu hinzukommenden Windows-Funktionen über COM-Schnittstellen an. Über das UDE-Objektmodell stehen externen Tools nahezu alle Funktionen des Debugger wie Flash-Programmierung, Ablaufsteuerung, Lesen und Schreiben von Target-Speicher in symbolischer und numerischer Form, Trace-Daten-Erfassung, deren Analyse und vieles mehr zur Verfügung (Abbildung 1, s. PDF).

 
Im Falle der Kopplung von TPT an die UDE realisiert eine in C++ geschriebene DLL die Schnittstelle zwischen beiden Werkzeugen. Die Tests werden zunächst im Testsystem grafisch modelliert (Abbildung 2, s. PDF)) und dann wie bereits oben beschrieben die Automaten für den Test generiert. Dank beliebig vieler alternativer Signaldefinitionen bzw. Übergangsbedingungen lassen sich unterschiedlichste Varianten von Testfällen realisieren. Der eigentliche Test besteht aus der Abarbeitung des jeweiligen Automaten unter Berücksichtigung seiner jeweiligen Varianten. Dabei ist es zunächst egal, ob die Zielapplikation beispielsweise durch Matlab/Simulink als Modell simuliert wird oder ob es sich um ein reales eingebettetes System handelt, das den Code auch tatsächlich ausführt. Im letzteren Fall werden für jeden Abarbeitungsschritt die vorher festgelegten Aktionen für die Manipulation des Zielsystems ausgeführt bzw. die Daten abgeholt. Durch die Kapselung der dafür benutzten UDE-Objektmodell-Funktionen in der DLL kommt der Testingenieur mit den eigentlichen Debugger-Funktionen der UDE nicht in Berührung, es ist also keine spezielle umfangreiche Einarbeitung erforderlich.

Der Vollständigkeit halber sei noch erwähnt, dass die Automatisierungsschnittstelle auch genutzt werden kann, um den Debugger per Skript zu steuern. Welche Skriptsprache der Anwender bevorzugt, spielt dabei dank des COM-basierten Ansatzes nahezu keine Rolle.

Fazit

Um die funktionelle Sicherheit von Embedded Software unter Feldbedingungen nachweisen zu können, bedarf es neben modellbasierten Methoden auch komplett neue ganzheitliche Ansätze auf Systemebene. Voraussetzung hierfür ist die Möglichkeit einer nahtlosen Kopplung verschiedener Tools, die unterschiedlichste Zielstellungen abdecken. Der vorliegende Beitrag hat beispielhaft gezeigt, wie eine solche Kopplung für den modellbasierten Test auf realer Hardware aussehen kann und wie sich damit ein vollständiger Workflow für den automatisierten Test auf Serienhardware etablieren lässt.

Autor

Jens Braunes ist Product Marketing Manager bei der PLS Programmierbare Logik & Systeme GmbH. Er studierte Informatik an der TU Dresden und arbeitete dort als wissenschaftlicher Mitarbeiter. 2005 wechselte er zum Softwareteam von PLS und ist dort maßgeblich an der Entwicklung der Universal Debug Engine beteiligt. Er erweiterte 2016 sein Tätigkeitsfeld auf das Produktmanagement und technische Marketing.

Jens Braunes ist regelmäßig als Autor von Fachartikeln und als Referent auf Kongressen tätig.

 

Beitrag als PDF downloaden

 


Test, Qualität & Debug - unsere Trainings & Coachings

Wollen Sie sich auf den aktuellen Stand der Technik bringen?

Dann informieren Sie sich hier zu Schulungen/ Seminaren/ Trainings/ Workshops und individuellen Coachings von MircoConsult zum Thema Test, Qualität & Debug.

 

Training & Coaching zu den weiteren Themen unseren Portfolios finden Sie hier.


Test, Qualität & Debug - Fachwissen

Wertvolles Fachwissen zum Thema Test, Qualität & Debug steht hier für Sie zum kostenfreien Download bereit.

Zu den Fachinformationen

 
Fachwissen zu weiteren Themen unseren Portfolios finden Sie hier.