Open Source im professionellen Einsatz

Software-Qualitätssicherung mit Purify, Quantify und Pure Coverage

Reinigungskräfte

Programme, die beim Entwickler ohne jedes Problem laufen, können dennoch schwere Fehler enthalten, die sich erst beim Anwender zeigen. Die drei Tools der Purify-Plus-Sammlung von Rational Software reinigen auch die verborgenen Ecken einer Applikation.

Bei der Fehlersuche in einer Applikation ist es wichtig, jeden möglichen und sinnvollen Einsatz durchzuspielen und die Ergebnisse zu verifizieren. Doch sind Entwickler und Tester oft dieselbe Person, die beim Testen auch den gleichen Gedankengängen wie beim Programmieren folgt und Bugs übersieht, auf die erst die Anwender stoßen. Wer sich kein Labor mit unabhängigen Testern leisten kann, weicht besser auf Tools aus. Beispielsweise auf das Purify-Plus-Paket von Rational Software[1]. Diese Firma gehört seit Anfang 2003 zu IBM.

Purify Plus besteht aus drei Anwendungen, die große Bereiche der Software-Qualitätssicherung abdecken. Pure Coverage stellt sicher, dass der Tester jeden Codeabschnitt erwischt. Quantify ist für das Profiling zuständig, es spürt Funktionen auf, die zu langsam laufen. Der Namensgeber des Pakets - Purify - überprüft jeden Speicherzugriff und warnt bei verdächtigen Aktionen.

Die Purify-Plus-Suite richtet sich an professionelle Entwickler. Hobbyprogrammierer wird bereits der hohe Preis abschrecken, die Einzelplatzlizenz von Rational Purify Plus für Linux und Unix liegt bei knapp 7000 Euro. Kommerzielle Projekte, besonders wenn die Entwickler fremden Code erben, sparen mit den drei Tools aber Zeit und Geld.

Bedingt durch die maschinennahe Vorgehensweise funktioniert nicht jede Kombination von Purify, Compiler und Betriebssystem problemlos - das lässt sich mit der kostenlosen Trial-Versionen aber vorab 15 Tage lang klären. Auch im Open-Source-Lager sind zu jedem Programm der Purify-Familie vergleichbare Tools zu finden[2],[3].

Pfadfinder

Häufig stürzt Software ab, wenn der Anwender sie abseits der üblichen Einsatzweise nutzt. Vielleicht hat er sich durch einen Wald von Dialogfenstern gehangelt und mittendrin beschlossen, den ganzen Vorgang abzubrechen. Kein Entwickler kann sich vorstellen, auf welch ungewöhnliche Ideen die Anwender gelegentlich kommen. Also gilt es, möglichst jeden Ausführungsstrang innerhalb des Programms zu testen.

Bei komplexen Anwendungen ist der Grad an Testabdeckung ohne spezielle Hilfsmittel nicht mehr zu kontrollieren. Durch die vielen bedingten Verzweigungen und unerwarteten Kombinationen aus internem Zustand und Programmablauf wird es unmöglich, zielsicher jede einzelne Zeile des Programms interaktiv zu durchlaufen. Das Programm Pure Coverage unterstützt diese Arbeiten maschinell. Seine Anwendung ist denkbar einfach. Es genügt, vor den Compiler-Aufruf »purecov« zu setzen:

purecov gcc -g hello_world.c


Das so kompilierte Programm protokolliert nach seinem Start für jede Codezeile, wie oft es sie durchlaufen hat. Im Anschluss erhält der Entwickler eine Auswertung (Abbildung 1a), in der bei jeder Zeile die Anzahl der Durchläufe steht. Schwarz markierte Bereiche sind Programmteile, die nicht ausgeführt, also auch nicht getestet wurden. Sie können versteckte Fehler enthalten.

Das Ziel lautet: Die Testfälle so zu gestalten, dass sie den gesamten Code abdecken. Um das auch in größeren Umgebungen automatisch sicherzustellen, sendet Pure Coverage auf Wunsch eine Mail an den Entwickler, wenn ein Testlauf zu wenig Code erwischt. Die Schwelle ist frei definierbar, etwa auf 75 Prozent des Code einer Funktion.

Farbiger Speicher

Anwendungen, die viel mit dynamisch angelegtem Speicher arbeiten und darauf mit wilder Zeigerarithmetik zugreifen sind besonders aufwändig zu debuggen. Fehler lassen sich schwer reproduzieren, wirken sich bei jedem Programmablauf anders aus und treten oft erst verspätet in Erscheinung. Hat ein Prozess den falschen Speicherbereich geändert, dann läuft er eventuell noch lange weiter, arbeitet aber mit verfälschten Daten und stürzt an einer korrekt programmierten Stelle ab.

