Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2012-08

EControlC instead of EZeroDivide when using SSE instructions

7 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Olivier SANNIER

Hello all,

I'm using FreePascal 2.6.0 under Win32 here and I'm seeing a very weird behavior as I'm not getting EZeroDivide when doing float divisions, but a rather surprising EControlC.
As I was writing the code in assembly, I also tried with plain Pascal code, but I get the same result. Here is the code:

program test;

uses
classes,

  sysutils;
procedure DirectTestExcept;
const
  A: Double = 5;
  B: Double = 0;
asm
  movsd xmm0, A
  divsd xmm0, B
end;
procedure MyTestExcept;
var
  Tmp: Double;
begin
  Tmp := 0.0;
  Tmp := 5.0 / Tmp;
end;
begin
  try
    MyTestExcept;
  except
    on E: Exception do
      WriteLn(E.ClassName + ': ' + E.Message);
  end;
  try
    DirectTestExcept;
  except
    on E: Exception do
      WriteLn(E.ClassName + ': ' + E.Message);
  end;
end.

When I first saw the EControlC, I thought that maybe the initial exception was eaten up, but it seems it's not the case because the above program gives this output:

EControlC: Control-C hit
EControlC: Control-C hit

The command line used to compile is the following:

ppc386.exe -alt -gl -CF64 -Cfsse2 -Mdelphi test.dpr

Looking at the generated assembly, the pure pascal one uses the same operands that my assembly, so it makes sense it behaves similarly. Does any of you have any suggestion as to explain this behavior, and best of all, how to fix it?

Regards
Olivier

Reply by email

OBones
to Olivier SANNIER
Olivier SANNIER wrote
Does any of you have any suggestion as to explain this behavior, and best of all, how to fix it?

I went further and changed the code this so that I can get better details as to where the exception is coming from:

program test;

uses
sysutils,

   classes;
function GetMXCSR: Cardinal;
asm
   stmxcsr Result
end;
var
   PreviousExceptProc: TExceptProc;
   PreviousErrorProc: TErrorProc;
procedure MyExceptProc(Obj: TObject; Addr: Pointer; FrameCount: LongInt;
Frame: PPointer);
begin
   WriteLn('MyExceptProc: ' + IntToHex(GetMXCSR, 2));
   WriteLn(Obj.ClassName);
   PreviousExceptProc(Obj, Addr, FrameCount, Frame);
end;
Procedure MyErrorProc(ErrNo : Longint; Address,Frame : Pointer);
begin
   WriteLn('MyErrorProc: ' + IntToHex(GetMXCSR, 2));
   WriteLn(ErrNo);
   PreviousErrorProc(ErrNo, Address, Frame);
end;
procedure MyTestExcept;
var
   Tmp: Double;
begin
   Tmp := 0.0;
   Tmp := 5.0 / Tmp;
end;
begin
   PreviousExceptProc := ExceptProc;
   ExceptProc := MyExceptProc;
   PreviousErrorProc := ErrorProc;
   ErrorProc := MyErrorProc;
   MyTestExcept;
end.

I compiled it in both x86 and x64 and then ran it. Here are the results:

============================== x86 ======================================== MyErrorProc: 1900
217
MyExceptProc: 1900
EControlC
An unhandled exception occurred at $004017CE :
EControlC : Control-C hit
$004017CE MYTESTEXCEPT, line 64 of test.dpr
$00401815 main, line 76 of test.dpr

============================== x64 ======================================== MyErrorProc: 1900
200
MyExceptProc: 1900
EDivByZero
An unhandled exception occurred at $000000000040189B : EDivByZero : Division by zero
$000000000040189B MYTESTEXCEPT, line 64 of test.dpr $00000000004018FA main, line 76 of test.dpr

So clearly, there is an issue in the x86 build because the ErrorProc has the wrong runtime error code. Strangely enough, the MXCSR has none of its exception flags set, so it seems they are reset before ErrorProc is called

I would very much appreciate any help in solving this issue.

Regards
Olivier

Reply by email

Jonas Maebe
to OBones

OBones wrote on Wed, 22 Aug 2012:

