Komfortabler Dateimanager mit vielen Funktionen

Statische Codeanalyse zum Nulltarif

By Sven on 11.02.2008 - 11:06 in Visual Studio 2008

Die Windows-Programmierer unter euch werden sicher auch festgestellt haben, dass sich Funktionsdefinitionen in den Header-Dateien vom Platform Windows SDK in den letzten Versionen etwas verändert haben. Hieß es bei Windows XP noch

WINBASEAPI
UINT
WINAPI
GetTempFileNameW(
    IN LPCWSTR lpPathName,
    IN LPCWSTR lpPrefixString,
    IN UINT uUnique,
    OUT LPWSTR lpTempFileName
    );

so schaut die Definition im Windows SDK für Server 2008 so aus:

WINBASEAPI
UINT
WINAPI
GetTempFileNameW(
    __in LPCWSTR lpPathName,
    __in LPCWSTR lpPrefixString,
    __in UINT uUnique,
    __out_ecount(MAX_PATH) LPWSTR lpTempFileName
    );

An den Funktionsparametern selbst hat sich natürlich nichts verändert, aber die neuen Präfixe fallen doch ins Auge. Anfangs habe ich mir dabei nicht viel gedacht, aber mittlerweile hat sich mir der tiefere Sinn der Änderungen erschlossen.

Alles hängt mit der Standard Annotation Language (SAL) zusammen. In Verbindung mit einem statischen Codeanalyse-Tool lassen sich so mögliche Programmierfehler schon beim Kompilieren ermitteln. Auch wenn sich ein Projekt unter höchster Warnstufe ohne Warnungen kompilieren lässt, so heißt dies noch lange nicht, dass alles sauber ablaufen muss.

Leider ist das statischen Codeanalyse-Tool den höherpreisigen Visual Studio-Versionen Team System und Team Edition for Developers vorbehalten. Die Team Edition for Developers kostet aber locker 5000 Euro mehr als die Professional Edition, was für den normalen Entwickler ziemlich unerschwinglich sein dürfte.

Abhilfe schafft hier das Windows SDK für Vista sowie das für Server 2008. Beide enthalten die vollständige Compilersammlung von Visual Studio 2005 bzw. Visual Studio 2008 samt Codeanalyse-Tool. Für die Codeanalyse sind die Dateien c1ast.dll, c1xxast.dll, mspft80.dll und mspft80ui.dll zuständig. Die ersten drei Dateien befinden sich unter VC\bin, die letzte enthält die englischsprachigen Ressourcen und ist unter VC\bin\1033 zu Hause.

Nachdem man die vier Dateien an den entsprechenden Ort kopiert hat, muss die Codeanalyse nur noch aktiviert werden. In der Team Edition for Developers gibt es dafür eine Seite in den Projekteigenschaften, in der Professional Edition muss man den Kommandoschalter /analyze unter Configuration Properties – C/C++ – Command Line manuell eintragen.

Was macht die Codeanalyse nun so interessant? Nehmen wir einfach mal die oben angesprochene Funktion GetTempFileNameW. Die Verwendung könnte so aussehen:

29: WCHAR szTempFileName[240];
30: GetTempFileNameW(L"C:\\", NULL, 1, szTempFileName);

Der Compiler hat hier nichts auszusetzen, selbst bei /W4 gibt es keine Warnungen. Aktiviert man aber die Codeanalyse, dann sieht man plötzlich folgende Warnhinweise:

: warning C6202: Buffer overrun for ’szTempFileName‘, which is possibly stack allocated, in call to ‚GetTempFileNameW‘: length ‚520‘ exceeds buffer size ‚480‘
:warning C6309: Argument ‚2‘ is null: this does not adhere to function specification of ‚GetTempFileNameW‘
: warning C6386: Buffer overrun: accessing ‚argument 4‘, the writable size is ‚480‘ bytes, but ‚520‘ bytes might be written: Lines: 29, 30
: warning C6387: ‚argument 2‘ might be ‚0‘: this does not adhere to the specification for the function ‚GetTempFileNameW‘: Lines: 29, 30

Anhand der Definition von GetTempFileNameW erkennt der Compiler, dass die Funktion als vierten Parameter einen Puffer von mindestens 260 Zeichen erwartet, der Programmierer aber aus unerklärlichen Gründen nur einen Puffer von 240 Zeichen bereitgestellt hat. Ist der gewünschte temporäre Dateiname nun länger als die zur Verfügung stehenden 240 Zeichen, dann führt dies zu einem sicheren Pufferüberlauf.

