How do I keep DEP from killing my JITted exception handler?
The following code might help (which coms from my own compiler for stubbing interfaces:
function GetExecutableMem(Size: Integer): Pointer; procedure RaiseOutofMemory; begin raise EOutOfResources.Create('UnitProxyGenerator.GetExecutableMem: Out of memory error.'); end;var LastCommitTop: PChar;begin // We round the memory needed up to 16 bytes which seems to be a cache line amound on the P4. Size := (Size + $F) and (not $F); // Result := MemUsed; Inc(MemUsed, Size); // Do we need to commit some more memory? if MemUsed > MemCommitTop then begin // Do we need more mem than we reserved initially? if MemUsed > MemTop then RaiseOutOfMemory; // Try to commit the memory requested. LastCommitTop := MemCommitTop; MemCommitTop := PChar((Longword(MemUsed) + (SystemInfo.dwPageSize - 1)) and (not (SystemInfo.dwPageSize - 1))); if not Assigned(VirtualAlloc(LastCommitTop, MemCommitTop - LastCommitTop, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) then RaiseOutOfMemory; end;end;initialization GetSystemInfo(SystemInfo); MemBase := VirtualAlloc(nil, MemSize, MEM_RESERVE, PAGE_NOACCESS); if MemBase = nil then Halt; // VERY BAD ... MemUsed := MemBase; MemCommitTop := MemBase; MemTop := MemBase + MemSize;finalization VirtualFree(MemBase, MemSize, MEM_DECOMMIT); VirtualFree(MemBase, 0, MEM_RELEASE);end.
Please note the PAGE_EXECUTE_READWRITE in the VirtualAlloc call.
When process is run DEP enabled the following runs correctly:
type TTestProc = procedure( out A: Integer ); stdcall;procedure Encode( var P: PByte; Code: array of Byte ); overload;var i: Integer;begin for i := 0 to High( Code ) do begin P^ := Code[ i ]; Inc( P ); end;end;procedure Encode( var P: PByte; Code: Integer ); overload;begin PInteger( P )^ := Code; Inc( P, sizeof( Integer ) );end;procedure Encode( var P: PByte; Code: Pointer ); overload;begin PPointer( P )^ := Code; Inc( P, sizeof( Pointer ) );end;// returns address where exceptiuon handler will be.function EncodeTry( var P: PByte ): PByte;begin Encode( P, [ $33, $C0, $55,$68 ] ); // xor eax,eax; push ebp; push @handle Result := P; Encode( P, nil ); Encode( P, [ $64, $FF, $30, $64, $89, $20 ] ); // push dword ptr fs:[eax]; mov fs:[eax],espend;procedure EncodePopTry( var P: PByte );begin Encode( P, [ $33, $C0, $5A, $59, $59, $64, $89, $10 ] ); // xor eax,eax; pop edx; pop ecx; pop ecx; mov fs:[eax],edxend;function Delta( P, Q: PByte ): Integer;begin Result := Integer( P ) - Integer( Q );end;function GetHandleFinally(): pointer;asm lea eax, system.@HandleFinallyend;procedure TForm10.Button5Click( Sender: TObject );var P, Q, R, S, T: PByte; A: Integer;begin P := VirtualAlloc( nil, $10000, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if not Assigned( P ) then Exit; try // ------------------------------------------------------------------------ // Equivalent // // A:=10; // try // A:=20 // PInteger(nil)^:=20 // finally // A:=30; // end; // A:=40; // // ------------------------------------------------------------------------ // Stack frame Q := P; Encode( Q, [ $55, $8B, $EC ] ); // push ebp, mov ebp, esp // A := 10; Encode( Q, [ $8B, $45, $08, $C7, $00 ] ); Encode( Q, 10 ); // mov eax,[ebp+$08], mov [eax],<int32> // try R := EncodeTry( Q ); // TRY CODE !!!! // A := 20; Encode( Q, [ $8B, $45, $08, $C7, $00 ] ); Encode( Q, 20 ); // mov eax,[ebp+$08], mov [eax],<int32> // REMOVE THIS AND NO EXCEPTION WILL OCCUR. Encode( Q, [ $33, $C0, $C7, $00 ] ); // EXCEPTION: xor eax, eax, mov [eax], 20 Encode( Q, 20 ); // END OF REMOVE // END OF TRY CODE EncodePopTry( Q ); Encode( Q, [ $68 ] ); // push @<afterfinally> S := Q; Encode( Q, nil ); // FINALLY CODE!!!! T := Q; // A := 30; Encode( Q, [ $8B, $45, $08, $C7, $00 ] ); Encode( Q, 30 ); // mov eax,[ebp+$08], mov [eax],<int32> // AFter finally Encode( Q, [ $C3 ] ); // ret Encode( R, Q ); // Fixup try // SEH handler Encode( Q, [ $E9 ] ); // jmp Encode( Q, Delta( GetHandleFinally(), Q ) - sizeof( Pointer ) ); // <diff:i32> Encode( Q, [ $E9 ] ); // jmp Encode( Q, Delta( T, Q ) - sizeof( Pointer ) ); // <diff:i32> // After SEH frame Encode( S, Q ); // A := 40; Encode( Q, [ $8B, $45, $08, $C7, $00 ] ); Encode( Q, 40 ); // mov eax,[ebp+$08], mov [eax],<int32> // pop stack frame Encode( Q, [ $5D, $C2, $04, $00 ] ); // pop ebp, ret 4 // ------------------------------------------------------------------------ // And.... execute A := 0; try TTestProc( P )( A ); except ; end; Caption := IntToStr( A )+'!1'; // Dofferent protection... execute VirtualProtect( P, $10000, PAGE_EXECUTE_READ, nil ); A := 0; try TTestProc( P )( A ); except ; end; Caption := IntToStr( A ) + '!2'; finally // Cleanup VirtualFree( P, $10000, MEM_RELEASE ); end;end;
It works on Windows 7 with both DEP disabled and enabled and seems to be a minimal piece of "JIT code" with a Delphi try-finally block in it. Could it be that it is a problem with a different / newer Windows platform?
I have deleted my other post and believe I realize what your problem probably is.
The issue lies in ntdll.RtlIsValidHandler which is validating your exception handler when its dispatching exceptions according to SAFESEH.
You need to avoid this by registering a Vectored Exception Handler and do your own exception dispatching so you wont have to worry about this behavior at all.
Edit:I believe your underlying issue is that the ExecuteDispatchEnable and ImageDispatchEnable are being set in the kernel's KPROCESS structure for some reason by DEP which is why you are having this problem to begin with. It might be possible to set these by calling NtSetInformationProcess, but given that this is not officially documented I cannot give good insight on how to make this call.