我有一个相当复杂但经过严格测试的汇编语言 x86-32 应用程序,在各种 x86-32 和 x86-64 机器上运行。这是一个语言编译器的运行时系统,因此它支持执行另一个已编译的二进制程序,即“目标代码”。
它使用 Windows SEH 捕获各种类型的陷阱:除以零、非法访问……并使用 Windows 提供的上下文信息打印寄存器转储,显示陷阱时机器的状态。(它做了很多与问题无关的其他事情,例如打印函数回溯或根据需要从除以零中恢复)。这允许“目标代码”的编写者了解他的程序出了什么问题。
它在两个或多或少相同的 Windows 7-64 系统上表现不同,我认为这是非法内存访问。具体问题是某处的“目标代码”(不是经过良好测试的运行时系统)愚蠢地将 0x82 加载到 EIP 中;那是地址空间AFAIK中不存在的页面。我希望通过 SEH 出现 Windows 陷阱,并希望使用 EIP=00000082 等进行注册转储。
在一个系统上,我得到的正是那个寄存器转储。我可以在这里展示它,但它不会给我的问题添加任何东西。所以,很明显我的运行时系统中的 SEH 可以捕捉到这一点,并显示情况。这台机器上没有任何MS开发工具。
在另一个(“神秘”)系统上,运行时系统和目标代码具有相同的二进制文件,我得到的只是命令提示符。没有进一步的输出。FWIW,这台机器上有 MS Visual Studio 2010。神秘机器被大量用于其他目的,在正常使用中没有表现出其他有趣的行为。
我假设行为差异是由某处的 Windows 配置或 Visual Studio 控制的东西引起的。系统菜单不是DEP配置;它们都被配置(vanilla)为“标准系统进程的 DEP”。我的运行时系统可执行文件配置了“No (/NXCOMPAT:NO)”。
两台机器都是 i7,但芯片不同,4 核,大量内存,不同的主板。我认为这无关紧要。当然,这两个 CPU 都以相同的方式捕获陷阱。
运行时系统在启动时包括以下行:
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); // 在崩溃时停止 Windows 弹出窗口
这是最近添加的,以防止“神秘”系统在崩溃发生时显示弹出窗口“xxx.exe 已停止工作”。弹出框行为不会在第一个系统上发生,因此所做的只是将问题推到“神秘”机器上的另一个角落。
我想在哪里配置/控制这个有什么线索吗?
我在这里提供了我正在使用的 SEH 代码。它已被编辑以删除大量的健全性检查代码,我声称这些代码对该代码中看到的明显状态没有影响。
运行时系统的顶层生成一组工作线程(使用CreateThread)并指向执行ASMGrabGranuleAndGo;每个线程都设置自己的 SEH,并分支到工作窃取调度程序 RunReadyGranule。据我所知,SEH 在那之后没有改变;至少,运行时系统和“目标代码”不这样做,但我不知道底层(例如,标准“C”)库可能会做什么。
再往下,我提供了陷阱处理程序 TopLevelEHFilter。是的,套准印刷机本身可能会爆炸导致第二个异常。我会尽快再次检查,但 IIRC 我最后一次尝试在神秘机器上的调试器中捕获这个,没有将控制权传递给调试器,只是让我弹出窗口。
public ASMGrabGranuleAndGo
ASSUME FS:NOTHING ; cancel any assumptions made for this register
ASMGrabGranuleAndGo:
;Purpose: Entry for threads as workers in PARLANSE runtime system.
; Each thread initializes as necessary, just once,
; It then goes and hunts for work in the GranulesQ
; and start executing a granule whenever one becomes available
; install top level exception handler
; Install handler for hardware exceptions
cmp gCompilerBreakpointSet, 0
jne HardwareEHinstall_end ; if set, do not install handler
push offset TopLevelEHFilter ; push new exception handler on Windows thread stack
mov eax, [TIB_SEH] ; expected to be empty
test eax, eax
BREAKPOINTIF jne
push eax ; save link to old exception handler
mov fs:[TIB_SEH], esp ; tell Windows that our exception handler is active for this thread
HardwareEHinstall_end:
;Initialize FPU to "empty"... all integer grains are configured like this
finit
fldcw RTSFPUStandardMode
lock sub gUnreadyProcessorCount, 1 ; signal that this thread has completed its initialization
@@: push 0 ; sleep for 0 ticks
call MySleep ; give up CPU (lets other threads run if we don't have enuf CPUs)
lea esp, [esp+4] ; pop arguments
mov eax, gUnreadyProcessorCount ; spin until all other threads have completed initialization
test eax, eax
jne @b
mov gThreadIsAlive[ecx], TRUE ; signal to scheduler that this thread now officially exists
jmp RunReadyGranule
ASMGrabGranuleAndGo_end:
;-------------------------------------------------------------------------------
TopLevelEHFilter: ; catch Windows Structured Exception Handling "trap"
; Invocation:
; call TopLevelEHFilter(&ReportRecord,&RegistrationRecord,&ContextRecord,&DispatcherRecord)
; The arguments are passed in the stack at an offset of 8 (<--NUMBER FROM MS DOCUMENT)
; ESP here "in the stack" being used by the code that caused the exception
; May be either grain stack or Windows thread stack
extern exit :proc
extern syscall @RTSC_PrintExceptionName@4:near ; FASTCALL
push ebp ; act as if this is a function entry
mov ebp, esp ; note: Context block is at offset ContextOffset[ebp]
IF_USING_WINDOWS_THREAD_STACK_GOTO unknown_exception, esp ; don't care what it is, we're dead
; *** otherwise, we must be using PARLANSE function grain stack space
; Compiler has ensured there's enough room, if the problem is a floating point trap
; If the problem is illegal memory reference, etc,
; there is no guarantee there is enough room, unless the application is compiled
; with -G ("large stacks to handle exception traps")
; check what kind of exception
mov eax, ExceptionRecordOffset[ebp]
mov eax, ExceptionRecord.ExceptionCode[eax]
cmp eax, _EXCEPTION_INTEGER_DIVIDE_BY_ZERO
je div_by_zero_exception
cmp eax, _EXCEPTION_FLOAT_DIVIDE_BY_ZERO
je float_div_by_zero_exception
jmp near ptr unknown_exception
float_div_by_zero_exception:
mov ebx, ContextOffset[ebp] ; ebx = context record
mov Context.FltStatusWord[ebx], CLEAR_FLOAT_EXCEPTIONS ; clear any floating point exceptions
mov Context.FltTagWord[ebx], -1 ; Marks all registers as empty
div_by_zero_exception: ; since RTS itself doesn't do division (that traps),
; if we get *here*, then we must be running a granule and EBX for granule points to GCB
mov ebx, ContextOffset[ebp] ; ebx = context record
mov ebx, Context.Rebx[ebx] ; grain EBX has to be set for AR Allocation routines
ALLOCATE_2TOK_BYTES 5 ; 5*4=20 bytes needed for the exception structure
mov ExceptionBufferT.cArgs[eax], 0
mov ExceptionBufferT.pException[eax], offset RTSDivideByZeroException ; copy ptr to exception
mov ebx, ContextOffset[ebp] ; ebx = context record
mov edx, Context.Reip[ebx]
mov Context.Redi[ebx], eax ; load exception into thread's edi
GET_GRANULE_TO ecx
; This is Windows SEH (Structured Exception Handler... see use of Context block below!
mov eax, edx
LOOKUP_EH_FROM_TABLE ; protected by DelayAbort
TRUST_JMP_INDIRECT_OK eax
mov Context.Reip[ebx], eax
mov eax, ExceptionContinueExecution ; signal to Windows: "return to caller" (we've revised the PC to go to Exception handler)
leave
ret
TopLevelEHFilter_end:
unknown_exception:
<print registers, etc. here>