1{ 2 This file is part of the Free Pascal run time library. 3 Copyright (c) 2013 by Free Pascal development team 4 5 Support for 32-bit Windows exception handling 6 7 See the file COPYING.FPC, included in this distribution, 8 for details about the copyright. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 14 **********************************************************************} 15 16const 17 EXCEPTION_UNWIND = EXCEPTION_UNWINDING or EXCEPTION_EXIT_UNWIND; 18 19type 20 TDispatcherContext=record 21 end; 22 23 PSEHFrame=^TSEHFrame; 24 TSEHFrame=record 25 Next: PSEHFrame; 26 Addr: Pointer; 27 _EBP: PtrUint; 28 HandlerArg: Pointer; 29 end; 30 31 32procedure RtlUnwind( 33 TargetFrame: Pointer; 34 TargetIp: Pointer; 35 ExceptionRecord: PExceptionRecord; 36 ReturnValue: Pointer); 37 stdcall; external 'kernel32.dll' name 'RtlUnwind'; 38 39{$ifdef FPC_USE_WIN32_SEH} 40function NullHandler( 41 var rec: TExceptionRecord; 42 var frame: TSEHFrame; 43 var context: TContext; 44 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; 45begin 46 result:=ExceptionContinueSearch; 47end; 48 49 50function GetBacktrace(Context: TContext; StartingFrame: Pointer; out Frames: PPointer): Longint; 51var 52 FrameCount: Longint; 53 oldebp: Cardinal; 54begin 55 Frames:=AllocMem(RaiseMaxFrameCount*sizeof(pointer)); 56 FrameCount:=0; 57 repeat 58 oldebp:=context.ebp; 59 { get_caller_stackinfo checks against StackTop on i386 } 60 get_caller_stackinfo(pointer(Context.Ebp),codepointer(Context.Eip)); 61 if (Context.ebp<=oldebp) or (FrameCount>=RaiseMaxFrameCount) then 62 break; 63 if (Pointer(Context.ebp)>StartingFrame) or (FrameCount>0) then 64 begin 65 Frames[FrameCount]:=Pointer(Context.eip); 66 Inc(FrameCount); 67 end; 68 until False; 69 result:=FrameCount; 70end; 71 72 73function RunErrorCode386(const rec: TExceptionRecord; const context: TContext): Longint; 74begin 75 result:=RunErrorCode(rec); 76 { deal with SSE exceptions } 77 if (result=-255) and ((context.ContextFlags and CONTEXT_EXTENDED_REGISTERS)<>0) then 78 TranslateMxcsr(PLongword(@context.ExtendedRegisters[24])^,result); 79end; 80 81 82procedure fpc_RaiseException(Obj: TObject; AnAddr,AFrame: Pointer); [public,alias: 'FPC_RAISEEXCEPTION']; compilerproc; 83var 84 ctx: TContext; 85 args: array[0..4] of PtrUint; 86begin 87 ctx.Ebp:=Cardinal(AFrame); 88 ctx.Eip:=Cardinal(AnAddr); 89 args[0]:=PtrUint(AnAddr); 90 args[1]:=PtrUint(Obj); 91 args[2]:=GetBacktrace(ctx,AFrame,PPointer(args[3])); 92 args[4]:=PtrUInt(AFrame); 93 RaiseException(FPC_EXCEPTION_CODE,EXCEPTION_NONCONTINUABLE,5,@args[0]); 94end; 95 96 97procedure fpc_reraise; [public,alias:'FPC_RERAISE']; compilerproc; 98var 99 hp: PExceptObject; 100begin 101 hp:=ExceptObjectStack; 102 ExceptObjectStack:=hp^.next; 103 { Since we're going to 'reraise' the original OS exception (or, more exactly, pretend 104 it wasn't handled), we must revert action of CommonHandler. } 105 if TExceptionRecord(hp^.ExceptRec^).ExceptionCode<>FPC_EXCEPTION_CODE then 106 begin 107 if assigned(hp^.frames) then 108 freemem(hp^.frames); 109 if hp^.refcount=0 then 110 hp^.FObject.Free; 111 end; 112 TSEHFrame(hp^.SEHFrame^).Addr:=@NullHandler; 113 longjmp(hp^.ReraiseBuf,1); 114end; 115 116 117{ Parameters are dummy and used to force "ret 16" at the end; 118 this removes a TSEHFrame record from the stack } 119procedure _fpc_leave(a1,a2,a3,a4:pointer); [public,alias:'_FPC_leave']; stdcall; compilerproc; assembler; nostackframe; 120asm 121 movl 4(%esp),%eax 122 movl %eax,%fs:(0) 123 movl %ebp,%eax 124 call 16(%esp) 125end; 126 127 128function PopObjectStack: PExceptObject; 129var 130 hp: PExceptObject; 131begin 132 hp:=ExceptObjectStack; 133 if hp=nil then 134 halt(255) 135 else 136 begin 137 ExceptObjectStack:=hp^.next; 138 if assigned(hp^.frames) then 139 freemem(hp^.frames); 140 end; 141 result:=hp; 142end; 143 144 145function __FPC_finally_handler( 146 var rec: TExceptionRecord; 147 var frame: TSEHFrame; 148 var context: TContext; 149 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; [public,alias:'__FPC_finally_handler']; 150begin 151 if (rec.ExceptionFlags and EXCEPTION_UNWIND)<>0 then 152 begin 153 { prevent endless loop if things go bad in user routine } 154 frame.Addr:=@NullHandler; 155 TUnwindProc(frame.HandlerArg)(frame._EBP); 156 end; 157 result:=ExceptionContinueSearch; 158end; 159 160 161function __FPC_default_handler( 162 var rec: TExceptionRecord; 163 var frame: TSEHFrame; 164 var context: TContext; 165 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; [public,alias:'__FPC_DEFAULT_HANDLER']; 166var 167 code: longint; 168 Obj: TObject; 169 Adr: Pointer; 170 Frames: PCodePointer; 171 FrameCount: Longint; 172begin 173 if (rec.ExceptionFlags and EXCEPTION_UNWIND)=0 then 174 begin 175 { Athlon prefetch bug? } 176 if (rec.ExceptionCode=STATUS_ACCESS_VIOLATION) and is_prefetch(pointer(context.eip)) then 177 begin 178 result:=ExceptionContinueExecution; 179 exit; 180 end 181 else if (rec.ExceptionCode=STATUS_ILLEGAL_INSTRUCTION) and sse_check then 182 begin 183 os_supports_sse:=False; 184 { skip the offending movaps %xmm7,%xmm6 instruction } 185 inc(context.eip,3); 186 result:=ExceptionContinueExecution; 187 exit; 188 end; 189 190 RtlUnwind(@frame,nil,@rec,nil); 191 asm 192 { RtlUnwind destroys nonvolatile registers, this assembler block prevents 193 regvar optimizations. } 194 end ['ebx','esi','edi']; 195 196 if rec.ExceptionCode<>FPC_EXCEPTION_CODE then 197 begin 198 code:=RunErrorCode386(rec,context); 199 if code<0 then 200 SysResetFPU; 201 code:=abs(code); 202 Adr:=rec.ExceptionAddress; 203 Obj:=nil; 204 if Assigned(ExceptObjProc) then 205 Obj:=TObject(TExceptObjProc(ExceptObjProc)(code,rec)); 206 if Obj=nil then 207 begin 208 { This works because RtlUnwind does not actually unwind the stack on i386 209 (and only on i386) } 210 errorcode:=word(code); 211 errorbase:=pointer(context.Ebp); 212 erroraddr:=pointer(context.Eip); 213 Halt(code); 214 end; 215 FrameCount:=GetBacktrace(context,nil,Frames); 216 end 217 else 218 begin 219 Obj:=TObject(rec.ExceptionInformation[1]); 220 Adr:=rec.ExceptionInformation[0]; 221 Frames:=PCodePointer(rec.ExceptionInformation[3]); 222 FrameCount:=ptruint(rec.ExceptionInformation[2]); 223 code:=217; 224 end; 225 if Assigned(ExceptProc) then 226 begin 227 ExceptProc(Obj,Adr,FrameCount,Frames); 228 Halt(217); 229 end 230 else 231 begin 232 errorcode:=word(code); 233 errorbase:=pointer(rec.ExceptionInformation[4]); 234 erroraddr:=pointer(Adr); 235 Halt(code); 236 end; 237 end; 238 result:=ExceptionContinueExecution; 239end; 240 241 242function NestedHandler( 243 var rec: TExceptionRecord; 244 var frame: TSEHFrame; 245 var context: TContext; 246 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; 247var 248 hp: PExceptObject; 249begin 250 if (rec.ExceptionFlags and EXCEPTION_UNWIND)<>0 then 251 begin 252 hp:=PopObjectStack; 253 if hp^.refcount=0 then 254 hp^.FObject.Free; 255 end; 256 result:=ExceptionContinueSearch; 257end; 258 259function __FPC_except_safecall( 260 var rec: TExceptionRecord; 261 var frame: TSEHFrame; 262 var context: TContext; 263 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; forward; 264 265procedure CommonHandler( 266 var rec: TExceptionRecord; 267 var frame: TSEHFrame; 268 var context: TContext; 269 TargetAddr: Pointer); 270var 271 Exc: TExceptObject; 272 code: Longint; 273begin 274 if rec.ExceptionCode<>FPC_EXCEPTION_CODE then 275 begin 276 Exc.FObject:=nil; 277 code:=RunErrorCode386(rec,context); 278 if Assigned(ExceptObjProc) then 279 Exc.FObject:=TObject(TExceptObjProc(ExceptObjProc)(abs(code),rec)); 280 if (Exc.FObject=nil) and (frame.Addr<>Pointer(@__FPC_except_safecall)) then 281 Exit; 282 Exc.Addr:=rec.ExceptionAddress; 283 Exc.FrameCount:=GetBacktrace(context,nil,Exc.Frames); 284 if code<0 then 285 SysResetFPU; 286 end 287 else 288 begin 289 Exc.Addr:=rec.ExceptionInformation[0]; 290 Exc.FObject:=TObject(rec.ExceptionInformation[1]); 291 Exc.Framecount:=Longint(PtrUInt(rec.ExceptionInformation[2])); 292 Exc.Frames:=rec.ExceptionInformation[3]; 293 end; 294 295 RtlUnwind(@frame,nil,@rec,nil); 296 297 Exc.Refcount:=0; 298 Exc.SEHFrame:=@frame; 299 Exc.ExceptRec:=@rec; 300 { link to ExceptObjectStack } 301 Exc.Next:=ExceptObjectStack; 302 ExceptObjectStack:=@Exc; 303 304 frame.Addr:=@NestedHandler; 305 if setjmp(Exc.ReraiseBuf)=0 then 306 asm 307 movl Exc.FObject,%eax 308 movl frame,%edx 309 movl TargetAddr,%ecx // load ebp-based var before changing ebp 310 movl TSEHFrame._EBP(%edx),%ebp 311 jmpl *%ecx 312 end; 313 { control comes here if exception is re-raised } 314 rec.ExceptionFlags:=rec.ExceptionFlags and (not EXCEPTION_UNWINDING); 315end; 316 317 318function __FPC_except_handler( 319 var rec: TExceptionRecord; 320 var frame: TSEHFrame; 321 var context: TContext; 322 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; [public,alias:'__FPC_except_handler']; 323begin 324 if (rec.ExceptionFlags and EXCEPTION_UNWIND)=0 then 325 begin 326 { Athlon prefetch bug? } 327 if (rec.ExceptionCode=STATUS_ACCESS_VIOLATION) and 328 is_prefetch(pointer(Context.eip)) then 329 begin 330 result:=ExceptionContinueExecution; 331 exit; 332 end; 333 CommonHandler(rec,frame,context,frame.HandlerArg); 334 end; 335 result:=ExceptionContinueSearch; 336end; 337 338{ Safecall procedures are expected to handle OS exceptions even if they cannot be 339 converted to language exceptions. This is indicated by distinct handler address. } 340function __FPC_except_safecall( 341 var rec: TExceptionRecord; 342 var frame: TSEHFrame; 343 var context: TContext; 344 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; [public,alias:'__FPC_except_safecall']; assembler; nostackframe; 345asm 346 jmp __FPC_except_handler 347end; 348 349 350function __FPC_on_handler( 351 var rec: TExceptionRecord; 352 var frame: TSEHFrame; 353 var context: TContext; 354 var dispatch: TDispatcherContext): EXCEPTION_DISPOSITION; cdecl; [public,alias:'__FPC_on_handler']; 355var 356 TargetAddr: Pointer; 357begin 358 if (rec.ExceptionFlags and EXCEPTION_UNWIND)=0 then 359 begin 360 { Athlon prefetch bug? } 361 if (rec.ExceptionCode=STATUS_ACCESS_VIOLATION) and 362 is_prefetch(pointer(Context.eip)) then 363 begin 364 result:=ExceptionContinueExecution; 365 exit; 366 end; 367 { Are we going to catch it? } 368 TargetAddr:=FilterException(rec,0,PtrUInt(frame.HandlerArg),abs(RunErrorCode386(rec,context))); 369 if assigned(TargetAddr) then 370 CommonHandler(rec,frame,context,TargetAddr); 371 end; 372 result:=ExceptionContinueSearch; 373end; 374 375 376function fpc_safecallhandler(obj: TObject): HResult; [public,alias:'FPC_SAFECALLHANDLER']; compilerproc; 377var 378 hp: PExceptObject; 379 exc: TObject; 380begin 381 hp:=PopObjectStack; 382 exc:=hp^.FObject; 383 if Assigned(obj) and Assigned(exc) then 384 result:=obj.SafeCallException(exc,hp^.Addr) 385 else 386 result:=E_UNEXPECTED; 387 if hp^.refcount=0 then 388 exc.Free; 389 asm 390 movl %ebp,%edx // save current frame 391 movl hp,%ecx 392 movl TExceptObject.SEHFrame(%ecx),%ecx // target ESP minus sizeof(TSEHFrame) 393 movl (%ecx),%eax 394 movl %eax,%fs:(0) // restore SEH chain 395 movl __RESULT,%eax 396 movl TSEHFrame._EBP(%ecx),%ebp // restore EBP 397 leal 16(%ecx),%esp // restore ESP past the SEH frame 398 jmpl 4(%edx) // jump to caller 399 end; 400end; 401 402 403procedure fpc_doneexception;[public,alias:'FPC_DONEEXCEPTION'] compilerproc; 404var 405 hp: PExceptObject; 406begin 407 hp:=PopObjectStack; 408 if hp^.refcount=0 then 409 hp^.FObject.Free; 410 erroraddr:=nil; 411 asm 412 movl %ebp,%edx // save current frame 413 movl hp,%eax 414 movl TExceptObject.SEHFrame(%eax),%eax // target ESP minus sizeof(TSEHFrame) 415 movl (%eax),%ecx 416 movl %ecx,%fs:(0) // restore SEH chain 417 movl TSEHFrame._EBP(%eax),%ebp // restore EBP 418 leal 16(%eax),%esp // restore ESP, removing SEH frame 419 jmpl 4(%edx) // jump to caller 420 end; 421end; 422 423 424function main_wrapper(arg: Pointer; proc: Pointer): ptrint; assembler; nostackframe; 425asm 426 xorl %ecx,%ecx 427 pushl $__FPC_default_handler 428 pushl %fs:(%ecx) 429 movl %esp,%fs:(%ecx) 430 call *%edx 431 xorl %ecx,%ecx 432 popl %edx 433 movl %edx,%fs:(%ecx) 434 popl %ecx 435end; 436 437{$endif FPC_USE_WIN32_SEH} 438 439