Show quoted text 5 lines
Olivier SANNIER wrote: > Does any of you have any suggestion as to explain this behavior, and best of > all, how to fix it? I went further and changed the code this so that I can get better details as to where the exception is coming from:

EControlC is the translation of run time error 217. The Win32 exception handler also sets error code 217 in case of an unknown exception (the Win64 exception handler does the same, for that matter). Apparently Win32 is not reporting that division-by-zero as either STATUS_INTEGER_DIVIDE_BY_ZERO or STATUS_FLOAT_DIVIDE_BY_ZERO, while Win64 does do this.

You can try debugging syswin32_i386_exception_handler in rtl/win32/system.pp to see what exception Win32 reports instead.

Jonas

Reply by email

OBones
to Jonas Maebe
Jonas Maebe wrote 19 lines
OBones wrote on Wed, 22 Aug 2012: > Olivier SANNIER wrote: >> Does any of you have any suggestion as to explain this behavior, and >> best of >> all, how to fix it? > I went further and changed the code this so that I can get better > details as to where the exception is coming from: EControlC is the translation of run time error 217. The Win32 exception handler also sets error code 217 in case of an unknown exception (the Win64 exception handler does the same, for that matter). Apparently Win32 is not reporting that division-by-zero as either STATUS_INTEGER_DIVIDE_BY_ZERO or STATUS_FLOAT_DIVIDE_BY_ZERO, while Win64 does do this. You can try debugging syswin32_i386_exception_handler in rtl/win32/system.pp to see what exception Win32 reports instead.

Thanks for the confirmation as I was heading that way when I posted the message, without much hope.
But now that I have put that idea to full length, it became clear that the problem is not (entirely) with Win32 but because the exception handler does not handle the STATUS_FLOAT_MULTIPLE_FAULTS and STATUS_FLOAT_MULTIPLE_TRAPS cases.
And as you said, when it does not know what to do, it gives 217 that gets later translated to Control C, which to me is a bit misleading. I'd rather have a 202, External Exception.
Anyway, I installed my own exception handler using SetUnhandledExceptionFilter, saving the return value and I wrote my own "overloaded" version of the exception handler that does this:

function GetMXCSR: Cardinal;
asm
   stmxcsr Result
end;
function MyUnhandledExceptionFilter(excep : PExceptionPointers) :
Longint;stdcall;
const
   MXCSR_IE = $01;   // invalid operation
   MXCSR_DE = $02;   // denormal
   MXCSR_ZE = $04;   // divide by zero
   MXCSR_OE = $08;   // overflow
   MXCSR_UE = $10;   // underflow
   MXCSR_PE = $20;   // precision
var
   MXCSR: Cardinal;
begin
   WriteLn('MyUnhandledExceptionFilter: ' + IntToHex(GetMXCSR, 2));
   WriteLn(IntToHex(excep^.ExceptionRecord^.ExceptionCode, 8));
   WriteLn(IntToHex(EXCEPTION_FLT_DIVIDE_BY_ZERO, 8));
   if Assigned(PreviousUnhandledExceptionFilter) then
   begin
     if (excep^.ExceptionRecord^.ExceptionCode =
STATUS_FLOAT_MULTIPLE_FAULTS) or
       (excep^.ExceptionRecord^.ExceptionCode =
STATUS_FLOAT_MULTIPLE_TRAPS) then
     begin
       MXCSR := GetMXCSR;
       if MXCSR and MXCSR_IE = MXCSR_IE then
         excep^.ExceptionRecord^.ExceptionCode :=
STATUS_FLOAT_INVALID_OPERATION
       else if MXCSR and MXCSR_DE = MXCSR_DE then
         excep^.ExceptionRecord^.ExceptionCode :=
STATUS_FLOAT_DENORMAL_OPERAND
       else if MXCSR and MXCSR_ZE = MXCSR_ZE then
         excep^.ExceptionRecord^.ExceptionCode :=
STATUS_FLOAT_DIVIDE_BY_ZERO
       else if MXCSR and MXCSR_OE = MXCSR_OE then
         excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_OVERFLOW
       else if MXCSR and MXCSR_UE = MXCSR_UE then
         excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_UNDERFLOW
       else if MXCSR and MXCSR_PE = MXCSR_PE then
         excep^.ExceptionRecord^.ExceptionCode :=
STATUS_FLOAT_INEXACT_RESULT;
     end;
     Result := PreviousUnhandledExceptionFilter(excep);
   end;
end;

I know that I hide the case where there might be two errors at once, but strangely enough, the MXCSR register only contained one exception bit set in my case, so I don't understand why Win32 is giving a "multiple traps" error.

So right, now, using this I'm getting the appropriate exception, I'm just left wondering if this code (or a similar one) should be added to FreePascal so that other people are not having the same issue as I'm having.

Note to others: this is only ever useful in Win32 because Win64 does not use the handlers set by SetUnhandledExceptionFilter

Regards
Olivier

Reply by email

Jonas Maebe
to OBones

OBones wrote on Wed, 22 Aug 2012:

Show quoted text 10 lines
Jonas Maebe wrote: > > You can try debugging syswin32_i386_exception_handler in > rtl/win32/system.pp to see what exception Win32 reports instead. Thanks for the confirmation as I was heading that way when I posted the message, without much hope. But now that I have put that idea to full length, it became clear that the problem is not (entirely) with Win32 but because the exception handler does not handle the STATUS_FLOAT_MULTIPLE_FAULTS and STATUS_FLOAT_MULTIPLE_TRAPS cases.

I guess you mean "a division by zero using an SSE instruction results in STATUS_FLOAT_MULTIPLE_FAULTS and/or STATUS_FLOAT_MULTIPLE_TRAPS"?

And as you said, when it does not know what to do, it gives 217 that gets later translated to Control C, which to me is a bit misleading. I'd rather have a 202, External Exception.

202 is "stack overflow". 217 is also used in the RTL as "unhandled exception". I don't know how come that this code is used both for ctrl-c and unhandled exception. Probably a simple error from a long time ago that never got corrected.

So right, now, using this I'm getting the appropriate exception, I'm just left wondering if this code (or a similar one) should be added to FreePascal so that other people are not having the same issue as I'm having.

Probably. At least a quick web search seems to indicate this is a fairly well-known problem with SSE exceptions and Win32.

Jonas

Reply by email

OBones
to Jonas Maebe
Jonas Maebe wrote 16 lines
OBones wrote on Wed, 22 Aug 2012: > Jonas Maebe wrote: >> >> You can try debugging syswin32_i386_exception_handler in >> rtl/win32/system.pp to see what exception Win32 reports instead. > Thanks for the confirmation as I was heading that way when I posted > the message, without much hope. > But now that I have put that idea to full length, it became clear > that the problem is not (entirely) with Win32 but because the > exception handler does not handle the STATUS_FLOAT_MULTIPLE_FAULTS > and STATUS_FLOAT_MULTIPLE_TRAPS cases. I guess you mean "a division by zero using an SSE instruction results in STATUS_FLOAT_MULTIPLE_FAULTS and/or STATUS_FLOAT_MULTIPLE_TRAPS"?

Yes, that's it, sorry for not making it clearer.

Show quoted text 8 lines
> And as you said, when it does not know what to do, it gives 217 that > gets later translated to Control C, which to me is a bit misleading. > I'd rather have a 202, External Exception. 202 is "stack overflow". 217 is also used in the RTL as "unhandled exception". I don't know how come that this code is used both for ctrl-c and unhandled exception. Probably a simple error from a long time ago that never got corrected.

Woops, typo, I meant 212, as can be found in RunErrorToExcept from sysutils.inc. That's where the Control C is coming from by the way.

Show quoted text 7 lines
> So right, now, using this I'm getting the appropriate exception, I'm > just left wondering if this code (or a similar one) should be added > to FreePascal so that other people are not having the same issue as > I'm having. Probably. At least a quick web search seems to indicate this is a fairly well-known problem with SSE exceptions and Win32.

Well, once one knows about the multiple traps/multiple faults, yes, it's easy to find it. But when starting from just a control C, it's much more complex.

Once again, thanks for your help.

Olivier

Reply by email

