unit md_exception; {$mode objfpc}{$H+} interface implementation uses Windows, sysutils, ntapi, machdep, md_context, md_proc, sys_bootparam, kern_exit, kern_thr, subr_backtrace, trap, signal, ucontext, vm, vm_map, vm_pmap, vm_pmap_prot, vm_tracking_map, kern_proc, kern_jit_ctx, kern_jit_dynamic; const EXCEPTION_SET_THREADNAME = $406D1388; DBG_PRINTEXCEPTION_C = $40010006; DBG_PRINTEXCEPTION_WIDE_C = $4001000A; type LPTOP_LEVEL_EXCEPTION_FILTER=function(excep:PEXCEPTION_POINTERS):longint; stdcall; function SetUnhandledExceptionFilter(lpTopLevelExceptionFilter:Pointer):Pointer; stdcall; external kernel32 name 'SetUnhandledExceptionFilter'; function AddVectoredExceptionHandler(FirstHandler:DWORD;VectoredHandler:Pointer):Pointer; stdcall; external kernel32 name 'AddVectoredExceptionHandler'; function RemoveVectoredExceptionHandler(VectoredHandlerHandle:Pointer): ULONG; stdcall; external kernel32 name 'RemoveVectoredExceptionHandler'; function GetModuleHandleEx(dwFlags:DWORD;lpModuleName:Pointer;var hModule:THandle):BOOL; stdcall; external kernel32 name 'GetModuleHandleExA'; function IsDebuggerPresent: BOOL; stdcall; external kernel32; function RunErrorCode(const rec: TExceptionRecord): longint; begin { negative result means 'FPU reset required' } case rec.ExceptionCode of STATUS_INTEGER_DIVIDE_BY_ZERO: result := 200; { reDivByZero } STATUS_FLOAT_DIVIDE_BY_ZERO: result := -208; { !!reZeroDivide } STATUS_ARRAY_BOUNDS_EXCEEDED: result := 201; { reRangeError } STATUS_STACK_OVERFLOW: result := 202; { reStackOverflow } STATUS_FLOAT_OVERFLOW: result := -205; { reOverflow } STATUS_FLOAT_DENORMAL_OPERAND, STATUS_FLOAT_UNDERFLOW: result := -206; { reUnderflow } STATUS_FLOAT_INEXACT_RESULT, STATUS_FLOAT_INVALID_OPERATION, STATUS_FLOAT_STACK_CHECK: result := -207; { reInvalidOp } STATUS_INTEGER_OVERFLOW: result := 215; { reIntOverflow } STATUS_ILLEGAL_INSTRUCTION: result := -216; STATUS_ACCESS_VIOLATION: result := 216; { reAccessViolation } STATUS_CONTROL_C_EXIT: result := 217; { reControlBreak } STATUS_PRIVILEGED_INSTRUCTION: result := 218; { rePrivilegedInstruction } STATUS_FLOAT_MULTIPLE_TRAPS, STATUS_FLOAT_MULTIPLE_FAULTS: result := -255; { indicate FPU reset } else result := 255; { reExternalException } end; end; procedure TranslateMxcsr(mxcsr: longword; var code: longint); begin { we can return only one value, further one's are lost } { InvalidOp } if (mxcsr and 1)<>0 then code:=-207 { Denormal } else if (mxcsr and 2)<>0 then code:=-206 { !!reZeroDivide } else if (mxcsr and 4)<>0 then code:=-208 { reOverflow } else if (mxcsr and 8)<>0 then code:=-205 { Underflow } else if (mxcsr and 16)<>0 then code:=-206 { Precision } else if (mxcsr and 32)<>0 then code:=-207 else { this should not happen } code:=-255 end; function RunErrorCodeSEH(const rec:TExceptionRecord;const context:TContext):Longint; begin Result:=RunErrorCode(rec); if (Result=-255) then begin TranslateMxcsr(context.MxCsr,result); end; end; const FPC_EXCEPTION_CODE=$E0465043; FPC_SET_EH_HANDLER=$E0465044; function translate_pageflt_err(v:QWORD):Byte; inline; begin Result:=VM_PROT_NONE; case v of 0:Result:=VM_PROT_READ; 1:Result:=VM_PROT_WRITE; 8:Result:=VM_PROT_EXECUTE; end; end; function get_pageflt_err(p:PExceptionPointers):Byte; inline; begin Result:=translate_pageflt_err(p^.ExceptionRecord^.ExceptionInformation[0]); end; function get_pageflt_addr(p:PExceptionPointers):QWORD; inline; begin Result:=p^.ExceptionRecord^.ExceptionInformation[1]; end; function get_exception(p:PExceptionPointers):DWORD; inline; begin Result:=p^.ExceptionRecord^.ExceptionCode; end; procedure jit_save_to_sys_save(td:p_kthread); SysV_ABI_CDecl; external; procedure sys_save_to_jit_save(td:p_kthread); SysV_ABI_CDecl; external; function ProcessException3(td:p_kthread;p:PExceptionPointers):longint; SysV_ABI_CDecl; var tf_addr:QWORD; rv:Integer; is_jit:Boolean; begin Result:=1; if (td=nil) then Exit; thread_suspend_all(td); tf_addr:=0; is_jit:=exist_jit_host(p^.ExceptionRecord^.ExceptionAddress,@tf_addr); Writeln('cr_rip:0x',HexStr(p^.ContextRecord^.Rip,16)); Writeln('cr_rsp:0x',HexStr(p^.ContextRecord^.Rsp,16)); Writeln('cr_rbp:0x',HexStr(p^.ContextRecord^.Rbp,16)); Writeln('jitcall:0x',HexStr(td^.td_teb^.jitcall)); print_frame(stderr,td^.td_teb^.jitcall); Writeln('tf_rip:0x',HexStr(tf_addr,16)); _get_frame(p^.ContextRecord,@td^.td_frame,{@td^.td_fpstate}nil,is_jit); if (is_jit) then begin jit_save_to_sys_save(td); td^.td_frame.tf_rip:=tf_addr; end; Writeln('tf_rsp:0x',HexStr(td^.td_frame.tf_rsp,16)); Writeln('tf_rbp:0x',HexStr(td^.td_frame.tf_rbp,16)); print_backtrace_td(stderr); td^.td_frame.tf_trapno:=0; rv:=-1; case get_exception(p) of STATUS_ACCESS_VIOLATION: begin tf_addr:=get_pageflt_addr(p); Writeln('tf_addr:0x',HexStr(tf_addr,16)); //Writeln(HexStr(p^.ContextRecord^.Rip,16)); //Writeln(HexStr(Get_pc_addr)); //_get_frame(p^.ContextRecord,@td^.td_frame,{@td^.td_fpstate}nil); td^.td_frame.tf_trapno:=T_PAGEFLT; td^.td_frame.tf_err :=get_pageflt_err(p); td^.td_frame.tf_addr :=tf_addr; rv:=trap.trap(@td^.td_frame,is_jit); end; else; end; if (is_jit) then begin sys_save_to_jit_save(td); end; if (rv=0) then begin //_set_frame(p^.ContextRecord,@td^.td_frame,{@td^.td_fpstate}nil); Result:=0; end; thread_resume_all(td); //simple ret //td^.pcb_flags:=td^.pcb_flags and (not PCB_FULL_IRET); //td^.td_frame:=backup; end; type TExceptObjProc=function(code: Longint; const rec: TExceptionRecord): Pointer; { Exception } TExceptClsProc=function(code: Longint): Pointer; { ExceptClass } function IS_SYSTEM_STACK(td:p_kthread;rsp:qword):Boolean; inline; begin Result:=(rsp<=QWORD(td^.td_kstack.stack)) and (rsp>(QWORD(td^.td_kstack.sttop))); end; function IsDefaultExceptions(ExceptionCode:DWORD):Boolean; inline; begin case ExceptionCode of EXCEPTION_ACCESS_VIOLATION, EXCEPTION_BREAKPOINT, EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_SINGLE_STEP, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_FLT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW, EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW, EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW, EXCEPTION_INVALID_HANDLE, EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_NONCONTINUABLE_EXCEPTION, EXCEPTION_STACK_OVERFLOW, EXCEPTION_INVALID_DISPOSITION, EXCEPTION_IN_PAGE_ERROR, EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_POSSIBLE_DEADLOCK: Result:=True; else Result:=False; end; end; function ProcessException(p:PExceptionPointers):longint; stdcall; var instr:t_instruction_info; begin Result:=EXCEPTION_CONTINUE_SEARCH; if (curkthread=nil) then Exit; //Writeln('ProcessException:0x',HexStr(get_exception(p),8)); case get_exception(p) of FPC_EXCEPTION_CODE :Exit; FPC_SET_EH_HANDLER :Exit(EXCEPTION_CONTINUE_EXECUTION); EXCEPTION_BREAKPOINT :Exit; EXCEPTION_SINGLE_STEP :Exit; EXCEPTION_SET_THREADNAME :Exit; DBG_PRINTEXCEPTION_C :Exit(EXCEPTION_CONTINUE_EXECUTION); DBG_PRINTEXCEPTION_WIDE_C:Exit(EXCEPTION_CONTINUE_EXECUTION); //RenderDoc issuse STATUS_ACCESS_VIOLATION: begin instr:=get_instruction_info(Pointer(p^.ContextRecord^.Rip)); if pmap_danger_zone(vm_map_t(p_proc.p_vmspace)^.pmap, get_pageflt_addr(p), instr.mema_size ) then begin Exit(EXCEPTION_CONTINUE_EXECUTION); end; case get_pageflt_err(p) of VM_PROT_READ: begin if ((ppmap_get_prot(get_pageflt_addr(p),instr.mema_size) and VM_PROT_READ)<>0) then begin Writeln(stderr,'Unhandled VM_PROT_READ'); end; end; VM_PROT_WRITE: begin if ((ppmap_get_prot(get_pageflt_addr(p),instr.mema_size) and VM_PROT_WRITE)<>0) then begin //Writeln('TRACK_WRITE:',HexStr(get_pageflt_addr(p),11)); //trigger and restore vm_map_track_trigger(p_proc.p_vmspace, get_pageflt_addr(p), get_pageflt_addr(p)+instr.mema_size, nil, M_CPU_WRITE); // Exit(EXCEPTION_CONTINUE_EXECUTION); end; end; else; end; //if not usermode if (curkthread^.pcb_onfault<>nil) then begin p^.ContextRecord^.Rip:=QWORD(curkthread^.pcb_onfault); Exit(EXCEPTION_CONTINUE_EXECUTION); end; end; else if not IsDefaultExceptions(get_exception(p)) then begin Writeln(stderr,'Unknow ExceptionCode:0x',HexStr(get_exception(p),8)); Exit; end; end; Result:=ProcessException3(curkthread,p); if (Result=0) then begin Result:=EXCEPTION_CONTINUE_EXECUTION; end else begin Writeln(stderr,'ExceptionCode:0x',HexStr(get_exception(p),8)); Result:=EXCEPTION_CONTINUE_SEARCH; end; end; function UnhandledException(p:PExceptionPointers):longint; stdcall; var rec:PExceptionRecord; code: Longint; ExObj:Exception; begin Result:=EXCEPTION_CONTINUE_SEARCH; { case get_exception(p) of DBG_PRINTEXCEPTION_C :; DBG_PRINTEXCEPTION_WIDE_C:; else Writeln('UnhandledException:0x',HexStr(get_exception(p),8)); end; } case get_exception(p) of FPC_EXCEPTION_CODE :Exit; FPC_SET_EH_HANDLER :Exit(EXCEPTION_CONTINUE_EXECUTION); EXCEPTION_BREAKPOINT :Exit; EXCEPTION_SET_THREADNAME :Exit; DBG_PRINTEXCEPTION_C :Exit(EXCEPTION_CONTINUE_EXECUTION); DBG_PRINTEXCEPTION_WIDE_C:Exit(EXCEPTION_CONTINUE_EXECUTION); //RenderDoc issuse end; if (curkthread=nil) then Exit; rec:=p^.ExceptionRecord; code:=abs(RunErrorCodeSEH(rec^,p^.ContextRecord^)); ExObj:=nil; if (rec^.ExceptionCode=FPC_EXCEPTION_CODE) then begin ExObj:=Exception(rec^.ExceptionInformation[1]) end else if Assigned(ExceptObjProc) then begin ExObj:=Exception(TExceptObjProc(ExceptObjProc)(abs(code),rec^)); end; if (curkthread<>nil) then begin Writeln('curkthread^.td_name:',curkthread^.td_name); end; if (ExObj=nil) then begin Writeln(stderr,'Runtime error ',code,' at $',hexstr(rec^.ExceptionAddress)); end else begin Writeln(stderr,'An unhandled exception occurred at $',hexstr(rec^.ExceptionAddress)); Writeln(stderr,ExObj.ClassName,': ',ExObj.Message); end; print_backtrace(stderr, Pointer(p^.ContextRecord^.Rip), Pointer(p^.ContextRecord^.Rbp),0); ErrorCode:=word(code); md_halt(code); end; procedure ExceptionDispatcher(p:PExceptionPointers); stdcall; begin if (ProcessException(p)=EXCEPTION_CONTINUE_EXECUTION) then begin NtContinue(p^.ContextRecord,False); end else begin UnhandledException(p); end; end; function _get_msg(Const Msg:Shortstring):Shortstring; inline; begin Result:=Msg; if (Result='') then begin Result:='Assertion failed'; end; end; var prev_assert:TAssertErrorProc; Procedure _Assert(Const Msg,FName:Shortstring;LineNo:Longint;ErrorAddr:Pointer); begin if (curkthread=nil) then begin prev_assert(Msg,FName,LineNo,ErrorAddr); Exit; end; Writeln(stderr,_get_msg(Msg),' (',FName,', line ',LineNo,').'); p_host_ipc.error(_get_msg(Msg)+' ('+FName+', line '+IntToStr(LineNo)+').'); print_backtrace(stderr,Get_pc_addr,get_frame,2); if IsDebuggerPresent then asm int3 end; kern_exit.exit1(217); end; var VEHandler:Pointer=nil; UEHandler:Pointer=nil; V2Handler:Pointer=nil; type PEH_HANDLER_INFO=^EH_HANDLER_INFO; EH_HANDLER_INFO=record ptr:Pointer; end; procedure RaiseException(dwExceptionCode:DWORD; dwExceptionFlags:DWORD; nNumberOfArguments:DWORD; lpArguments:Pointer); external 'kernel32'; procedure InstallExceptionHandler; var eh:EH_HANDLER_INFO; begin prev_assert:=AssertErrorProc; AssertErrorProc:=@_Assert; UEHandler:=SetUnhandledExceptionFilter(@UnhandledException); VEHandler:=AddVectoredExceptionHandler(1,@ProcessException); V2Handler:=AddVectoredExceptionHandler(0,@UnhandledException); eh.ptr:=@ExceptionDispatcher; //RaiseException(FPC_SET_EH_HANDLER,0,2,@eh); end; procedure UninstallExceptionHandler; begin //SetUnhandledExceptionFilter(UEHandler); RemoveVectoredExceptionHandler(VEHandler); RemoveVectoredExceptionHandler(V2Handler); end; initialization InstallExceptionHandler; finalization UninstallExceptionHandler; end.