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