Der Grund für die zweite Warnung ist der zweite Funktionsparameter. __in legt fest, dass lpPrefixString ein gültiger und initialisierter Zeiger sein muss. Der Compiler erkennt, dass NULL nicht in diese Kategorie passt und weist uns darauf hin. NULL wäre nur gültig, wenn lpPrefixString stattdessen mit __in_opt gekennzeichnet sein würde.

Insgesamt hat die statische Codeanalyse über 130 verschiedene Warnhinweise in petto. Sie erkennt z.B., wenn HRESULT-Werte in booleschen Vergleichen verwendet werden:

CAtlFile file;
if (!file.Create(L"C:\\test.txt", GENERIC_READ, FILE_SHARE_READ, CREATE_ALWAYS))
{
//
}

oder wenn eine Funktion Variablen enthält, die unverhältnismäßig viel Platz auf dem Stack belegen:

WCHAR szTempFileName[32768];
GetTempFileNameW(L"C:\\", L"123", 1, szTempFileName);

Mich selbst haben die Möglichkeiten der statischen Codeanalyse mehr als begeistert. Wenn man sich die möglichen 138 Warnungen so anschaut, dann entdeckt man viele Sachen, die man gerne mal falsch macht und über die man auch schon das eine oder andere Mal gestolpert ist. In der nächsten Zeit werde ich daher meine Projekte Stück für Stück mit /analyze kompilieren und die angezeigten Warnungen abarbeiten. Im zweiten Schritt werden dann die eigenen Funktionen mit den SAL-Parametern versehen.

Es gibt 7 Kommentare zu diesem Beitrag

Trackback URL | RSS-Feed für Kommentare

  1. Peter sagt:

    So lange die Liste klein bleibt, kein Problem. Wehe wenn nicht. 😀

  2. Coder sagt:

    Interessanter Ansatz, den man noch viel weiter treiben könnte…

  3. Sven sagt:

    An was denkst Du da genau?

  4. BenHero sagt:

    WOW, habe das jetzt auch mal ausprobiert. Was man da für nette Fehler findet! Danke für den MEGAHEISSEN Tip!

    Nur das einbauen in Eigene Funktionen is doch nicht so trivial wie es erst aussieht…

    Gruß BenHero

  5. Sven sagt:

    Das hängt von den Funktionen ab. Für viele reichen ja __in, __in_opt, __out und __inout aus. Bei Puffern kommt dann nochmal __out_ecount(Anzahl) bzw. __out_bcount(Anzahl) ins Spiel. Schau‘ Dir ruhig mal ein paar Windows API-Funktionen an, da kann man einiges von lernen.

  6. Oliver sagt:

    Cooler Tip. Daß man es auch direkt aus VS heraus (unterhalb Team-Edition) nutzen kann, wußte ich noch nicht.

    SAL wird im Zusammenhang mit Treibern schon länger genutzt und PREfast, so heißt das Tool im DDK/WDK, ist dort auch schon lange Standardumfang. Gelohnt hat sich zumindest dafür die Team-Edition nicht. Einziger Wermutstropfen, das DDK/WDK versteht keine VS-Projekte. Ggf. hilft da allerdings mein DDKWizard -> http://ddkwizard.assarbad.net … den man auch benutzen kann, falls man auf den Overhead der STL/CRT-Bibliotheken ab VS 2002 .NET keinen Bock hat, denn das DDK enthält alles um seine Programme gegen MSVCRT.DLL zu linken … jene DLL, die seit einiger Zeit auch offiziell den Status einer System-DLL hat.

    PCLINT ist aber noch ein Stück heftiger und gründlicher als PREfast (und irgendwie weniger spezifisch ;)).

    Ach so, in Sachen Stack. Das ist eine Funktion, bei der ich zuerst an KM-Treiber denke, da jeder Thread im Kernelmode nur 12kiB zur Verfügung hat (bei sog. GUI-Threads sind es 64kiB). Wenn man sich nun anschaut was passiert wenn der Stack nicht ausreicht, sind Tools ala PREfast ziemlich nützlich:
    – Usermode: Thread crasht, abhängig von der Fehlerbehandlung nimmt der Thread den Rest des Programms mit
    – Kernelmode: Thread crasht, Fehlerbehandlung ist nicht möglich, es folgt ein Bluescreen 😉

    // Oliver

  7. Oliver sagt:

    Da fällt mir gerade noch was im Zusammenhang mit dem SDK und DDK ein. Wir haben mal die Probe aufs Exempel gemacht gehabt, ob der optimierende Compiler im SDK enthalten ist. Ist er. Funktioniert offenbar aber nur, wenn man eine entsprechende Version von VS parallel installiert hat, welche ebenfalls einen optimierenden Compiler enthält.

Top