...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

Symbole im Debugger gezielt nicht laden

In diesem Blogpost beschreibe ich jetzt mal, wie man für den Debugger von Visual Studio verhindern kann, dass bestimmte Symbole geladen werden. Mein Problem ist, dass ich immer wieder vergesse, wie der Registry Key heisst, den man da anlegen/bearbeiten muss, und welche DLLs ich da am Besten eintrage, und dieses Problem wiederholt sich bei mir bei jeder Entwicklungsbox, die ich aufsetze, insofern bin ich mit diesem Blogpost mein eigenes und bestes Zielpublikum.

Das Problem

Wenn man einen Pfad zum Laden von Symbolen gesetzt hat, dann ist das normalerweise eine schöne Sache. Der Debugger versucht, von jeder DLL im Prozessraum des Debuggees die Symbole zu laden, und man erhält zumindest schöne Callstacks beim Debuggen. Bei einem Symbol Store für die eigenen Binaries sieht man auch lokale Variablen und ihre Werte sowie den dazugehörigen Code, notfalls automatisch ausgecheckt aus dem Versionskontrollsystem (wie man sich sowas aufbaut, habe ich vor langer Zeit hier beschrieben). Damit ist die Welt schön, denn beispielsweise von den DLLs des Betriebssystems bekommt man die Symbole automatisiert von Microsofts Symbol Server runtergeladen.

Blöd wird das Ganze aber, wenn im Prozessraum eine DLL auftaucht, für die man partout keine Symbole bekommt, denn der zum Scheitern verurteilte Versuch, für solche DLLs die Symbole zu finden und zu laden, braucht sehr viel Zeit, und das Leben ist bekanntlich ja schon kurz genug. Und beim Debuggen warten zu müssen ist eine PITA. Historisch zählten dazu schon ab und zu mal DLLs von Microsoft, aber notorisch bekannt dafür sind eigentlich die Hersteller von Grafikkarten, die es fertig bringen, in jeden Prozessraum ihre verschissenen DLLs zu injizieren, oder aber irgendwelche third-party Shell-Extensions. Da ich zuhause bekannterweise einen Subversion-Server einsetze und auf meinen Entwicklungsboxen daher das wunderbare Tortoise-SVN zum Einsatz kommt, ist Tortoise-SVN ein wunderbares Beispiel für dieses Problem. Am Besten manifestiert sich das Problem, wenn man beim Debuggen eine File-Open Box öffnet. Dann wird alles an Shell-Extensions geladen, was nicht bei drei auf den Bäumen ist, unter anderem auch Tortoise-SVN. Auf meiner guten alten SAAVIK, einer x64-Box von 2005, dauert das dann 22s im Debugger, bis die File-Open Box dargestellt und bedienbar ist. Der Debugger schaut für jede zu ladenden DLL in das Binary rein und versucht, das Symbol, also die pdb-Datei, zu ermitteln und dann zu laden. Wenn er sie nicht über einen absoluten Pfad findet, klappert er der Reihe nach die konfigurierten Symbol Stores ab und das dauert dann üblicherweise jedesmal ein paar Sekunden, bis er bei der Nachfrage beim Microsoft Symbol Store (oder dem eigenen Symbol Store) die Antwort bekommt: "Nein die Datei TortoiseSVN.pdb ist nicht von uns".

Die Lösung

Die Lösung besteht darin, diejenigen DLLs zu identifizieren, für die man ohnehin keine Symbole bekommt, und die dann von vornherein vom Laden auszuschliessen. Wenn Symbole lokal gefunden werden, dann sieht man sowas im Debugger-Output:

'abcd.exe': Loaded 'C:\foobar\bin\Debug\xyz.dll', Symbols loaded.

oder aber, wenn die Symbole  beispielsweise vom MS Symbol Store kommen (und daher die Debuginformationen 'stripped' sind, also nur Funktionsnamen beinhalten):

'abcd.exe': Loaded 'C:\Windows\SysWOW64\ntdll.dll', Symbols loaded (source information stripped).

Wenn aber die Symbole nicht gefunden werden, dann sieht das lediglich so aus (also ohne irgendwas mit "Symbols loaded"):

'abcd.exe': Loaded 'C:\Program Files (x86)\TortoiseSVN\bin\TortoiseSVN.dll'

Es gilt also, diese DLLs im Debugger-Output zu identifizieren und deren Symboldateien zu ermitteln. In der Regel heissen die Symboldateien so wie die DLLs, nur mit der Extension .pdb. Um aber auf Nummer Sicher zu gehen, schaut man sich jede DLL in einem Hexeditor an und sucht nach dem String ".pdb". Üblicherweise erhält man nur einen Treffer und das ist dann der Name der Symboldatei, nach der der Debugger sucht. Hat man den Namen der Datei, dann legt man den Key HKEY_CURRENT_USER\Software\Microsoft\Symbol Server\Exclusions an (sofern der noch nicht existiert) und ergänzt dort einen REG_SZ-Value mit dem Namen der Symboldatei. Bei mir schaut das dann beispielsweise so aus:

[HKEY_CURRENT_USER\Software\Microsoft\Symbol Server\Exclusions]
"TortoiseOverlays.pdb"=""
"TortoiseStub.pdb"=""
"TortoiseSVN.pdb"=""
"libapr_tsvn.pdb"=""
"libaprutil_tsvn.pdb"=""
"intl3_tsvn.pdb"=""
"slc.pdb"=""
"WMVCORE.pdb"=""
"SPYHK55.pdb"=""
"TortoiseShell.pdb"=""
"libsvn_tsvn32.pdb"=""
"intl3_tsvn32.pdb"=""
"libsasl.pdb"=""


Wenn man damit dann fertig ist, beendet man Visual Studio und startet es neu, und ab jetzt werden die angegebenen Symbole ignoriert und man kann pfeilschnell debuggen. Auf SAAVIK dauert damit das Öffnen einer File-Open Box dann unter 5s.

Als Referenz ist ein Regfile mit meinen Einstellungen von oben hier zu finden. Einfach die Datei in die lokale Registry reinmergen und dann Rock'n Roll. Debuggt man dann unter einem anderen User muss man das dann für diesen User auch nochmal tun.

Happy debugging and symbol loading, you know, I am.

 

Update (03/14/2013): Registry-Skript und dessen Listing angepasst für die aktuelle Version des Tortoise-SVN-Clients

PREfast und third-party-code

Wenn man Third-Party-Code einsetzt, hat man sehr oft nicht den Luxus, an dem so lange rumschrauben zu können, bis er sauber durch PREfast durchläuft. Ein neues Release dieses Third-Party-Codes, und die Arbeit fängt wieder von vorne an. SQLite ist so ein Fall. Ich benutze in einer Reihe von Projekten die SQLite Amalgamation, das ist ein einziges riesiges C-File namens sqlite3.c mit dem kompletten Code von SQLite drinne. Damit ich Projekte, die sqlite3.c benutzen, auch mit PREfast übersetzen kann und dabei nicht von den Gazillionen von PREfast-Warnungen aus der sqlite3.c, an denen ich eh nichts ändern kann/sollte, erschlagen werde, injiziere ich beim Übersetzen von sqlite3.c ein Headerfile von mir. Das Injizieren von Headerfiles ist ein ziemlich fieses mächtiges Feature von Visual C, das seit Urzeiten in diesem Produkt drinne ist, das aber auffällig wenige Leute kennen. Das ganze geht mit dem Compilerschalter /FI, mit dem man den Namen eines Headerfiles angibt, das gelesen werden soll, und zwar vor allen Headerfiles, die von dem zu übersetzenden Sourcefile inkludiert werden (sogar vor einem eventuell einzulesenden precompiled header). Damit kann man also allerhand fiesen Schabernack treiben und Header inkludieren, ohne Sourcefiles zu ändern. Speziell im Fall sqlite3.c habe ich ein Headerfile namens sqltpref.h, bei dem ich über die include-Pfade dafür sorge, dass es in den relevanten Projekten automatisch über Angabe des Dateinamens gefunden wird, und das in etwa folgendermassen ausschaut:

#ifndef SQLTPREF_H__
#define SQLTPREF_H__

#ifdef _PREFAST_
///
/// here we collect all those warnings that prefast
/// shows if we compile the sqlite amalgamation:
///
#pragma warning (disable:6386 6326 6001 6246 6011 6292 6244 6385 6239 6235 6328 6295)

#endif /// _PREFAST_


#endif /// SQLTPREF_H__

Dann muss ich nur noch in jedem Projekt für die Datei sqlite3.c mit dem Compilerschalter /FI diesen Header injizieren, also durch Angabe von

/FI"sqltpref.h"

auf der Kommandozeile des Compilers. In Visual Studio geht das sehr einfach, indem man die Datei sqlite3.c im Baum vom Solution Explorer anwählt, sich die Properties anzeigen lässt und zur Anzeige der Kommandozeile navigiert unter "Configuration Properties"-"C/C++"-"Command Line". Dort trägt man einfach unter Additional Options den /FI"sqltpref.h" ein und fertig. So sieht das dann in echt aus:

Das war's, damit lässt sich nun sqlite3.c auch dann problemlos übersetzen, wenn mit PREfast übersetzt wird, und bleibt dabei mucksmäuschenstill. Happy PREfasting, you know, I am!

PREfast und MFC

Da meine Ansprüche an meinen eigenen Code dergestalt sind, dass er ungestreift mit PREfast übersetzt werden können muss, habe ich bei meinen MFC-basierten Anwendungen immer ein kleines Problem: Die Headerfiles der MFC sind nicht PREfast-compliant und an diesen Headern schraubt man selber nix rum. Daher benutze ich meist folgenden kleinen Trick: Während in der stdafx.h die MFC-Headerfiles eingelesen werden, schalte ich die notorischen PREfast-Warnungen der MFC-Headers aus und schalte sie hinterher ein. Das tue ich nur, wenn auch ein Übersetzungslauf mit PREfast ansteht, denn ansonsten erhalte ich Warnungen für nicht-existierende Warnungsnummern (nämlich die aus PREfast). Das ganze sieht also in einer stdafx.h von mir etwa so aus:

 

#ifdef _PREFAST_
#pragma warning (push)
#pragma warning (disable: 6387)
#endif ///_PREFAST_

#include <afxwin.h>        
#include <afxext.h>

/// include other stuff from MFC, yaddaa, yaddaa ...

#ifdef _PREFAST_
#pragma warning (pop)
#endif ///_PREFAST_

Durch das Makro _PREFAST_, das automatisch definiert ist, wenn ein Übersetzungslauf mit PREfast stattfindet, kann man somit sehr geschickt ganz selektiv die Warnung 6387 ausschalten.

Happy PREfasting, you know, I am!

Quote of the day

"Das ist wie Pädagogisch, nur ohne Füße"

Karl Farrent am 21.3.2012 zur Frage nach der Bedeutung des Worts "Agogisch"

Mein Kobold bei der Arbeit

Seit etwas mehr als einer Woche bin ich jetzt der stolze Besitzer eines vollautonomen Saugroboters vom Typ Vorwerk Kobold VR100. Da gehört es sich, dass ich den Knaben mal bei der Arbeit beobachte. Das Bild während der Fahrt hat meine Digitalkamera gefilmt, die ich mit Gaffer-Tape vorne an den Roboter angeklebt habe. Das Picture-In-Picture wurde mit meinem Handy vom Tisch aus aufgenommen, auch provisorisch mit Gaffer-Tape angebracht. Ich hoffe, dass ausreichend viele Betrachter in der Lage sind, die gelungene Musikauswahl zu goutieren (man muss den Film dazu kennen).

Also: Unbedingt die Lautsprecher einschalten beim Videokucken.





<< 1 ... 6 7 8 9 10 11 12 13 14 15 16 ... 45 >>