The offsets used in this blog are correct for Delphi XE2, and this information is only valid for x86. You will have to plug in other values for other versions of Delphi. You can find more details in my earlier Delphi WinDbg blog articles:
The following WinDbg command will return a list of all Delphi exception records located within the stacks of each thread in the process. Delphi uses the exception code 0EEDFADE:
~*e s -d poi(@$teb+8) poi(@$teb+4) 0EEDFADE
If you just wanted to do the current thread, you would run:
s -d poi(@$teb+8) poi(@$teb+4) 0EEDFADE
What is teb
? It’s the Thread Environment Block. The data at teb+8
and teb+4
are the current bottom of the stack and the top of the stack, respectively.
For example, when looking at a crash dump we received, we were able to spot exceptions in two different threads:
0:000&> ~*e s -d poi(@$teb+8) poi(@$teb+4) 0EEDFADE 0012e9ec 0eedfade 00000000 00000001 00000000 ................ 0012ef6c 0eedfade 00000003 00000000 7586d36f ............o..u 0012f3d4 0eedfade 00000001 00000000 7586d36f ............o..u 0012f42c 0eedfade 00000001 00000007 0012f43c ............<... 0012f6ec 0eedfade 00000003 00000000 7586d36f ............o..u 0012fb70 0eedfade 00000001 00000000 7586d36f ............o..u 0012fbc8 0eedfade 00000001 00000007 0012fbd8 ................ 04a6f06c 0eedfade 00000003 00000000 7586d36f ............o..u 04a6f4e8 0eedfade 00000001 00000000 7586d36f ............o..u 04a6f540 0eedfade 00000001 00000007 04a6f550 ............P... 04a6f72c 0eedfade 00000003 00000000 7586d36f ............o..u 04a6fb94 0eedfade 00000001 00000000 7586d36f ............o..u 04a6fbec 0eedfade 00000001 00000007 04a6fbfc ................
This has returned exception records in two different thread stacks (0012* and 04a6*). We can see a number of potential exception records; some of these are not really records (because the 0EEDFADE
value is not only used in the EXCEPTION_RECORD
structure; it is also passed as a parameter to the RaiseException
function among others). However, if the 3rd DWORD
shown is 0
, then this is probably a real exception record, and not part of a function call. Why this? Because EXCEPTION_RECORD
's third member is a pointed to a nested exception record, which it seems is always set to NULL
in Delphi.
To examine the exception record run the following command:
0:000>.exr 0012ef6c ExceptionAddress: 7586d36f (KERNELBASE!RaiseException+0x00000058) ExceptionCode: 0eedfade ExceptionFlags: 00000003 NumberParameters: 7 Parameter[0]: 005f2d4c Parameter[1]: 08c92148 Parameter[2]: 800a0e7f Parameter[3]: 005f2d4c Parameter[4]: 0299ef64 Parameter[5]: 0012f48c Parameter[6]: 0012f458
To confirm that this is a real Delphi exception check two things:
- The
ExceptionAddress
should point to an address within theRaiseException
function (that actual address may vary between versions of Windows). - It should have 7 parameters:
0: code address where the exception was raised 1: address of the Exception object 2-6: additional data relating to the exception type and stored registers
Let's examine the Exception object:
0:000> dd 08c92148 08c92148 004c2134 08cbb20c 0012ee54 800a0e7f 08c92158 08cf3f94 080cb690 0000002a 0054e1a4 08c92168 08c99fec 0000005e 08134330 00000000 08c92178 00000000 00000024 00000032 00000100 08c92188 0000001a 00000003 0000000b 4473624f 08c92198 54657461 00656d69 0000002a 0054e1a4 08c921a8 08c99fec 0000005d 08134314 00000000 08c921b8 00000000 00000023 00000032 00000100
From here, we can use the same spelunking techniques as in my previous WinDbg articles:
0:000> da poi(poi(8c92148)-38)+1 004c214f "EOleException.!L" 0:000> du poi(8c92148+4) 08cbb20c "Operation cannot be performed wh" 08cbb22c "ile executing asynchronously"
You can also skip examining the exception record if you want, with shortcuts such as:
da poi(poi(poi(0012ef6c+18))-38)+1; du poi(poi(0012ef6c+18)+4)
How about working with nested exceptions? Take the following scratch program:
unit NestedExceptions; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} type EWhatAMess = class(Exception); EAnotherError = class(Exception); ESomeError = class(Exception) private FExtraData: string; public constructor Create(const Message, ExtraData: string); end; procedure HandleThisOneToo; begin raise EWhatAMess.Create('What a mess'); end; procedure HandleIt; begin try raise EAnotherError.Create('Another Error Message'); except on E:EAnotherError do HandleThisOneToo; end; end; procedure TForm1.Button1Click(Sender: TObject); begin try raise ESomeError.Create('Some Error Message', 'Here''s some extra data'); except on E:ESomeError do HandleIt; end; end; { ESomeError } constructor ESomeError.Create(const Message, ExtraData: string); begin FExtraData := ExtraData; inherited Create(Message); end; end.
Build this program, then load it up in WinDbg. You'll need to enable the event filter for 0EEDFADE
as per my previous blog. Click the bad, bad button and watch as the exceptions are thrown. For the first two exceptions, just g
. On the third exception, we'll spelunk with the search technique.
0:000> ~*e s -d poi(@$teb+8) poi(@$teb+4) 0EEDFADE 0018e814 0eedfade 00000001 00000000 754ab9bc ..............Ju 0018e86c 0eedfade 00000001 00000007 0018e87c ............|... 0018e9ac 0eedfade 00000003 00000000 754ab9bc ..............Ju 0018ee60 0eedfade 00000001 00000000 754ab9bc ..............Ju 0018eeb8 0eedfade 00000001 00000007 0018eec8 ................ 0018f014 0eedfade 00000003 00000000 754ab9bc ..............Ju 0018f4c8 0eedfade 00000001 00000000 754ab9bc ..............Ju 0018f520 0eedfade 00000001 00000007 0018f530 ............0...
The first instance of an exception in the stack will have the Exception Flag 00000001
. This is the one we are interested in, in each case. Let's look at them:
0:000> da poi(poi(poi(0018e814+18))-38)+1; du poi(poi(0018e814+18)+4) 0051138f "EWhatAMess" 02548654 "What a mess" 0:000> da poi(poi(poi(0018ee60+18))-38)+1; du poi(poi(0018ee60+18)+4) 00511437 "EAnotherErrorH.Q" 0256c36c "Another Error Message" 0:000> da poi(poi(poi(0018f4c8+18))-38)+1; du poi(poi(0018f4c8+18)+4) 00511523 "ESomeErrorJ" 02581d3c "Some Error Message"
You may find that some exception records are no longer valid as they can be overwritten over time. This happens for nested exceptions, unfortunately, if you don't actually break on the exception in WinDbg before it is handled in the application (which will typically be the case if you attach a debugger to a process with an exception dialog visible). In this situation, only the final exception record will point to a live Exception
object. You may notice that innermost exception had a little bit of extra data. How do we pull that out? Let's look at the ESomeError
object in memory:
0:000> dd poi(18f4c8+18) 02548698 005114d4 02581d3c 00000000 00000000 025486a8 00000000 00000000 0256c32c 00000000 025486b8 00000073 00000000 00000000 00000000 025486c8 00000000 00000000 00000000 00000000 025486d8 00000000 00000000 00000000 00000000 025486e8 00000000 00000000 00000000 00000000 025486f8 00000000 00000000 00000000 00000000 02548708 00000000 00000000 00000000 00000000
What have we got here? Breaking that data down, we have:
02548698 005114d4 Pointer to class +0000004 02581d3c Pointer to Exception.FMessage Unicode string +0000008 00000000 Exception.FHelpContext +000000C 00000000 Exception.FInnerException +0000010 00000000 Exception.FStackInfo +0000014 00000000 Exception.FAcquireInnerException
That InnerException data would be useful — but it is not commonly used, yet. And then we have the data for ESomeError
:
+0000018 0256c32c ESomeError.FExtraData
Examining that:
0:000> du 256c32c 0256c32c "Here's some extra data"
And there you have it. Knock yourself out!
1 thought on “Locating Delphi exceptions in a live session or dump using WinDbg”