Delphi access violation inside TLanguages utility - delphi

For some reason, trying to create a TLanguages object provided by the SysUtils header by using the singleton or by calling the constructor directly is causing trouble in the wild, where some users report this error (X varies):
Access violation at address X. Write of address X (at address X)
... when the following seemingly innocent line of code is executed:
TLanguages.Create;
To clarify, this is not related to context. I can put this line in any place I like (as the only line of code of an empty program for example), but the problem remains.
The weird part is that this class is part of Delphi's standard headers, which should not fail (right?).
constructor TLanguages.Create;
type
TCallbackThunk = packed record
POPEDX: Byte;
MOVEAX: Byte;
SelfPtr: Pointer;
PUSHEAX: Byte;
PUSHEDX: Byte;
JMP: Byte;
JmpOffset: Integer;
end;
var
Callback: TCallbackThunk;
begin
inherited Create;
Callback.POPEDX := $5A;
Callback.MOVEAX := $B8;
Callback.SelfPtr := Self;
Callback.PUSHEAX := $50;
Callback.PUSHEDX := $52;
Callback.JMP := $E9;
Callback.JmpOffset := Integer(#TLanguages.LocalesCallback) - Integer(#Callback.JMP) - 5;
EnumSystemLocales(TFNLocaleEnumProc(#Callback), LCID_SUPPORTED);
end;
The constructor attempts to use a member function as the EnumSystemLocales callback, which seems to be causing the crashes, because copying the TLanguages.LocalesCallback function to global scope and passing that to EnumSystemLocales works perfectly fine.
The struct contains the following Intel x86 assembly, where each item is given by its opcode:
pop edx
mov eax Self
push eax
push edx
jmp JmpOffset
Can anyone explain how the trick works and tell me why it's not working as expected?

It appears to be a known issue with older Delphi versions, related to DEP, as I guessed in comments to the question. It's clear that the code in the RTL cannot work when DEP is enabled.
Here's a link to confirm the theory: http://codecentral.embarcadero.com/Item/23411
Although that CodeCentral article includes code to fix the problem in Delphi 5, it looks like it will work in Delphi 7 too. The fix works by hooking the SysUtils.Languages function. So make sure you always use that rather than calling TLanguages.Create yourself, for obvious reasons.

Related

Access Violation - how do I track down the cause?

I'm getting an access violation when I close a form in my application. It seems to happen only after I have access a database a couple of times, but that doesn't seem to make sense.
I have traced through and put outputdebugstring messages in all the related OnDestroy() methods, but the AV appears to be outside of my code.
This is the text of the message:
Access violation at address 00405F7C in module
'MySoopaApplication.exe'. Read of address 00000008.
How do I find where in the application 00405F7C is?
What tools are available in Delphi 10.1 Berlin to help me with this?
Edit: added a bit more info ... when clicking "Break" the IDE always takes me to this piece of code in GETMEM.INC:
#SmallPoolWasFull:
{Insert this as the first partially free pool for the block size}
mov ecx, TSmallBlockType[ebx].NextPartiallyFreePool
Further edit: well, I found the culprit, though I can't honestly say that the debug tools got me there - they just seemed to indicate it wasn't in my code.
I had used code from the net that I used to find the Windows logged in user - this is it:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen);
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
once I had replaced the code with a simple result := '12345' the AVs stopped. I have no changed it to this code:
function GetThisComputerName: string;
var
nSize: DWord;
CompName: PChar;
begin
nSize := 1024;
GetMem(CompName, nSize);
try
GetComputerName(CompName, nSize);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
which seems to work and, as a bonus, doesn't cause AVs.
Thanks for your help, much appreciated.
Under Tools|Options in the IDE go to Embarcadero Debuggers | Language Exceptions and make sure Notify on Language Exceptions is checked. Also under Project Options | Compiling, make sure Debugging | Use debug DCUs is checked.
Allow the exception to happen, then go to View | Debug Windows | Call stack and you should be able to see exactly where it occurred. The fact that it occurs after db access is probably because that causes some object to be created which generates the AV when it is destroyed. Possibly because it is being Free()ed twice.
If that doesn't solve it, you may may need an exception-logging tool like madExcept mentioned by DavidH.
Read of address 00000008.
The fact that this address is a low number is suggestive of it being the address of a member of an object (because they are typically at low offsets from the base address of the object).
How do I find where in the application 00405F7C is?
With your app running, in the IDE go to Search | Go to Address. This should find it if the exception is in your application and not in some related module like a .DLL it is using. The menu item is enabled once the application is running in the IDE and stopped at a breakpoint. Also there is a compiler command line switch to find an error by address.
Others have explained how to diagnose an AV.
Regarding the code itself, there are issues with it:
Most importantly, you are not allocating enough memory for the buffer. GetMem() operates on bytes but GetComputetName() operates on characters, and in this case SizeOf (Char) is 2 bytes. So you are actually allocating half the number of bytes that you are reporting to GetComputerName(), so if it writes more than you allocate then it will corrupt heap memory. The corruption went away when you over-allocated the buffer. So take SizeOf(Char) into account when allocating:
function GetThisComputerName: string;
var
CompName: PChar;
maxlen: cardinal;
begin
maxlen := MAX_COMPUTERNAME_LENGTH +1;
GetMem(CompName, maxlen * SizeOf(Char)); // <-- here
try
GetComputerName(CompName, maxlen);
Result := CompName;
finally
FreeMem(CompName);
end;
end;
In addition to that:
you are ignoring errors from GetComputerName(), so you are not guaranteeing that CompName is even valid to pass to Result in the first place.
You should use SetString(Result, CompName, nSize) instead of Result := CompName, since GetComputerName() outputs the actual CompName length. There is no need to waste processing time having the RTL calculate the length to copy when you already know the length. And since you don't check for errors, you can't rely on CompName being null terminated anyway if GetComputerName() fails.
You should get rid of GetMem() altogether and just use a static array on the stack instead:
function GetThisComputerName: string;
var
CompName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
nSize: DWORD;
begin
nSize := Length(CompName);
if GetComputerName(CompName, nSize) then
SetString(Result, CompName, nSize)
else
Result := '';
end;

How to get the address of a virtual, overloaded procedure for a given class

A bit of context... I need to write a fix for a bug in the VCL's TField.CopyData routine.
Not being a huge fan of recompiling the VCL, I usually opt for method hooks.
Usually, it's pretty straightforward, write the replacement method and hook it in place of the original one:
HookProc(#TMenu.ProcessMenuChar, #TPatchMenu.ProcessMenuChar, Backup);
But this time, the method is virtual and overloaded. I can't seem to find a reliable way to get the right method address.
If it wasn't overloaded, I could simply use #TField.CopyData and it would work, but that isn't guaranteed to give the right address for an overloaded function.
If it wasn't virtual, I could extend the method described here and do the following
type
TCopyDataMethod = procedure(Source, Dest : TValueBuffer) of object;
procedure DoHook;
var vOldMethod : TCopyDataMethod;
begin
vOldMethod := TField(nil).CopyData;
HookProc(TMethod(vOldMethod).Code, #TSomeClass.NewMethod, Backup);
end;
But that gives an access violation (It is using the nil reference to lookup in the VMT for the right CopyData address).
I tried various syntax that all gave me "incompatible types" at compile time.
I did come up with a solution (posted as answer), but it is less than ideal.
Inspired from David Heffernan's answer, here's the solution I was looking/hoping for.
type
TCopyDataMethod = procedure(Source, Dest : TValueBuffer) of object;
procedure Proc;
var VMT : NativeInt;
vMethod : TCopyDataMethod;
begin
VMT := NativeInt(TField);
vMethod := TField(#Vmt).CopyData;
uOriginalAddress := #vMethod;
[...]
end;
I believe this is as straightforward as it can get.
The only solution I've found to this problem is to create an instance of the class
procedure Proc;
var vMethod : TCopyDataMethod;
vFieldOrig : TField;
[...]
begin
vFieldNew := nil;
vFieldOrig := TField.Create(nil);
try
vMethod := vFieldOrig.CopyData;
uOriginalAddress := #vMethod;
[...]
finally
vFieldOrig.Free;
end;
end;
Hopefully, there is a more elegant solution.
If you know the index in the VMT, you can get the address directly from the class. If you look at a call to CopyData under the debugger you see the following:
Project1.dpr.19: THackField(Field).CopyData(TValueBuffer(nil), TValueBuffer(nil));
0053886B 33C9 xor ecx,ecx
0053886D 33D2 xor edx,edx
0053886F A104345400 mov eax,[$00543404]
00538874 8B18 mov ebx,[eax]
00538876 FF93A4000000 call dword ptr [ebx+$000000a4]
Here we can see that in my XE7, the method point is at an offset of $000000a4 bytes from the VMT. And the class is a pointer to the VMT.
So, you can get your method address like this:
Pointer(NativeUInt(TField) + $00a4)
I expect that you could come up with a more elegant solution using enhanced RTTI. But you typically do this when looking to patch bugs in specific library versions. So you hard code values for specific versions, and throw a compiler error otherwise. At least, that's what I do!
Having said all of this, there's nothing to be ashamed of with instantiating an instance at startup as you demonstrate in your answer. So long as that has no unwanted side effects I can't see any real downside.

Wrong code when combining anonymous and nested procedures

I've got some unexpected access violations for Delphi code that I think is correct, but seems to be miscompiled. I can reduce it to
procedure Run(Proc: TProc);
begin
Proc;
end;
procedure Test;
begin
Run(
procedure
var
S: PChar;
procedure Nested;
begin
Run(
procedure
begin
end);
S := 'Hello, world!';
end;
begin
Run(
procedure
begin
S := 'Hello';
end);
Nested;
ShowMessage(S);
end);
end;
What happens for me is that S := 'Hello, world!' is storing in the wrong location. Because of that, either an access violation is raised, or ShowMessage(S) shows "Hello" (and sometimes, an access violation is raised when freeing the objects used to implement anonymous procedures).
I'm using Delphi XE, all updates installed.
How can I know where this is going to cause problems? I know how to rewrite my code to avoid anonymous procedures, but I have trouble figuring out in precisely which situations they lead to wrong code, so I don't know where to avoid them.
It would be interesting to me to know if this is fixed in later versions of Delphi, but nothing more than interesting, upgrading is not an option at this point.
On QC, the most recent report I can find the similar #91876, but that is resolved in Delphi XE.
Update:
Based on AlexSC's comments, with a slight modification:
...
procedure Nested;
begin
Run(
procedure
begin
S := S;
end);
S := 'Hello, world!';
end;
...
does work.
The generated machine code for
S := 'Hello, world!';
in the failing program is
ScratchForm.pas.44: S := 'Hello, world!';
004BD971 B89CD94B00 mov eax,$004bd99c
004BD976 894524 mov [ebp+$24],eax
whereas the correct version is
ScratchForm.pas.45: S := 'Hello, world!';
004BD981 B8B0D94B00 mov eax,$004bd9b0
004BD986 8B5508 mov edx,[ebp+$08]
004BD989 8B52FC mov edx,[edx-$04]
004BD98C 89420C mov [edx+$0c],eax
The generated code in the failing program is not seeing that S has been moved to a compiler-generated class, [ebp+$24] is how outer local variables of nested methods are accessed how local variables are accessed.
Without seeing the whole Assembler Code for the whole (procedure Test) and only assuming on the Snippet you posted, it's probably that on the failing Snippet only a Pointer has been moved where on the correct version there is some Data moved too.
So it seems that S:=S or S:='' causes the Compiler to create a reference by it's own and could even allocate some Memory, which would explain why it works then.
I also assume that's why a Access Violation occurs without S:=S or S:='', because if there is no Memory allocated for the String (remember you only declared S: PChar) then a Access Violation is raised because non-allocated Memory was accessed.
If you simply declare S: String instead, this probably won't happen.
Additions after extended Commenting:
A PChar is only a Pointer to Data Structure, that must exist. Also another common Issue with PChar is to declare local Variables and then passing a PChar to that Variable to other Procs, because what happens is that the local Variable is freed once the routine ends, but the PChar will still point to it, which then raise Access Violations once accessed.
The only possibility that exists per Documentation is declaring something like that const S: PChar = 'Hello, world!' this works because the Compiler can resolve a relative Adresse to it. But this only works for Constants and not for Variables like on the Example above. Doing it like in the Example above needs Storage to be allocated for the string literal to which the PChar then points to like S:String; P:PChar; S:='Hello, world!'; P:=PChar(S); or similar.
If it still fails with declaring String or Integer then perhaps the Variable disappears somewhere along or suddenly isn't visible anymore in a proc, but that would be another Issue that has nothing to do with the existing PChar Issue explained already.
Final Conclusion:
It's possible to do S:PChar; S:='Hello, world!' but the Compiler then simply allocates it as a local or global Constant like const S: PChar = 'Hello, world!' does which is saved into Executable, a second S := 'Hello' then creates another one which is also saved into Executable and so on - but S then only points to the last one allocated, all others are still in the Executable but not accessible any more without knowing the exact Location, because S only points to the last one allocated.
So depending which one was the last S points either to Hello, world! or Hello. On the Example above i can only guess which one was the last and who knows perhaps the Compiler can only guess too and depending on optimizations and other unpredictable Factors S could suddenly point to unallocated Mem instead of the last one by the Time Showmessage(S) is executed which then raises a Access Violation.
How can I know where this is going to cause problems?
It's hard to tell at this point in time.
If we knew the nature of the fix in Delphi XE2 we'd be in a better position.
All you can do is refrain from using anonymous functions.
Delphi has had procedural variables, so the need for anonymous functions ready is not that dire.
See http://www.deltics.co.nz/blog/posts/48.
It would be interesting to me to know if this is fixed in later versions of Delphi
According to #Sertac Akyuz this has been fixed in XE2.
Personally I dislike anonymous methods and have had to ban people from using them in my Java projects because a sizable proportion of our code base was going anonymous (event handlers).
Used in extreme moderation I can see the use case.
But in Delphi where we have procedural variables and nested procedures... Not so much.

Is the compiler treatment of implicit interface variables documented?

I asked a similar question about implicit interface variables not so long ago.
The source of this question was a bug in my code due to me not being aware of the existence of an implicit interface variable created by the compiler. This variable was finalized when the procedure that owned it finished. This in turn caused a bug due to the lifetime of the variable being longer than I had anticipated.
Now, I have a simple project to illustrate some interesting behaviour from the compiler:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := #I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal is compiled just as you would imagine. The local variable I, the function's result, is passed as an implicit var parameter to Create. The tidy up for StoreToLocal results in a single call to IntfClear. No surprises there.
However, StoreViaPointerToLocal is treated differently. The compiler creates an implicit local variable which it passes to Create. When Create returns, the assignment to P^ is performed. This leaves the routine with two local variables holding references to the interface. The tidy up for StoreViaPointerToLocal results in two calls to IntfClear.
The compiled code for StoreViaPointerToLocal is like this:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := #I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call #IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call #IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call #IntfClear
00435C9D C3 ret
I can guess as to why the compiler is doing this. When it can prove that assigning to the result variable will not raise an exception (i.e. if the variable is a local) then it uses the result variable directly. Otherwise it uses an implicit local and copies the interface once the function has returned thus ensuring that we don't leak the reference in case of an exception.
But I cannot find any statement of this in the documentation. It matters because interface lifetime is important and as a programmer you need to be able to influence it on occasion.
So, does anybody know if there is any documentation of this behaviour? If not does anyone have any more knowledge of it? How are instance fields handled, I have not checked that yet. Of course I could try it all out for myself but I'm looking for a more formal statement and always prefer to avoid relying on implementation detail worked out by trial and error.
Update 1
To answer Remy's question, it mattered to me when I needed to finalize the object behind the interface before carrying out another finalization.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
As written like this it is fine. But in the real code I had a second implicit local which was finalized after the GIL was released and that bombed. I solved the problem by extracting the code inside the Acquire/Release GIL into a separate method and thus narrowed the scope of the interface variable.
If there is any documentation of this behavior, it will probably be in the area of compiler production of temporary variables to hold intermediate results when passing function results as parameters. Consider this code:
procedure UseInterface(foo: IInterface);
begin
end;
procedure Test()
begin
UseInterface(Create());
end;
The compiler has to create an implicit temp variable to hold the result of Create as it is passed into UseInterface, to make sure that the interface has a lifetime >= the lifetime of the UseInterface call. That implicit temp variable will be disposed at the end of the procedure that owns it, in this case at the end of the Test() procedure.
It's possible that your pointer assignment case may fall into the same bucket as passing intermediate interface values as function parameters, since the compiler can't "see" where the value is going.
I recall there have been a few bugs in this area over the years. Long ago (D3? D4?), the compiler didn't reference count the intermediate value at all. It worked most of the time, but got into trouble in parameter alias situations. Once that was addressed there was a follow up regarding const params, I believe. There was always a desire to move disposal of the intermediate value interface up to as soon as possible after the statement in which it was needed, but I don't think that ever got implemented in the Win32 optimizer because the compiler just wasn't set up for handling disposal at statement or block granularity.
You can not guarantee that compiler will not decide to create a temporal invisible variable.
And even if you do, the turned off optimization (or even stack frames?) may mess up your perfectly checked code.
And even if you manage to review your code under all possible combinations of project options - compiling your code under something like Lazarus or even new Delphi version will bring hell back.
A best bet would be to use "internal variables can not outlive routine" rule. We usually do not know, if compiler would create some internal variables or not, but we do know, that any such variables (if created) would be finalized when routine exists.
Therefore, if you have code like this:
// 1. Some code which may (or may not) create invisible variables
// 2. Some code which requires release of reference-counted data
E.g.:
Lib := LoadLibrary(Lib, 'xyz');
try
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- May be not OK
end;
Then you should just wrap "Work with interface" block into subroutine:
procedure Work(const Lib: HModule);
begin
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
end; // <- Releases hidden variables (if any exist)
Lib := LoadLibrary(Lib, 'xyz');
try
Work(Lib);
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- OK!
end;
It is a simple, but effective rule.

Delphi 2009 and copying memory

I'm testing DelphiModbus library on Delphi 2009 and don't get quite the results I want. I think the problem lies with the following line on IdModbusClient.pas:
Move(Buffer, ReceiveBuffer, iSize);
It looks like ReceiveBuffer is set to some garbage.
Buffer is defined as TIdBytes (from Indy components)
ReceiveBuffer is defined as TCommsBuffer:
TModBusFunction = Byte;
TModBusDataBuffer = array[0..256] of Byte;
TCommsBuffer = packed record
TransactionID: Word;
ProtocolID: Word;
RecLength: Word;
UnitID: Byte;
FunctionCode: TModBusFunction;
MBPData: TModBusDataBuffer;
Spare: Byte;
end; { TCommsBuffer }
And iSize is of course the size of the Buffer in bytes.
I wonder if this has anything to do with unicode conversion?
Indy's TIdBytes type is a dynamic array, defined in IdGlobal.pas:
type
TIdBytes = array of Byte;
You can't pass a variable of that type directly to Move and expect it to work because it will only copy the four-byte reference stored in that variable. (And if you told it to copy more than four bytes, then it will proceed to copy whatever else resides in memory after that variable — who knows what.) Given these declarations:
var
Buffer: TIdBytes;
ReceiveBuffer: TCommsBuffer;
The way to call Move on those variables is like this:
if Length(Buffer) > 0 then
Move(Buffer[0], ReceiveBuffer, iSize);
It works like that because Move's parameters are untyped, so you need to pass the value that you want to copy, not a pointer or reference to the value. The compiler handles the referencing by itself.
The code is a little weird because it looks like you're just copying one byte out of Buffer, but don't let it bother you too much. It's a Delphi idiom; it's just the way it works.
Also, this has nothing to do with Delphi 2009; it's worked this way ever since Delphi 4, when dynamic arrays were introduced. And Move has been this way forever.
It looks to me like you're missing a couple of pointer dereferences, and therefore corrupting memory addresses.
If I'm not mistaken, the Move() call should be:
Move(Buffer^, ReceiveBuffer^, iSize);
I've removed my totally worthless post content (leaving it for posterity and to give someone a good laugh).
I don't see anything that would be affected by Unicode at all. I'm going to edit the tags to include Delphi (without the 2009), as some of the CodeGear Delphi developers are currently posting there. Perhaps one of them can see what's happening.
I made up a contrived example (actually a pretty useless one):
uses
IdGlobal;
type
TModBusFunction = Byte;
TModBusDataBuffer = array[0..256] of Byte;
TCommsBuffer=packed record
TransactionID: Word;
ProtocolID: Word;
RecLength: Word;
UnitID: Byte;
FunctionCode: TModBusFunction;
MBPData: TModBusDataBuffer;
Spare: Byte;
end;
procedure TForm1.FormShow(Sender: TObject);
var
Buffer: TIdBytes;
ReceiveBuffer: TCommsBuffer;
//iSize: Word;
begin
FillChar(ReceiveBuffer, SizeOf(ReceiveBuffer), 0);
ReceiveBuffer.TransactionID := 1;
ReceiveBuffer.ProtocolID := 2;
ReceiveBuffer.RecLength := 3;
ReceiveBuffer.UnitID := 4;
ReceiveBuffer.FunctionCode := 5;
FillChar(ReceiveBuffer.MBPData[0], SizeOf(ReceiveBuffer.MBPData), 6);
ReceiveBuffer.Spare := 7;
SetLength(Buffer, SizeOf(ReceiveBuffer));
Move(ReceiveBuffer, Buffer, SizeOf(ReceiveBuffer));
Move(Buffer, ReceiveBuffer, SizeOf(ReceiveBuffer));
ReceiveBuffer.UnitID := 8;
end;
I then set a breakpoint on the last line before the end, and ran it. When the breakpoint was hit, I looked at the contents of ReceiveBuffer using ToolTip Evaluation, and everything looked perfectly fine. I could see all of the proper values, including the ReceiveBuffer.Spare being 7. I then single stepped, and looked at ReceiveBuffer.UnitID; it in fact had a value of 8.
However, pressing F9 to continue running (expecting to be able to just close the form and end the application), I ended up in the CPU window and got a message from Vista that the application wasn't responding. I was just outside ntdll.DebugBreakPoint, IIRC, and single stepping brought me into ntdll.RtlReportException. I'm not quite sure what's happening, but it ain't good. Still looking.
Edit2: I ran it again, with the same results. However, this time I noticed before I used Ctrl+F2 to terminate the app that Vista was giving me a popup tooltray window indicating that "Project1.exe has been closed" and mentioning DEP (which I have enabled in hardware on this machine).

Resources