usuario usar try propiedades por personalizar personalizadas manejo las excepciones ejemplos definicion crear creadas catch windows delphi compiler-construction exception-handling jit

windows - usar - try catch definicion



¿Cómo evito que DEP mate a mi controlador de excepciones JITted? (2)

Estoy trabajando en un compilador JIT que parece funcionar bien hasta ahora, excepto por un problema: cuando el código genera una excepción y el controlador de excepciones se encuentra en una rutina JIT, el sistema operativo mata el proceso de inmediato. Esto no sucede cuando desactivo el DEP, por lo que asumo que está relacionado con el DEP.

Cuando DEP se desactiva, el controlador de excepciones se ejecuta correctamente y me aseguré de llamar a VirtualProtect en la rutina JITted con un valor de protección de PAGE_EXECUTE_READ y luego verificarlo con VirtualQuery .

Al probar esto en un depurador, se informa que el error fatal se produce en el punto en el que se produce la excepción, no más tarde, lo que supongo significa que algo como esto está sucediendo:

  • Se levanta la excepción
  • SEH busca el controlador de excepciones más cercano
  • SEH ve que el controlador de excepciones más cercano está en código JIT e inmediatamente se vuelve loco
  • Windows mata la tarea

¿Alguien tiene alguna idea de lo que podría estar haciendo mal y cómo puedo hacer que DEP acepte mi controlador de excepciones? No tiene ningún problema al ejecutar el código JIT.

EDITAR: Aquí está el código Delphi que genera el código auxiliar. Asigna memoria, carga código básico, corrige correcciones para saltos y prueba bloques, y luego marca la memoria como ejecutable. Esto es parte del trabajo en progreso para la función externa JIT en el proyecto DWS .

function MakeExecutable(const value: TBytes; const calls: TFunctionCallArray; call: pointer; const tryFrame: TTryFrame): pointer; var oldprotect: cardinal; lCall, lOffset: nativeInt; ptr: pointer; fixup: TFunctionCall; info: _MEMORY_BASIC_INFORMATION; begin result := VirtualAlloc(nil, length(value), MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE); system.Move(value[0], result^, length(value)); for fixup in calls do begin ptr := @PByte(result)[fixup.offset]; if fixup.call = 0 then lCall := nativeInt(call) else lCall := fixup.call; lOffset := (lCall - NativeInt(ptr)) - sizeof(pointer); PNativeInt(ptr)^ := lOffset; end; if tryFrame[0] <> 0 then begin ptr := @PByte(result)[tryFrame[0]]; if PPointer(ptr)^ <> nil then asm int 3 end; PPointer(ptr)^ := @PByte(result)[tryFrame[2] - 1]; ptr := @PByte(result)[tryFrame[1]]; if PPointer(ptr)^ <> nil then asm int 3 end; PPointer(ptr)^ := @PByte(result)[tryFrame[3]]; end; if not VirtualProtect(result, length(value), PAGE_EXECUTE_READ, oldProtect) then RaiseLastOSError; VirtualQuery(result, info, sizeof(info)); if info.Protect <> PAGE_EXECUTE_READ then raise Exception.Create(''VirtualProtect failed''); end;

Para reproducir el problema:

  • Echa un vistazo a la última versión de DWS de SVN
  • Construye LanguageTests.exe en la carpeta / test
  • Deshabilite todas las pruebas, luego habilite la que está en la parte inferior de la lista, bajo el encabezado dwsExternalFunctionTests .
  • Ejecutar el probador. Si DEP está desactivado, debería funcionar. Si DEP está activado, se bloqueará como se describe.

EDIT 2: Aquí hay un volcado de la rutina de código de máquina generada en cuestión:

//preamble 02870000 55 push ebp 02870001 89E5 mov ebp,esp 02870003 83C4F4 add esp,-$0c 02870006 51 push ecx 02870007 53 push ebx 02870008 56 push esi 02870009 57 push edi 0287000A 8BDA mov ebx,edx 0287000C 8B33 mov esi,[ebx] 0287000E 31C0 xor eax,eax //setup exception frame 02870010 55 push ebp 02870011 685D008702 push $0287005d 02870016 64FF30 push dword ptr fs:[eax] 02870019 648920 mov fs:[eax],esp //procedure body 0287001C 31C9 xor ecx,ecx 0287001E 894DF8 mov [ebp-$08],ecx 02870021 8B06 mov eax,[esi] 02870023 8B5308 mov edx,[ebx+$08] 02870026 8B38 mov edi,[eax] 02870028 FF5710 call dword ptr [edi+$10] 0287002B 8945FC mov [ebp-$04],eax 0287002E 8B4604 mov eax,[esi+$04] 02870031 8B5308 mov edx,[ebx+$08] 02870034 8D4DF8 lea ecx,[ebp-$08] 02870037 8B38 mov edi,[eax] 02870039 FF571C call dword ptr [edi+$1c] //call to a native routine. This routine raises an exception 0287003C 8B55F8 mov edx,[ebp-$08] 0287003F 8B45FC mov eax,[ebp-$04] 02870042 E8CD1FE6FD call TestStringExc //cleanup 02870047 31C0 xor eax,eax 02870049 5A pop edx 0287004A 59 pop ecx 0287004B 59 pop ecx //exception handler: a try/finally block to clean //up a string variable used in the body of the code 0287004C 648910 mov fs:[eax],edx 0287004F 6864008702 push $02870064 02870054 8D45F8 lea eax,[ebp-$08] 02870057 E86870B9FD call @UStrClr 0287005C C3 ret 0287005D E98666B9FD jmp @HandleFinally 02870062 EBF0 jmp $02870054 //more cleanup 02870064 5F pop edi 02870065 5E pop esi 02870066 5B pop ebx 02870067 59 pop ecx 02870068 8BE5 mov esp,ebp 0287006A 5D pop ebp 0287006B C3 ret

Esto está diseñado para ser equivalente (si no es idéntico) al siguiente código de Delphi:

function Stub(const args: TExprBaseListExec): Variant; var list: PObjectTightList; a: integer; b: string; //use of a string variable will introduce an implicit try-finally //block by the compiler to handle cleanup begin list := args.List; a := TExprBase(args[0]).EvalAsInteger(args.exec); TExprBase(args[1]).EvalAsString(args.exec, b); TestStringExc(a, b); end;

El propósito de la rutina TestStringExc es generar una excepción y garantizar que el controlador de excepciones limpie correctamente la cadena.


El siguiente código podría ayudar (que viene de mi propio compilador para las interfaces de apéndice:

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.

Tenga en cuenta el PAGE_EXECUTE_READWRITE en la llamada VirtualAlloc.

Cuando se ejecuta el proceso, DEP habilitó las siguientes ejecuciones correctamente:

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],esp end; 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],edx end; function Delta( P, Q: PByte ): Integer; begin Result := Integer( P ) - Integer( Q ); end; function GetHandleFinally(): pointer; asm lea eax, system.@HandleFinally end; 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;

Funciona en Windows 7 con DEP deshabilitado y habilitado y parece ser una pieza mínima de "código JIT" con un bloque Delphi try-finally en él. ¿Podría ser que es un problema con una plataforma de Windows diferente / más nueva?


He eliminado mi otra publicación y creo que me doy cuenta de cuál es tu problema probablemente.

El problema radica en ntdll.RtlIsValidHandler que está validando su controlador de excepciones cuando envía excepciones de acuerdo con SAFESEH.

Debe evitar esto al registrar un Controlador de excepciones vectorizado y hacer su propio envío de excepciones para que no tenga que preocuparse por este comportamiento en absoluto.

Edición: creo que su problema subyacente es que ExecuteDispatchEnable y ImageDispatchEnable se están configurando en la estructura KPROCESS del kernel por algún motivo por parte de DEP, por lo que está teniendo este problema para empezar. Podría ser posible establecerlos llamando a NtSetInformationProcess, pero dado que esto no está documentado oficialmente, no puedo dar una buena idea de cómo hacer esta llamada.