Vor solchen Überraschungen schützt Purify. Beim Übersetzen mit »purify gcc ...« instrumentiert es den Code mit speziellen Kontrollanweisungen, die jeden Speicherzugriff protokollieren und kontrollieren. Bei jedem lesenden oder schreibenden Zugriff auf eine Variable oder einen Speicherblock stellt Purify fest, ob die Aktion legal ist. Es benutzt dazu das Konzept des farbigen Speichers (Abbildung 2). Die Farbe symbolisiert den Zustand der Speicherstelle, also ob sie alloziert oder frei ist und ob das Programm den Inhalt initialisiert hat.

G Abbildung 1b: Die Zusammenfassung von Pure Coverage verrät, dass am Programmlauf 66 Prozent der Funktionen und 80 Prozent der Zeilen der Main-Routine beteiligt waren.

G Abbildung 1b: Die Zusammenfassung von Pure Coverage verrät, dass am Programmlauf 66 Prozent der Funktionen und 80 Prozent der Zeilen der Main-Routine beteiligt waren.

G Abbildung 1b: Die Zusammenfassung von Pure Coverage verrät, dass am Programmlauf 66 Prozent der Funktionen und 80 Prozent der Zeilen der Main-Routine beteiligt waren.

G Abbildung 1b: Die Zusammenfassung von Pure Coverage verrät, dass am Programmlauf 66 Prozent der Funktionen und 80 Prozent der Zeilen der Main-Routine beteiligt waren.

Die vier möglichen Zustände sind rot, gelb, grün und blau gefärbt. Rot steht für Speicher, den der Prozess noch nicht alloziert und auch noch nicht initialisiert hat. Gelben Speicher hat er zwar angefordert, ihm aber noch keinen Inhalt zugewiesen. Lesende Zugriffe darauf führen zu keinem definierten Ergebnis. Erst wenn der Inhalt initialisiert ist, wechselt die Farbe auf Grün. Nach dem Freigeben ist der Speicher blau - er enthält noch den alten Inhalt, darf aber nicht mehr verwendet werden.

Purify meldet jeden illegalen Zugriff als Fehler. Es öffnet dazu ein eigenes Fenster, das alle Warnungen sammelt, während das untersuchte Programm weiterläuft. Über das GUI findet der Entwickler sofort die Codestelle, die einen bestimmten Speicherbereich reserviert oder darauf falsch zugegriffen hat. Manche von Purify angemahnten Zugriffe sind bei genauer Betrachtung keine Fehler oder die Bugs stecken in Bibliotheken, auf die der Entwickler keinen Einfluss hat. Um zwischen diesen Falschmeldungen die echten Problemstellen nicht zu übersehen, hat das GUI flexible Filter, die bestimmte Fehlerklassen oder einzelne Bibliotheken ausblenden.

Dank seines Farbkonzepts kann Purify viele verschiedene Fehlerarten unterscheiden, unter anderem Zugriffe auf bereits freigegebene Speicherblöcke (FMR/FMW, Free Memory Read/Write), die bei Arrays beliebten Off-by-One-Fehler (ABR/ABW, Array Bounds Read/Write) und mehrfache Speicherfreigaben (FUM, Freeing Unallocated Memory).

Abbildung 2: Purify markiert den Zustand jeder Speicherstelle farbig. Roten Speicher darf das Programm weder lesen noch schreiben oder freigeben (Tabelle rechts). Das ändert sich nach einem »malloc()«-Aufruf (Zustandsübergang-Diagramm links), der Speicher wird gelb und damit beschreibbar.

Abbildung 2: Purify markiert den Zustand jeder Speicherstelle farbig. Roten Speicher darf das Programm weder lesen noch schreiben oder freigeben (Tabelle rechts). Das ändert sich nach einem »malloc()«-Aufruf (Zustandsübergang-Diagramm links), der Speicher wird gelb und damit beschreibbar.

Auch Programmen, die zur Laufzeit mit einem immensen Speicherbedarf auffallen, kommt Purify auf die Schliche. Zu jeder Zeit kontrolliert es den belegten und wieder freigegebenen Speicher. Damit bemerkt es Memory Leaks, also allozierte Speicherbereiche, auf die kein Zeiger mehr verweist.

Als sehr nützlich erweist sich in vielen Fällen die Purify-Schnittstelle: Das untersuchte Programm kann über dieses Interface mit Purify kommunizieren. Beispielsweise ruft die Debug-Version eines Programms in regelmäßigen Abständen die Funktion »purify_new_leaks()« auf, um Informationen über neue Speicherlecks auszugeben.

Abbildung 3a: Der Call-Graph zeigt, welche Funktionen welche Routinen aufgerufen haben. Er stellt sogar Rekursion passend dar.

Abbildung 3a: Der Call-Graph zeigt, welche Funktionen welche Routinen aufgerufen haben. Er stellt sogar Rekursion passend dar.

Abbildung 3b: Die Detailansicht von Quantify verrät, wie oft eine Funktion aufgerufen wurde, wie lange sie gebraucht und wie viel dieser Zeit sie in Subroutinen verbracht hat.

Abbildung 3b: Die Detailansicht von Quantify verrät, wie oft eine Funktion aufgerufen wurde, wie lange sie gebraucht und wie viel dieser Zeit sie in Subroutinen verbracht hat.

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook