Eine Tour ins Extrem-Debugging

Veröffentlicht: 15. Dez 2001 | Aktualisiert: 14. Jun 2004

Von Matt Pietrek

Aus einem einfachen Projekt kann schnell eine aufwendige Suche nach dem Fehler werden, wenn die Fehlermeldung keinen Sinn macht. Dieser Beitrag schildert den überaus dornenreichen Weg von einem Problem mit VBScript und den IIS hin zur Erkenntnis der Ursachen.

Auf dieser Seite

Eine Geschichte aus dem richtigen Leben
Am Abgrund
Nun kommen die schweren Geschütze
Der Debugger-Sumpf...
Das Problem wird festgenagelt

Eine Geschichte aus dem richtigen Leben

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

Bild02

In dieser Folge möchte ich Ihnen eine Geschichte über Fehler, Ignoranz und glatte Lügen erzählen. Nein, ich meine nicht die Präsidentschaftswahlen vom letzen Jahr, es geht nur um meine ersten Stolperschritte in die ASP-Programmierung (Active Server Page). Was als einfaches Experiment mit ein paar VBScript-Zeilen begann, endete schließlich im Systemdebugger, auf der Suche nach anscheinend geheimen Informationen aus den internen Datenstrukturen von Windows NT. Das war eigentlich gar nicht geplant und kam auch völlig unvermutet.

Warum diese Geschichte erzählen? Oh, dafür gibt es verschiedene Gründe. Zunächst möchte ich Ihnen zeigen, wie diese verschiedenen Softwareschichten, die gnadenlos aufeinandergetürmt werden, leicht ins Wanken geraten können. Das gilt besonders dann, wenn die Sicht durch die Schichten nicht sonderlich gut ist. Zweitens möchte ich Ihnen die Ursachenforschung für ein reales Problem beschreiben, das anfangs fast unlösbar erschien. Und schließlich ist es auch eine schöne Gelegenheit, einen kleinen Rückblick auf meine Arbeit zu wagen und mir zu überlegen, wie ich den Ablauf hätte verbessern können.

Derzeit habe ich noch nicht allzu viel Ahnung von Web, VBScript und verteilten Anwendungen. Es scheint mir aber an der Zeit zu sein, einmal die Nase in den Wind zu halten. Nach der Installation der IIS (Internet Information Services) auf einer Windows 2000-Maschine und der obligatorischen "Hello World"-Webseite, die ich in statischem HTML schrieb, wollte ich endlich mit der "richtigen" ASP-Programmierung beginnen. Nun war mir Visual Basic zwar nicht völlig unbekannt, aber mit VBScript hatte ich noch nie zuvor gearbeitet. Ich wollte etwas schreiben, das mit echten Daten arbeitet, nicht zu kompliziert ist und sich in VBScript und ASP realisieren lässt, damit ich mir die Daten in einem Browser anschauen kann. Die Idee dazu erhielt ich aus dem Artikel von Ken Ramirez "Anwendungen auf Basis von Visual SourceSafe" (System Journal 05/2000, S. 109).

Ich hatte vor, den Code zuerst in Visual Basic zum Laufen zu bringen und ihn dann auf die IIS zu portieren. In kurzer Zeit hatte ich einen 25-Zeiler fertig, der per Automation die Visual SourceSafe-Datenbank in meiner Firma öffnen und auslesen konnte. Der nächste Schritt war die Umsetzung des Codes auf ASP und die Anzeige des Ergebnisses im Browser statt in einem Listenfeld. Da ich kein schweres Gepäck mitschleppen wollte, schrieb ich meine ASP-Seiten in meinem guten, alten Codeeditor, kopierte sie ins entsprechende IIS-Verzeichnis, startete den Browser und erhielt prompt diverse Syntaxfehlermeldungen.

Die typischen Anfängerfehler zu beseitigen war kein großer Akt. Im Wesentlichen musste ich meine Anweisungen vom Grundmuster Dim foo as bar in Dim foo ändern. Ohne die starke Typisierung von C++ kam ich mir zwar ziemlich nackt vor, aber wenn Sie mit VBScript arbeiten möchten, müssen Sie dieses Sicherheitsnetz zurücklassen. Trotzdem gelang es mir praktisch von jetzt auf gleich, eine ASP-Seite dazu zu bewegen, die Visual SourceSafe-Datenbank zu öffnen. Mein Code sah ungefähr so aus:

Dim db  
set db = Server.CreateObject( "SourceSafe" )

Mit der nächsten Zeile stellte sich ein gewisses Problem ein:

db.Open "R:\SourceDir\MyProject\srcsafe.ini", "MATT"

Anders gesagt, hier kündigte sich der Absturz in den Wahnsinn an. Bei der Kontrolle im Browser erhielt ich folgende Fehlermeldung: Eine Ausnahme des Typs 'SourceSafe: Der SourceSafe-Datenpfad MATT existiert nicht. Wählen Sie bitte eine andere Datenbank.' wurde nicht bearbeitet.

Sehr seltsam, dachte ich. Der erste Parameter gibt die zu öffnende Datenbank an und "MATT" ist kein Datenbankpfad, sondern der Name, unter dem ich mich auf meiner Maschine anmelde. Kurz gesagt, die Meldung war einfach unsinnig. Nach einigen Versuchen mit verschiedenen Parameterkombinationen zeichnete sich immer noch keine Lösung ab. Probieren half also nicht weiter - ich musste tatsächlich anfangen, nachzudenken.

Ungefähr an dieser Stelle fiel mir ganz vage wieder ein, was ich irgendwann einmal über die Unterschiede zwischen Standard-VBScript und VBScript in ASP gelesen hatte. Warum also meinen Code nicht in normalem VBScript ausprobieren? Das schien mir eine gute Idee zu sein. Leider hatte ich absolut keine Idee, in welchem Programm ich meinen VBScript-Code ausprobieren könnte.

Da ich gelegentlich wohl einen ausgeprägten Dickkopf habe und keine Lust hatte, lange in der Dokumentation zu blättern (wer hat dafür schon Zeit?), probierte ich zuerst den WSH aus (Windows Script Host). Zu diesem Zweck brachte ich meinen Code in einer .wsh-Datei unter. Die Fehlermeldungen, die mir dann entgegensprangen, waren praktisch nicht zu verstehen und schienen irgendwas mit dem ersten Zeichen des Namens zu tun zu haben, den ich für die .wsh-Datei gewählt hatte. Also kam ich zu dem Schluss, dass der WSH wohl nicht der erfolgversprechendste Weg wäre.

Irgendwann war ich soweit, aus lauter Verzweiflung mehr oder weniger geduldig in der MSDN-Website herumzuwühlen. Dort wurde mir klar, dass praktisch jede der Microsoft Office-Anwendungen eine perfekte VBScript-Programmierumgebung darstellt. Ich bin mir auch hundertprozentig sicher, das schon früher einmal gelesen zu haben. Aber ich habe diese Information wohl in der falschen Ecke meines Hirns abgelegt, weil ich damals der Meinung war, sowieso nie VBScript-Code für Microsoft Office zu entwickeln.

Mit einem Gefühl wie Alice in Wunderland startete ich den Visual Basic-Editor, und zwar in Outlook (Tools/Makro/Visual Basic-Editor.). Nachdem ich den Visual Basic-Editor von Outlook mit meinem ursprünglichen Visual Basic-Code gefüttert hatte, erklärte sich der Code schnell bereit, Verbindung zu Visual SourceSafe aufzunehmen und in den Projekten herumzublättern. Mein Code funktionierte also in Visual Basic, er funktionierte in Outlook als VBScript... aber ich holte mir immer wieder eine blutige Nase, wenn ich denselben Code auf dem IIS fahren wollte.

Im Rückblick muss ich leider sagen, dass mein Drang, möglichst schnell etwas zum Laufen zu bekommen, mich wohl in die Irre geführt hat. Ich hätte eine Menge Zeit sparen können, wenn ich nicht die Methode Versuch-und-Irrtum zelebriert hätte. Ungefähr zu dem Zeitpunkt wurde mir auch endlich klar, dass ich besser schon früher in den sauren Apfel gebissenund mich mit den richtigen Werkzeugen beschäftigt hätte. In diesem Fall mit Visual InterDev.

Da ich mich schon damit abgefunden hatte, dass der Fehler nur auftrat, wenn mein Code unter IIS laufen sollte, gab ich mich der vagen Hoffnung hin, der Debugger vom Visual InterDev könne mir bessere Informationen über diese seltsame Visual SourceSafe-Fehlermeldung geben. Nach der Installation vom Visual InterDev legte ich schnell ein neues Projekt an und ergänzte es durch meine ASP-Seite. Und schwups - das gesamte Visual InterDev verschwand! Ich versuchte es erneut und das Visual InterDev verschwand erneut. Es ist schon seltsam, dass ein Programmierer, wenn er erst einmal mit dem Schädel gegen die Wand gelaufen ist, dies anscheinend sofort mit derselben Begeisterung wiederholen muss - in der Hoffnung, beim dritten oder vierten mal werde die Wand schon nachgeben.

Mit dem begründeten Verdacht, mit meinem Code müsse etwas ernsthaft faul sein, legte ich ein brandneues Visual InterDev-Projekt an und trug eine leere ASP-Seite ein. Das gleiche Problem wie beim ersten Mal: Visual InterDev verschwand einfach. Der nächste logische Schritt war der Griff zum neusten Visual Studio-Servicepack. Gesagt, getan. Aber die Wand hielt und ich hatte keinen Plan B mehr.

Ich wühlte ein wenig im MSDN Online herum, um herauszufinden, ob es sich um ein bekanntes Problem handelt. Ich stieß zwar auf viele KnowledgeBase-Artikel, die sich über Abstürze von Visual InterDev ausließen, aber davon hatte nichts mit meinem eigenen Problem zu tun. Erst später machte ich die Entdeckung, dass ich wohl irgendwann in grauer Vorzeit eine Vorversion vom Internet Explorer 5.5 installiert hatte. Diese Version verwendet eine andere MSHTML.DLL als Windows 2000 und Visual InterDev fiel stets in der MSHTML.DLL auf die Nase.

Da ich den Internet Explorer 5.5 nicht einfach rauswerfen konnte (eine andere Beta-Anwendung ist darauf angewiesen), war der Umzug auf eine andere Maschine der nächste logische Schritt. Auf dieser Maschine, die ebenfalls Windows 2000 fuhr, aber leider nicht mit den IIS ausgestattet war, lief Visual InterDev großartig. Ich fand es eigentlich ziemlich witzig, den IIS auf der einen Maschine zu haben und das Visual InterDev auf einer anderen. Die ASP-Dateien konnte ich dann auf der IIS-Maschine lesen und schreiben. Durch den Einsatz zweier Maschinen hatte ich mir allerdings ungewollt selbst eine hübsche Folterbank aufgebaut, die als "Remote Debugging" bekannt ist.

Am Abgrund

Als ich der Meinung war, es sei Zeit für die Fehlersuche im ASP-Skript, setzte ich einen Haltepunkt und drückte F5. Praktisch ohne jede Verzögerung schlug mir Visual InterDev ein Dialogfenster um die Ohren, in dem es hieß, dass es keinen Server für's Debuggen finden konnte. Seltsam, sehr seltsam... dachte ich wieder. Irgendwie kann Visual InterDev doch den Server finden, wenn es die ASP-Dateien schreibt. Also begab ich mich wieder an die Dokumentation und fand heraus, dass ich die Fehlersuche in der Ferne freischalten musste (remote debugging).

Im MSDN Online fand ich heraus, dass ich ein paar neue Sachen aus der Visual Studio Enterprise Edition installieren musste, nämlich die BackOffice Server 4.5 Developer Edition-Tools. Nachdem das Installationsprogramm vom Visual Studio fertig war, befolgte ich die Neun-Schritte-Konfiguration von DCOM für's Remote Debugging. Nachdem das getan war, befolgte ich noch einige andere Schritte, um die manuelle Fehlersuche in ASP-Seiten freizuschalten.

Als ich mit der Konfiguration der Dinge fertig war (jedenfalls dachte ich das in meinem jugendlichen Leichtsinn), wandte ich mich wieder der Maschine zu, auf der Visual InterDev lief. Wiederum drückte ich auf F5 - und wiederum erhielt ich dieselbe Fehlermeldung über den nicht aufzufindenden Server. Der Vollständigkeit halber führte ich mit der IIS-Maschine zur Sicherheit einen Neustart durch. Keine Änderung.

Nachdem mir langsam die Ideen ausgingen, was ich auf der IIS-Maschine noch ausprobieren könnte, ging ich wieder zu der Maschine, auf der Visual InterDev lief. Und ich begann, an praktisch allem herumzufummeln, was mir irgendwie erfolgversprechend aussah. Durch irgendeine glückliche Fügung des Schicksals muss ich wohl irgendwas getan haben, was den Rechner zu einem spontanen Neustart veranlasste. Es war wohl direkt mein Glückstag oder so. Natürlich war ich sauer, weil mein Windows 2000 abgestürzt war. Und das, obwohl es meiner Erfahrung nach eigentlich sehr stabil ist. Nachdem aber die Maschine, auf der Visual InterDev lief, ihren Neustart hinter sich hatte, konnte ich wie durch ein Wunder die Verbindung zur IIS-Maschine herstellen. Die Fehlersuche konnte also beginnen. Nun, manchmal nimmt man sein Schicksal eben hin, ohne viele Fragen zu stellen...

Mit der neugewonnenen Fähigkeit, endlich mein VBScript in einer ASP-Seite entwanzen zu können, versuchte ich prompt, eine Codezeile zu überspringen, die sich nun in mein Bewusstsein drängte:

db.Open "R:\SourceDir\MyProject\srcsafe.ini", "MATT"

Zwar erhielt ich dieselbe seltsame Fehlermeldung wie bei meinem ersten Versuch, diesmal aber fein säuberlich im Kästchen (Bild B1) und nicht via Browser als HTML. Schon viel besser, nicht wahr? Soviel zur Extra-Debugging-Power vom Visual InterDev, zumindest, was diesen Fall anbetrifft.

Bild01

B1 Eine tolle und hilfreiche Fehlermeldung.

So langsam an meiner geistigen Gesundheit zweifelnd sah ich mir noch einmal den Artikel von Ken Ramirez an und nahm sein VSSDatabase::Open statt meines Aufrufs, allerdings mit meinen Werten. Immer noch nichts. Allerdings fiel mir auf, dass Ken eine SRCSAFE.INI-Datei von einer lokalen Festplatte verwendet, während ich die Version benutzte, die von SRCSAFE.INI via Netz erhältlich war.

Sehr verdächtig! Ich änderte also meinen Code auf eine lokale Kopie von SRCSAFE.INI ab - und tatsächlich, es lief! Die lokale Visual SourceSafe-Datenbank hatte für mich zwar praktisch keinen Wert, aber zumindest war ich dem Ziel wieder einen Schritt näher.

Da mir die Idee kam, die IIS stellten mit Dateinamen vielleicht seltsame Dinge an, versuchte ich also, meinen ursprünglichen SRCSAFE.INI-Pfad im Netz durch die UNC-Version zu ersetzen (also mit \\maschine\pfad und nicht mit R:\). Aber das brachte leider nichts.

An diesem Punkt begann ich mich zu fragen, ob das Problem vielleicht etwas mit den Konten und der Sicherheit von Windows 2000 zu tun habe. Mir war ein anonymes Anwenderkonto aufgefallen, das die IIS benutzen. Und es schien im Netz nur eingeschränkte Rechte zu haben. Allerdings weist die Visual SourceSafe-Fehlermeldung nicht direkt auf den Sicherheitsmechanismus als Ursache hin. Die eigentliche Frage war also, wie man herausfinden konnte, ob die begrenzten Rechte der IIS tatsächlich die Ursache des Problems waren.

Nun kommen die schweren Geschütze

Wer viel mit Debuggern arbeitet, für den ist ein Debugger, der ihn nicht tun lässt, was er will, noch viel schlimmer, als wenn er gar keinen Debugger zur Verfügung hätte. Der Debugger von Visual InterDev funktioniert zwar auf der Skript-Ebene recht ordentlich, aber wenn ein Skript eine COM-Methode aufruft, können Sie das Karnickel nicht mehr bis in seinen Bau hinein verfolgen. Ich weiß zwar genau, dass die ASP-Seite die Methode VSSDatabase::Open aufruft, aber aus irgendeinem Grund versagt diese Methode. Und ich kann den Code im Visual InterDev nicht im Einzelschrittmodus abarbeiten, um herauszufinden, woran es liegt.

Wenn ich das herausfinden möchte, muss ich anscheinend zu einem der üblichen Debugger greifen, zum Beispiel zum WinDBG oder zum Debugger von Visual C++. Damit könnte ich den Prozess untersuchen, der den Skriptcode interpretiert. Rein theoretisch könnte ich dann den Visual SourceSafe-Code untersuchen, der die Methode VSSDatabase::Open implementiert, und dort nach dem Fehler forschen. In der Praxis ist dies aber nicht ganz so einfach, und zwar aus zwei Gründen.

Erstens steht überhaupt noch nicht fest, wo ich einen Haltepunkt setzen könnte. Es gibt keinen offensichtlichen Punkt, an dem das Skript endet und das aufgerufene COM-Objekt beginnt. Zweitens stellt sich die Frage, welchen Prozess ich eigentlich überwachen sollte. Von DLLHOST.EXE sind allein vier separate Kopien geladen und jede könnte meine ASP-Seite fahren.

Mir war klar, dass die Methode VSSDatabase::Open eine Datei öffnen musste. Also schien mir die Vermutung berechtigt, dass mich ein Systemdebugger, der gewissermaßen Herr über alle Prozesse ist, schnell zum fraglichen Code führen könnte. Da der SoftIce von Compuware systemweite Haltepunkte setzen kann, setze ich jeweils einen Haltepunkt auf die Eintrittspunkte CreateFileA und CreateFileW von der KERNEL32.DLL. Ich werde später noch eine bessere Lösung vorstellen, die ohne SoftIce auskommt (dieser Debugger wird übrigens von meiner Firma hergestellt), aber ich würde vorschlagen, dass Sie jetzt trotzdem weiterlesen und sich den weiteren Weg beschreiben lassen, den ich einschlug.

Praktisch sofort nach dem Setzen der Haltepunkte auf CreateFile schlugen diese auch an. Und das war schlecht, denn ich war noch gar nicht im ASP-Code. Anscheinend rief ein anderer Prozess wiederholt CreateFile auf. Der Versuch, die störenden CreateFile-Aufrufe mit bedingten Haltepunkten herauszufiltern, war leider nicht erfolgreich.

Als besserer Lösungsansatz erwies es sich, den Debugger vom Visual InterDev und SoftIce zu kombinieren. Die Grundidee ist, mit einem Debugger, der auf der oberen Ebene arbeitet (Visual InterDev), möglichst nahe ans Problem zu kommen und dann für die Tiefenbohrungen auf SoftIce umzuschalten. Also schaltete ich die mit SoftIce gesetzten Haltepunkte wieder ab und setzte im Debugger von Visual InterDev einen Haltepunkt auf den db.Open-Aufruf. Ein kurzer Druck auf die Refresh-Taste vom Browser sorgte für die erneute Ausführung des ASP-Skripts.

Als der Haltepunkt im Skriptcode anschlug, wechselte ich in den SoftIce und schaltete meine Haltepunkte auf die CreateFile-Eintrittspunkte wieder ein. Anschließend wies ich Visal InterDev an, einen Schritt über den Aufruf hinweg zu machen. Nach meiner Hypothese sollte VSSDatabase::Open irgendwann CreateFileA aufrufen und SoftIce sollte an dieser Stelle anhalten. In der Praxis schlug der Haltepunkt aber nicht an und Visual InterDev teilte mir fröhlich mit, dass "MATT" keine gültige Visual SourceSafe-Datenbank sei (Als ob ich dies nach der 150sten Wiederholung nicht schon gewusst hätte...). Was jetzt?

Angesichts der Alternativen, den Code von VSSDatabase::Open entweder durch geduldiges Herumstochern im Speicher oder durch Überlegung zu finden, entschied ich mich für letzteres. Während der obligatorischen kleinen Pause versuchte ich, die bekannten Punkte miteinander zu verbinden. Dabei fiel mir auf, dass die Visual SourceSafe-DLL (SSAPI.DLL) ja auch eine Typbibliothek enthalten musste, die das Automationsmodell von Visual SourceSafe beschreibt.

In meinem Artikel "Generieren Sie Symbole aus COM-Typbibliotheken" (System Journal 04/1999, S. 20) hatte ich Ihnen das Programm CoClassSyms vorgestellt. CoClassSyms lädt eine prozessinterne Server-DLL (wie zum Beispiel die SSAPI.DLL) und versucht, für diese DLL rudimentäre Debug-Symbole zusammenzustellen. Sie tut dies, indem sie von den angebotenen CoClass-Objekten Instanzen anlegt und dann deren vtable-Einträge und die dazugehörigen Methodennamen aus der Typbibliothek untersucht. Nachdem ich CoClassSyms auf SSAPI.DLL losgelassen hatte, verfügte ich über die Datei SSAPI.DBG, in der sich Symbole für die Methoden der Schnittstelle IVSSDatabase fanden.

Nun hatte ich endlich eine reale Chance, einen Haltepunkt hinreichend dicht an dem Punkt zu setzen, an dem das Problem auftrat. Die Symbole, die mein Programm generiert hatte, waren zwar in keiner Weise vollständig, aber sie umfassten zumindest die wichtige Methode VSSDatabase::Open. Nach dem Laden der SSAPI.DBG ins SoftIce setzte ich einen Haltepunkt auf VSSDatabase::Open und drückte in meinem Browser wieder auf die Refresh-Taste, damit der ASP-Code ausgeführt wird. Aber was soll ich Ihnen sagen... der Haltepunkt schlug nicht an. Irgendetwas lief definitiv nicht so, wie ich mir das vorstellte.

Der Debugger-Sumpf...

An diesem Punkt war es mein Wissen über meine Werkzeuge, das mir den Tag rettete. Wegen der Art und Weise, in welcher der SoftIce Haltepunkte verwaltet, wurde der Haltepunkt leider nicht in dem Prozess gesetzt, der die SSAPI.DLL benutzt. Damit der Haltepunkt funktioniert, muss ich den Prozess genau angeben, in dem er gesetzt werden soll. Und die Prozesskennung (PID) für diesen Prozess herauszufinden, erwies sich als weiteres Problem.

Bei der Einrichtung von IIS und Visual InterDev für das ferne Debugging musste ich die IIS-Option wählen, bei der jede IIS-Anwendung in einem separaten Host-Prozess läuft und nicht im Adressraum der IIS. Unter Windows 2000 wird dieser Host-Prozess von DLLHOST.EXE angelegt. Das ist dieselbe Datei, die auch die Prozesse für die COM+-Anwendungen liefert.

Mein Problem war nun, dass auf meiner Maschine nicht nur eine, sondern vier Instanzen von DLLHOST.EXE liefen. Eine dieser Instanzen führte meinen ASP-Code aus und war auch für die Aufrufe in die SSAPI.DLL verantwortlich. Die Frage war nur, welche. Ich habe zwar versucht, dies in der Microsoft Management Console mit dem Verwaltungsprogramm für die Komponentendienste herauszufinden, aber dieses Programm liefert keine Prozesskennungen für die DLLHOST.EXE-Instanzen, die auf meiner Maschine laufen. (Anmerkung der Redaktion: Die Prozesskennungen werden geliefert, aber man muss wissen, wo... Sie wählen in den Komponentenservices im linken Fenster den Eintrag COM+-Anwendungen. Rechts erscheint eine Liste aller COM+Anwendungen. Im Menüpunkt Ansicht wählen Sie dann Status. Sofern die IIS-Anwendung läuft, ist in der Liste im rechten Fenster die PID des Prozesses zu sehen. Läuft die Anwendung nicht, müssen Sie sie starten: Im IIS-Verwaltungsfenster mit der rechten Maustaste auf die Web-Anwendung klicken. Aus dem Kontextmenü den Punkt Im Browser anzeigen wählen. Der Prozess läuft weiter, auch wenn das Browser-Fenster geschlossen wird.)

Da dieser relativ geradlinige Lösungsansatz nicht funktioniert, probierte ich es eben anders herum. Ich wusste, dass die SSAPI.DLL von einer der DLLHOST-Instanzen geladen wurde. Das ModuleList-Programm aus meinem Artikel "Modullisten unter Win32" (System Journal 01/1999, S. 93) versucht, eine Liste aller geladenen DLLs aufzustellen, die im System zu finden sind. Für jede DLL listet es dann auch die Prozesse auf, von denen die DLL benutzt wird. Leider konnte ModuleList die SSAPI.DLL weder als geladen noch als benutzt ausweisen.

Als Ursache des Problem schien sich abzuzeichnen, dass die DLLHOST-Instanz auf einem anderen Konto läuft als der interaktive Anwender. Dadurch war ModuleList nicht in der Lage, ein Prozesshandle für die DLLHOST-Instanz zu öffnen, um deren Modulliste auszuwerten. Im Nachhinein kann ich nur sagen, dass ein Programm wie das HandleEx von SysInternals das Modul gefunden hätte, weil es in einer anderen Weise auf die Liste der geladenen DLLs zugreift.

Um die Sache endlich zum Abschluss zu bringen, entschloss ich mich, doch noch zum altbewährten Holzhammer zu greifen. Ich hielt SoftIce an und listete alle Prozesse auf. Dann versuchte ich mit einem anderen Befehl, den aktiven Speicherkontext der Reihe nach auf jede einzelne der DLLHOST-Instanzen umzuschalten. In jedem Kontext listete ich die geladenen DLLs auf. Und als ich auf die SSAPI.DLL stieß, wusste ich, dass ich endlich den richtigen Kontext gefunden hatte. Einmal im richtigen Speicherkontext hatte SoftIce kein Problem, den Haltepunkt wirksam zu setzen. Bei der erneuten Ausführung des ASP-Codes sprach der Haltepunkt endlich an.

Bevor ich Ihnen verrate, was ich fand, möchte ich noch darauf hinweisen, dass ich es mir eigentlich selbst unnötig schwer gemacht hatte. Nachdem ich mich auf SoftIce konzentriert hatte, ignorierte ich im Grunde die Möglichkeit, den WinDBG oder den Debugger von Visual C++ (MSDEV.EXE) einzusetzen. Am selben Ort, an dem Sie die IIS anweisen, den ASP-Code in einem separaten Prozess zu fahren (also in DLLHOST.EXE), können Sie die IIS auch anweisen, den separaten Prozess unter der Kontrolle eines Debuggers zu starten. Durch die schlichte Wahl dieser Option und dem Neustart der IIS wäre ich automatisch und mehr oder weniger sofort im WinDBG oder Visual Studio gelandet und hätte gleich mit dem richtigen Prozess arbeiten können, statt ihn erst umständlich zu suchen.

Zu meiner Verteidigung darf ich allerdings anführen, dass das Laden von .DBG-Dateien in den WinDBG und MSDEV gelegentlich eine trickreiche Angelegenheit ist und diese beiden Debugger meine SSAPI.DBG vielleicht gar nicht akzeptiert hätten. Allerdings hätte ich mir auch per Dump den Inhalt der SSAPI.DBG anzeigen lassen können, um die logische Adresse der VSSDatabase::Open zu ermitteln. Dann hätte ich diese logische Adresse in die lineare Adresse konvertieren und im WinDBG oder MSDEV einen Haltepunkt auf diese Adresse setzen können. Außerdem sind beide Debugger in der Lage, automatisch die Exporte von jeder DLL im Zielprozess zu laden. Letztlich brauchte ich nämlich die Exportinformationen und meine SSAPI.DBG-Symbole, um zu verstehen, was ich beim Gang durch den VSSDatabase::Open-Code sah.

Das Problem wird festgenagelt

Nachdem der Haltepunkt auf VSSDatabase::Open endlich angeschlagen hatte und die Symbole und Exporte geladen waren, konnte ich schließlich durch den SSAPI.DLL-Code gehen und das Problem suchen. Wie erwähnt, hat nicht nur VSSDatabase::Open eine unsinnige Fehlermeldung geliefert, sondern es fehlte auch noch der erwartete CreateFile-Aufruf. Darüber hinaus stand mir auch kein Quelltext zur Verfügung. Am Pokertisch gab es also nur mich, ein Disassemblerfenster und eine kleine Gruppe Symbole.

Statt nun langsam durch den Assemblercode zu pirschen und ihn solange zu schütteln, bis er einen Sinn ergab, begann ich die Untersuchung lieber auf einer höheren Ebene. Ich ging also nicht in jeden CALL hinein, den VSSDatabase::Open durchführte, sondern übersprang diese Aufrufe. Nach jedem übersprungenen CALL sah ich mir dessen Ergebniswert im EAX-Register an und überprüfte, ob es sich um einen Fehlercode handelte oder nicht. Außerdem sah ich mir die Befehle an, die auf den CALL folgten. Wenn sie noch auf dem Hauptausführungspfad lagen, war der CALL vermutlich erfolgreich.

Wenn die Befehle nach dem CALL dagegen zu einem Codeabschnitt sprangen, der VSSDatabase::Open schnell verließ, konnte ich daraus folgern, dass der Aufruf wohl fehlgeschlagen war. Nachdem ich nun den fraglichen CALL gefunden hatte, setzte ich einen Haltepunkt darauf und startete den ASP-Code erneut. Diesmal schlug der neue Haltepunkt an. Und diesmal ging ich in die Funktion hinein und begann wieder, nach der Stelle zu suchen, an der ein Fehler auftrat und die Funktion zu ihrem Aufrufer zurückkehrte.

In vielen Fällen hatte ich Glück und die Funktion, in die ich hineinging, wurde exportiert, so dass mir der Debugger ihren Namen zeigen konnte. Das erleichterte natürlich das große Rätselraten, welche Aufgabe die Funktion wohl hatte und ob sie erfolgreich war. Trotzdem ist die Suche nach einer Fehlerbedingung aber kein Vorgang, der sich mit wenigen Sätzen eindeutig beschreiben lässt. Man kommt recht oft zu falschen Schlüssen. Allerdings hat mir wohl die Übung, die ich mir im Laufe der letzten Jahre in solchen Dingen angeeignet hatte, die Arbeit erleichtert.

In der beschriebenen Weise arbeitete ich mich tiefer und tiefer in die SSAPI.DLL hinein und fand schließlich einen Win32-Aufruf, von dem ich wusste, dass er wohl schiefgegangen war, aber hätte klappen sollen. Bei der fraglichen Funktion handelte es sich um GetFileAttributes. Ich sah mir den Dateinamensparameter von GetFileAttributes genau an. Der Dateiname war mit dem identisch, den ich an VSSDatabase::Open übergeben hatte - es war immer noch der Name meiner SRCSAFE.INI-Datei auf dem Netzlaufwerk.

