woensdag 19 september 2007

Adv. debugging: hook all exceptions!

In mijn vorige artikel schreef ik hoe je een “stack trace” kunt maken. Maar soms is dat niet genoeg. Je krijgt bijvoorbeeld een ongrijpbare “Catastrophic failure” of de stack trace geeft niet precies de echte exceptie weer. Dit komt omdat een stack trace niet goed werkt als deze in een try..except (of try..catch in C) afgevangen wordt. Als je namelijk onderstaande code probeert:


try
raise Exception.Create(’hier gaat het fout’);
except
raise; //deze regel geeft stack trace weer
end;


dan geeft de stack trace niet de 2e regel (Exception.Create) aan als de plek waar het fout ging, maar de 4e, waar de exception opnieuw geraised wordt. Nu is het in het bovenstaande voorbeeld wel duidelijk waar het fout ging, maar niet als de code groter is en geneste procedures heeft.

Ik heb dit namelijk zelf vaak meegemaakt. Dan dacht ik: ik heb een mooie exception handler met stack trace gebouwd, nu kan ik alle fouten precies lokaliseren en oplossen. Maar kwam ik menigmaal bij een stack trace uit op een try..except combinatie… Dan weet je nog niet waar het fout ging (vooral als het geen code van jezelf is, waar je de try..except kunt aanpassen).

Gelukkig kwam ik een andere JEDI unit tegen: JclHookExcept.pas. Hiermee is het mogelijk om ALLE exceptions binnen te krijgen! Het maakt namelijk een exception hook: de “RaiseException” functie van “kernel32.dll” wordt omgeleid (detouring, hier hoop ik ook een stuk over te schrijven) naar een eigen functie. Omdat hier alle fouten binnen komen, voordat ze door een try..except afgevangen worden, kun je deze fouten eenvoudig loggen!. Als je dan vaak smerige trucs toepast zoals:

try
i := StrToInt(string);
except
i := -1;
end;

dan val je gauw door de mand: de Econversion exception komt dan netjes via de exception hook binnen :-).

Wat ik vooral handig vind met deze exception hook, is dat je “verborgen” fouten in bijvoorbeeld dll’s kunt traceren, of “Catastrophic failures” kunt achterhalen. Voor dit laatste heb ik een voorbeeld gebruikt van een QC posting: http://qc.borland.com/wc/qcmain.aspx?d=6069
Hieraan heb ik de volgende code toegevoegd:



uses
JclDebug, JclHookExcept;



procedure AnyExceptionNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean);
var
sError:string;
begin
if ExceptObj is Exception then
begin
sError := Format(’%s at adress %p: %s’,
[ExceptObj.ClassName, ExceptAddr,
(ExceptObj as Exception).Message]);

if OSException then
sError := ‘OS ‘ + sError;
sError := ‘HOOK: ‘ + sError;
MessageDlg(sError, mtError, [mbOK], 0);
end;
end;

initialization
JclStartExceptionTracking;
JclAddExceptNotifier(AnyExceptionNotify);

Dit geeft bij het uitvoeren de volgende afgehandelde fout:
—————————
Error
—————————
HOOK: Exception at adress 0047D639:
IErrorServer.RaiseServerError
Error at server side
—————————
OK
—————————

Normaal zou je alleen “Catastrophic failure” krijgen!

Geen opmerkingen: