【发布时间】:2014-02-13 14:16:43
【问题描述】:
我正在开发一个 JIT 编译器,目前它似乎工作正常,除了一个问题:当代码引发异常并且异常处理程序位于 JIT 例程中时,操作系统会立即终止该进程。当我关闭 DEP 时不会发生这种情况,所以我认为它与 DEP 有关。
当 DEP 关闭时,异常处理程序正确运行,我确保在 JITted 例程上调用 VirtualProtect,保护值为 PAGE_EXECUTE_READ,然后使用 VirtualQuery 进行验证。
在调试器下测试报告说致命错误发生在引发异常的地方,而不是稍后,我认为这意味着正在发生这样的事情:
- 引发异常
- SEH 寻找最近的异常处理程序
- SEH 发现最近的异常处理程序在 JITted 代码中并立即吓坏了
- Windows 终止任务
有谁知道我可能做错了什么,以及如何让 DEP 接受我的异常处理程序?执行 JITted 代码本身没有任何问题。
编辑:这是生成存根的 Delphi 代码。它分配内存,加载基本代码,修复跳转和尝试块的修正,然后将内存标记为可执行。这是 DWS 项目的外部函数 JIT 正在进行的工作的一部分。
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;
重现问题:
- 从 SVN 查看最新版本的 DWS
- 在 \test 文件夹中构建 LanguageTests.exe
- 禁用所有测试,然后启用列表底部
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 例程的目的是引发异常并确保异常处理程序正确清理字符串。
【问题讨论】:
-
我在 Detours 单元中遇到了类似的问题。我发现调用 VirtualProtect 是不够的。您还必须在此之前调用 VirtualAlloc。它还指出:“指定区域中的所有页面必须在使用 MEM_RESERVE 调用 VirtualAlloc 或 VirtualAllocEx 函数时分配的同一保留区域内”。但我承认,这可能不是因为对这个主题了解得不够多,无法成为任何专家。
-
@Runner: 有问题的内存已分配
VirtualAlloc,标志MEM_RESERVE or MEM_COMMIT。 -
好吧,那不是。我用 MEM_COMMIT 和 PAGE_EXECUTE_READWRITE 标志调用它。如果不是,则检查内存是否在受保护区域中。在那之后我没有更多的建议:(
-
代码是x86还是x64?
-
完成。这是调用: VirtualAlloc(nil,TrampolineSize,MEM_COMMIT,PAGE_EXECUTE_READWRITE);并且保护也被称为 PAGE_EXECUTE_READWRITE 我确信它们必须匹配
标签: windows delphi compiler-construction exception-handling jit