Nachdem ich wusste, dass GetFileAttributes nicht zum erwarteten Ergebnis kam, war es kein Problem mehr, sich davon zu überzeugen, dass der Rest des ausgeführten SSAPI.DLL-Codes nichts weiter zu tun hatte, als so schnell wie möglich zum Aufrufer von VSSDatabase::Open zurückzukehren. Trotzdem war mir noch nicht klar, warum GetFileAttributes eigentlich versagt hatte. Ich griff die Hypothese wieder auf, dass es wohl mit den Sicherheitsattributen zu tun hätte, mit denen die DLLHOST.EXE läuft. Aber wie konnte ich das überprüfen?

Wenn die Funktion GetFileAttributes fehlschlägt, setzt sie den LastError-Code. Leider macht sich der SSAPI.DLL-Code nicht die Mühe, GetLastError aufzurufen. Die gute Nachricht aber lautet, dass man sich relativ leicht behelfen kann. Ich hatte einfach das CPU-Fenster auf die GetLastError-Funktion in der KERNEL32.DLL gesetzt, die aus sage und schreibe 3 Befehlen besteht:

MOV EAX,FS:[18h] 
MOV EAX,DWORD PTR [EAX+34h] 
RET

Der GetLastError-Code schnappt sich das DWORD in der Position 0x34 des Thread-Umgebungsblocks, auf den FS:[18h] verweist. Es war kein Problem, dieses DWORD mit dem SoftIce auszulesen. So verschafft sich übrigens der Visual C++-Debugger den Wert für das ERR-Pseudoregister, das man sich im Watch- und im QuickWatch-Fenster anzeigen lassen kann.

Wie ich herausfand, versagt GetFileAttributes mit dem GetLastError-Code 5. Mit Hilfe des Fehlerprogramms (ERRLOOK.EXE) aus dem Visual Studio konnte ich schnell ermitteln, dass der Fehlercode 5 für einen verweigerten Zugriff steht. Damit war meine Hypothese bestätigt, dass das Problem mit den Zugriffsrechten zu tun habe und nicht mit irgendwelchen falsch angegebenen Parametern. Außerdem war damit klar, dass die Fehlermeldung aus der SSAPI.DLL nicht mehr taugt als die Wahlversprechen vom letzten Jahr.

Das Problem mit den Zugriffsrechten wurde ich ziemlich schnell dadurch los, dass ich die IIS meine DLLHOST-Instanz mit meinem interaktiven Anwenderkonto starten ließ. Ein weiterer Beweis dafür, dass meine ursprüngliche Hypothese über die Rechte des benutzten Kontos richtig war. Dies reißt natürlich ein riesiges, ja geradezu klaffendes Sicherheitsloch auf. Käme ich auf die Idee, daraus ein kommerzielles Programm zu machen, müsste ich mir zweifellos einen sichereren Zugriffsweg auf die Visual SourceSafe-Datenbank ausdenken.

(Anmerkung der Redaktion: Um es noch einmal klar zu machen: Die IIS starten einen ASP-Prozess und jeden im IIS kreierten Thread standardmäßig mit bestimmten von den IIS eingerichteten Anwenderkonten. Es handelt sich um die Konten IWAM_Machine (für den Prozess) und IUSR_Machine (für den Thread). Machine steht für den Namen der Maschine, auf der die IIS installiert sind. Deshalb verfügt ein Anwender einer IIS-Anwendung nicht über seine üblichen Rechte, selbst wenn der Browser im lokalen Netzwerk oder gar auf dem Server-Rechner läuft. Die Lösung für das Szenario würde heißen: 1. einen eigenen SourceSafe-Team-Account einrichten, 2. dem Account die erforderlichen Rechte für den Zugriff auf die SourceSafe-Dateien geben, 3. die ASP-Anwendung so einrichten, dass sie mit dem Konto dieses Accounts gestartet wird.)

Und die Moral von der Geschichte? Wenn Sie mit dem Kopf gegen eine Wand stoßen, dann gehen Sie ganz langsam einen Schritt zurück und denken Sie in aller Ruhe über das Problem nach. Überlegen Sie sich, welche potentiellen Probleme sich ergeben könnten und wie man diese Hypothesen beweisen oder widerlegen könnte. Überlegen Sie sich auch, welche Werkzeuge Ihnen eigentlich zur Verfügung stehen. Vielleicht hilft ihnen ein einzelnes Werkzeug oder ein einzelner "toller Trick" nicht viel weiter, aber richtig kombiniert, liefern sie vielleicht trotzdem die entscheidenden Hinweise auf das eigentliche Problem, dem Sie dann mit dem richtigen Werkzeug auf die Spur kommen können. Und seien Sie hartnäckig. Geradezu penetrant stur. Aber nicht bis zu dem Punkt, an dem Sie sich die einfacheren Lösungswege verbauen.