Я работаю над компилятором JIT, который пока работает нормально, за исключением одной проблемы: когда код вызывает исключение, а обработчик исключений находится в подпрограмме JITted, ОС немедленно убивает процесс. Этого не происходит, когда я выключаю DEP, поэтому я предполагаю, что это связано с DEP.
Когда DEP выключен, обработчик исключений работает правильно, и я обязательно позвонил в VirtualProtect
в JITted-режиме со значением защиты PAGE_EXECUTE_READ
, а затем проверит его с помощью VirtualQuery
.
Тестирование этого под отладчиком сообщает о том, что фатальная ошибка происходит в точке, где возникает исключение, а не позже, которое, как я предполагаю, означает, что происходит подобное:
- Исключение возникает
- SEH ищет ближайший обработчик исключений
- SEH видит, что ближайший обработчик исключений находится в JIT-коде и сразу же выдает
- Windows убивает задачу
Кто-нибудь знает, что я могу делать неправильно, и как я могу заставить DEP принять мой обработчик исключений? У него нет никаких проблем с выполнением самого JIT-кода.
EDIT: Здесь используется код Delphi, который генерирует заглушку. Он выделяет память, загружает базовый код, исправляет исправления для переходов и пытается блокировать, а затем помещает память как исполняемую. Это часть незавершенной работы для внешней функции JIT в проекте 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;
Чтобы воспроизвести проблему:
- Проверьте последнюю версию DWS из SVN
- Build LanguageTests.exe в папке \test
- Отключите все тесты, затем включите тот, который находится внизу списка, под заголовком
dwsExternalFunctionTests
. - Запустите тестер. Если DEP выключен, он должен работать. Если DEP включен, он будет аварийно завершен, как описано.
РЕДАКТИРОВАТЬ 2: Ниже приведена дамп сгенерированной машинной программы:
//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
Это будет эквивалентно (если не идентично) следующему коду 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;
Цель процедуры TestStringExc состоит в том, чтобы создать исключение и убедиться, что обработчик исключений корректно очищает строку.