Sven Barth
to OBones: Thanks for the confirmation as I was heading that way when I posted the message, without…
OBones wrote 96 lines
Jonas Maebe wrote: > > OBones wrote on Wed, 22 Aug 2012: > >> Olivier SANNIER wrote: >>> Does any of you have any suggestion as to explain this behavior, and >>> best of >>> all, how to fix it? >> I went further and changed the code this so that I can get better >> details as to where the exception is coming from: > > EControlC is the translation of run time error 217. The Win32 > exception handler also sets error code 217 in case of an unknown > exception (the Win64 exception handler does the same, for that > matter). Apparently Win32 is not reporting that division-by-zero as > either STATUS_INTEGER_DIVIDE_BY_ZERO or STATUS_FLOAT_DIVIDE_BY_ZERO, > while Win64 does do this. > > You can try debugging syswin32_i386_exception_handler in > rtl/win32/system.pp to see what exception Win32 reports instead. Thanks for the confirmation as I was heading that way when I posted the message, without much hope. But now that I have put that idea to full length, it became clear that the problem is not (entirely) with Win32 but because the exception handler does not handle the STATUS_FLOAT_MULTIPLE_FAULTS and STATUS_FLOAT_MULTIPLE_TRAPS cases. And as you said, when it does not know what to do, it gives 217 that gets later translated to Control C, which to me is a bit misleading. I'd rather have a 202, External Exception. Anyway, I installed my own exception handler using SetUnhandledExceptionFilter, saving the return value and I wrote my own "overloaded" version of the exception handler that does this: function GetMXCSR: Cardinal; asm stmxcsr Result end; function MyUnhandledExceptionFilter(excep : PExceptionPointers) : Longint;stdcall; const MXCSR_IE = $01; // invalid operation MXCSR_DE = $02; // denormal MXCSR_ZE = $04; // divide by zero MXCSR_OE = $08; // overflow MXCSR_UE = $10; // underflow MXCSR_PE = $20; // precision var MXCSR: Cardinal; begin WriteLn('MyUnhandledExceptionFilter: ' + IntToHex(GetMXCSR, 2)); WriteLn(IntToHex(excep^.ExceptionRecord^.ExceptionCode, 8)); WriteLn(IntToHex(EXCEPTION_FLT_DIVIDE_BY_ZERO, 8)); if Assigned(PreviousUnhandledExceptionFilter) then begin if (excep^.ExceptionRecord^.ExceptionCode = STATUS_FLOAT_MULTIPLE_FAULTS) or (excep^.ExceptionRecord^.ExceptionCode = STATUS_FLOAT_MULTIPLE_TRAPS) then begin MXCSR := GetMXCSR; if MXCSR and MXCSR_IE = MXCSR_IE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_INVALID_OPERATION else if MXCSR and MXCSR_DE = MXCSR_DE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_DENORMAL_OPERAND else if MXCSR and MXCSR_ZE = MXCSR_ZE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_DIVIDE_BY_ZERO else if MXCSR and MXCSR_OE = MXCSR_OE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_OVERFLOW else if MXCSR and MXCSR_UE = MXCSR_UE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_UNDERFLOW else if MXCSR and MXCSR_PE = MXCSR_PE then excep^.ExceptionRecord^.ExceptionCode := STATUS_FLOAT_INEXACT_RESULT; end; Result := PreviousUnhandledExceptionFilter(excep); end; end; I know that I hide the case where there might be two errors at once, but strangely enough, the MXCSR register only contained one exception bit set in my case, so I don't understand why Win32 is giving a "multiple traps" error. So right, now, using this I'm getting the appropriate exception, I'm just left wondering if this code (or a similar one) should be added to FreePascal so that other people are not having the same issue as I'm having. Note to others: this is only ever useful in Win32 because Win64 does not use the handlers set by SetUnhandledExceptionFilter

Win64 (and Win32 systems after Windows XP) do all use SetUnhandledExceptionFilter, but we do use AddVectoredExceptionHandler on Win64 (at least if not compiled using the new Win64 SEH code in 2.7.1 ;) ) of which the handlers are called before the handler set by SetUnhandledExceptionFilter is called.

Regards,
Sven

Reply by email