Jump to content
Sign in to follow this  
Stefan Glienke

Filter Exception causing debugger errors

Recommended Posts

Since the introduction of the Filter Exception expert I am often getting this error during debugging:

 

image.thumb.png.39ff23aa094278d112b2a0fe2d6a22c6.png

 

You can easily reproduce this when causing some AV - for example by simply making a new console application and executing this code: PInteger(1234)^ := 42;

 

Turn off the Filter Exceptions expert and this will properly show:

 

image.thumb.png.6e467281ca797a7235314a4528b2f14e.png

 

I don't have any filters specified in the configuration of the expert.

Edited by Stefan Glienke

Share this post


Link to post

All that I am using which would be mostly 10.1 and 10.4 - I would guess its related to the type of the exception - I usually see this with hardware exceptions such as AV or SO.

Share this post


Link to post

I can confirm the bug and its source

 

It is an escaping AV at this line in GX_FilterExceptionsNotification.pas

function ReadPointer(Process: IOTAProcess; Address: TAddress): TAddress;
begin
..
  Process.ReadProcessMemory(Address, SizeOf(Integer), Result);  // <----

the solution is easy to encapsulate all the content of that function with try..except

function ReadPointer(Process: IOTAProcess; Address: TAddress): TAddress;
begin
	try
	..
	except
		//Log it as nothing need to be done
	end;
end;

Though it can be fixed right with the OS API IsBadReadPtr, the idea it should if we have the memory pointer right, but TAddress is UInt64 !

Share this post


Link to post

It seems IsBadReadptr is working fine and it was my bad, this solves the bug for both 32 and 64

function ReadPointer(Process: IOTAProcess; Address: TAddress): TAddress;
begin
  try
    { read pointer value }
    Result := 0;
  {$IFDEF IS_WIN32_ONLY}
    if not IsBadReadPtr(Pointer(Address), SizeOf(Integer)) then
      Process.ReadProcessMemory(Address, SizeOf(Integer), Result);
  {$ELSE}
    case Process.GetProcessType of
  {$IF declared(optiOS32)}
      optiOS32,
  {$IFEND}
  {$IF declared(optAndroid)}
      optAndroid,
  {$IFEND}
  {$IF declared(optOSX32)}
      optOSX32,
  {$IFEND}
      optWin32:
        if not IsBadReadPtr(Pointer(Address), SizeOf(Integer)) then
          Process.ReadProcessMemory(Address, SizeOf(Integer), Result);
  {$IF declared(optOSX64)}
      optOSX64,
  {$IFEND}
  {$IF declared(optLinux64)}
      optLinux64,
  {$IFEND}
  {$IF declared(optiOS64)}
      optiOS64,
  {$IFEND}
  {$IF declared(optAndroid64)}
      optAndroid64,
  {$IFEND}
      optWin64:
        if not IsBadReadPtr(Pointer(Address), SizeOf(Integer)) then
          Process.ReadProcessMemory(Address, SizeOf(Int64), Result)
    else
      raise Exception.Create('Please implement me.');
    end;
  {$ENDIF}
  except
    // Debug message
  end;
end;

But found a bug in 64 bit with the exception message with wrong address's

ExceptionFilter.thumb.png.212839b7f30b8680211e1d6d2d9f6337.png

  • Thanks 1

Share this post


Link to post

Thanks that seems to fix the problem. Unfortunately when debugging GExperts itself, this still throws the exception Stefan mentioned. Apparently that's by design:

Quote

If the application is compiled as a debugging version, and the process does not have read access to all bytes in the specified memory range, the function causes an assertion and breaks into the debugger. Leaving the debugger, the function continues as usual, and returns a nonzero value. This behavior is by design, as a debugging aid.

The problem you see with Win64 is caused by the fix. Otherwise this would result in


Project GExpertsRS102.exe raised exception class EDbkError with message 'Debug process not initialized'.
 

in the call:

          Process.ReadProcessMemory(Address, SizeOf(Result), Result);

 

No idea yet, how to solve this.

Share this post


Link to post

I did not sleep since Wednesday, and i think i am out of mental strength!

 

What was i thinking and how did miss this?!!, IsBadReadPtr is wrong to begin with, and should not be used here as the debugged process is is not the one will be tested against GoodReadPtr.

So if the implementation of IOTAProcess is designed to raise an exception then we can do very little about it, but there is still the poking around with OpenProcess then ReadProcessMemory, and to do this we need that handle... and.. long story here..

 

It is easier to catch the exception with try..except and ditch it.

 

Later will check and try to protect the ReadProcessMemory outside ReadPointer, and make sure the result on exception is 0 for all, in other words there is no other exception raised/escaping to the debnugger.

 

 

 

 

 

  • Thanks 1

Share this post


Link to post
20 hours ago, Stefan Glienke said:

I would guess its related to the type of the exception - I usually see this with hardware exceptions such as AV or SO.

Yep you're totally right !

Unlike software exception, hardware exception are not blessed to Delphi exception class. So the problem occurs because the expert is trying to read(dereference) class information for HE that does not have a class associated. Why it tries to do that ? ... Well that's my bad, it appeared that I reversed a branch when translating some assembly code to pascal :classic_sad: . 

 

4 hours ago, Kas Ob. said:

I did not sleep since Wednesday, and i think i am out of mental strength!

I really appreciate your effort ... you did great 🙂 ! Thanks !

Quote

But found a bug in 64 bit with the exception message with wrong address's

This happens even if you don't use GExperts ... So I guess its coming from the IDE. 

4 hours ago, Kas Ob. said:

What was i thinking and how did miss this?!!, IsBadReadPtr is wrong to begin with, and should not be used here as the debugged process is is not the one will be tested against GoodReadPtr.

For IsBadReadPtr, you can use VirtualQuery to check for read access.

 

@dummzeuch Below there is a fix ! 
BTW Tomas, you should do a cleanup and remove all unused codes/features. I see that you're not using GetExceptionObject and yet its still being called and caused this issue. Same for sl.add(xxx).
Another thing, I found that "Ignore All this Session" button does not scale properly when resizing the exception dialog.  

function GetExceptionObjectNew(Thread: TThread): TAddress;
var
  I: Integer;
  Src: TThreadOsInfoArray;
  Parsed: TParsedThreadOsInfoArray;
  P: PByte;
  C: Cardinal;
begin
  // this function should be used only with new delphi versions
  Result := 0;
  ZeroMemory(@Src, SizeOf(Src));
  ZeroMemory(@Parsed, SizeOf(Parsed));
  I := GetThreadOsInfo(Thread, Src);
  if I <> 0 then
  begin
    case I of
      4, 6, 8, 7, 9, 10:
        Exit; // ==>
    end;
    ParseThreadOsInfo(Thread, Src, Parsed);

    // disasm TNativeThread::DoGetExceptionName
    P := @Parsed[0];
    Inc(P, $A8);
    P := PPointer(P)^;
    C := PCardinal(Integer(P) + $18)^;
    { !!! don't optimize me !!! }
    if (C <> $0EEDFAE6) then
    begin
      if C = $0EEDFADE then
      begin
        Inc(P, $38);
        Result := PUInt64(P)^;
      end
      else if C <> $0EEDFAE4 then
      begin
        Exit; // ==> hex.
      end
      else
      begin
        Inc(P, $38);
        Result := PUInt64(P)^;
      end;
    end
    else
    begin
      C := PCardinal(Integer(P) + $34)^;
      if C <> 0 then
      begin
        Inc(P, $48);
        Result := PUInt64(P)^;
      end
      else
      begin
        C := PCardinal(Integer(P) + $30)^;
        if C <> 1 then
        begin
          Inc(P, $48);
          Result := PUInt64(P)^;
        end
        else
        begin
          Exit;
        end;
      end;
    end;
  end;
end;
{$ENDIF}

function GetExceptionObjectLegacy(Thread: TThread): TAddress;
begin
  Result := 0;
  // This function should only be used with old Delphi versions, where GetExceptionObjectNew does
  // not work, that is ParseThradOsInfo does not exist.
  FDebugEventCritSect.Enter;
  if FDebugEvent.Exception.ExceptionRecord.NumberParameters > 1 then
  begin
    // Param[1] = Exception object.
    // FDebugEvent.dwProcessId = process id.
    // FDebugEvent.dwThreadId = dwThreadId id.
    // FDebugEvent.Exception.ExceptionRecord.ExceptionAddress = exception address.
    // see  TExceptionRecord for more info.
    if FDebugEvent.Exception.ExceptionRecord.ExceptionCode = $0EEDFADE { cDelphiException } then
      Result := FDebugEvent.Exception.ExceptionRecord.ExceptionInformation[1];
  end;
  FDebugEventCritSect.Leave;
end;

 

 

Share this post


Link to post
3 hours ago, Mahdi Safsafi said:

I see that you're not using GetExceptionObject and yet its still being called 

That's because I haven't decided yet how to use this information. I definitely want to.

Share this post


Link to post
16 hours ago, Mahdi Safsafi said:

@dummzeuch Below there is a fix ! 

Thanks for that.

 

One question that I wanted to ask for a while:

 

What do those "magic numbers" mean:

  • $0EEDFAE6
  • $0EEDFAE4
  • $0EEDFADE

I googled them and found that the last two are declared as constants in system.pas:
 

const
  cNonDelphiException = $0EEDFAE4;
  cDelphiException = $0EEDFADE;

Oddly enough the first one does not occur in system.pas, Google found it only once

(Warning: This URL might not be save! It should go to a file TXLib.h which is part of

"TX Library is a tiny 2D graphics library for MS Windows written in C++. This is a small sandbox for the
very beginners to help them to learn basic programming principles. The documentation is in Russian."

Copyright: (C) Ded (Ilya Dedinsky, http://txlib.ru) <mail@txlib.ru>"

but the repository on Sourceforge to which txlib.ru links contains a different TXLib.h file.)

#define EXCEPTION_CPP_BORLAND_BUILDER            0x0EEDFAE6  // Should never occur here <<<--- here
#define EXCEPTION_CPP_BORLAND_DELPHI             0x0EEDFADE  // Should never occur here

So I guess $0EEDFAE6 is an exception number that Delphi / C++ Builder use internally.

 

Am I right?

 

So I could declare them as constants for readability:

const
  cNonDelphiException = $0EEDFAE4;
  cCppBuilderException = $0EEDFAE6;
  cDelphiException = $0EEDFADE;

 

Share this post


Link to post

Looks fixed for the filter to work, but still missing few things, 

1) the message '$C0000005' or 'C0000005 ACCESS_VIOLATION' is somehow useless as it combine all AV and this will defeat/limit most of the filter usefulness with this exception, so i suggest a workaround until better solution to read the exception record the right way

function DoShowExceptionHooked(Debugger: TDebugger): Boolean;
..

  GetExceptionName(Debugger, ExceptionName);
  if (IProcess.GetProcessType = optWin64) and (CompareText(ExceptionName,'$c0000005') = 0) then
    begin
      ExceptionName := ExceptionName +'@'+ IntToHex(IThread.GetOTAThreadContextEx.win64.Rip,8);
      Msg := Msg +' @address '+IntToHex(IThread.GetOTAThreadContextEx.win64.Rip,16);
    end;
  Projectname := GxOtaGetCurrentProjectName;

and the result will be like this

AV64bit.thumb.png.7341431e2d6131a3a02ee24c89a287ab.png

Now we have the address and can separate each AV based on the address of code for 64bit.

 

2) while (1) add nice feature, i noticed separation of those AV even on 32bit is not working !, that need to be fixed now, as in my example i can't ignore one of the AV and leave the other, even when on 32bit each AV does have unique ExceptionName and Msg per address of the code that raised the AV.

Share this post


Link to post

I don't have RTL/VCL sources so i have to debug and walk them, if i am not wrong this is how to read access violation according to https://channel9.msdn.com/Shows/Inside/C0000005

and from debugging, i think there is GetExceptionObject in Sysutils that shows how AV message been built, this will help in rebuilding it for 64bit exactly as for 32bit

using FDebugEvent.Exception.ExceptionRecord you can read ExceptionAddress for the address instead of reading EIP, while the parameters list

ExceptionInformation[0] will hold the operation read,write,execute (the values can be obtained from msdn link above)

ExceptionInformation[1] will hold of the invalid address

Share this post


Link to post
3 hours ago, dummzeuch said:

One question that I wanted to ask for a while:

 

What do those "magic numbers" mean:

  • $0EEDFAE6
  • $0EEDFAE4
  • $0EEDFADE

I googled them and found that the last two are declared as constants in system.pas:
  


const
  cNonDelphiException = $0EEDFAE4;
  cDelphiException = $0EEDFADE;

 

Yes you're right !

Quote

So I guess $0EEDFAE6 is an exception number that Delphi / C++ Builder use internally.

 

Am I right?

Yes its an exception number but honestly I don't know for what its used for. The explanation you gave is very logic and I'd adopt it until its proven otherwise. 

Quote

So I could declare them as constants for readability:

I prefer this notation "if x = $0EEDFADE { cDelphiException } then" because it makes comparing to the assembly more easy. 

Share this post


Link to post
1 hour ago, Kas Ob. said:

I don't have RTL/VCL sources so i have to debug and walk them, if i am not wrong this is how to read access violation according to https://channel9.msdn.com/Shows/Inside/C0000005

and from debugging, i think there is GetExceptionObject in Sysutils that shows how AV message been built, this will help in rebuilding it for 64bit exactly as for 32bit

using FDebugEvent.Exception.ExceptionRecord you can read ExceptionAddress for the address instead of reading EIP, while the parameters list

ExceptionInformation[0] will hold the operation read,write,execute (the values can be obtained from msdn link above)

ExceptionInformation[1] will hold of the invalid address

Indeed everything is explained in SysUtils.GetExceptionObject. But I don't understand why Delphi IDE treats AV for 32bit/64bit differently.

Share this post


Link to post
4 hours ago, Mahdi Safsafi said:

I prefer this notation "if x = $0EEDFADE { cDelphiException } then" because it makes comparing to the assembly more easy. 

You have a point there. I just reverted my change with the constants and added them as comments only.

Share this post


Link to post

Thanks for the hints @Mahdi Safsafi and @Kas Ob..

I got it to display the exception address so far and changed the exception name to EAccessViolation. It's too late in the evening to get more work done, but I'll try to get the rest of the information too. It works for the Win32 only IDEs so far, but FDebugEvent is not available for later IDEs, so I need a different way to get the ExceptionInformation array contents for those.

 

Of course the same applies to other hardware exceptions.

  • Like 1

Share this post


Link to post
4 hours ago, dummzeuch said:

I got it to display the exception address so far and changed the exception name to EAccessViolation. It's too late in the evening to get more work done, but I'll try to get the rest of the information too. It works for the Win32 only IDEs so far, but FDebugEvent is not available for later IDEs, so I need a different way to get the ExceptionInformation array contents for those.

 

You can get that info from the Parsed structure (see GetExceptionObjectNew) like this :

type
  TExceptionInformation = array [0 .. 14] of UInt64; // this is a UInt64 and not NativeUInt as RTL !
  PExceptionInformation = ^TExceptionInformation;

var Params:   PExceptionInformation;
  
  P := @Parsed[0];
  Inc(P, $A8);                         
  P := PPointer(P)^;                    // -----> Internal Exception record (not like RTL record !) 
  Params := (Pointer(PByte(P) + $30));  // -----> ExceptionInformation params. 
  

I guess, you will need to adjust GetExceptionObjectNew to return the new extra information.  

  • Like 1
  • Thanks 1

Share this post


Link to post

@dummzeuch it seems that you fixed the hex problem yesterday, and it is working just fine, now with my semi workaround it does work on each AV separately by address.

Now i tried what Mahdi suggested like this

function GetExceptionObjectNew(Thread: TThread): TAddress;
var
  PE: PExceptionRecord;
...

    C := PCardinal(Integer(P) + $18)^;
    PE := PExceptionRecord(P + $18);
    {$IFOPT D+}
      SendDebugWarning('PE = '+IntToHex(UInt64(PE),8));
      SendDebugWarning('PE.ExceptionCode = '+IntToHex(PE.ExceptionCode,8));
      SendDebugWarning('PE.ExceptionFlags = '+IntToHex(PE.ExceptionFlags,8));
      SendDebugWarning('PE.ExceptionAddress = '+IntToHex(UInt64(PE.ExceptionAddress),16));
      //SendDebugWarning('PE.ExceptionAddress = '+IntToHex(PE.ExceptionAddress,8));
      I := PE.NumberParameters;
      SendDebugWarning('PE.NumberParameters = '+IntToHex(I,1));
      if I > 0 then
        for I := 0 to PE.NumberParameters - 1 do
          SendDebugWarning('PE.ExceptionInformation['+IntToStr(I)+'] = '+IntToHex(PE.ExceptionInformation[I],1));
    {$ENDIF}
    { !!! don't optimize me !!! }

But used $18 instead of $30 and used the Delphi default Exception record and it is in fact different from the OS somehow, the result of the above is like this
AV64bit2.thumb.png.abc53157ba45994a6ef88ea16f0e606a.png


Now i believe you can have the exception address with this record, also you can rebuild a message or exception identifier similar to 32bit,
the bonus here is not only you have read and write access violation but with that value of 8 you can point the execute on invalid address !, i never saw Delphi report an access violation on execution, now GExperts have it.

Share this post


Link to post
13 hours ago, Mahdi Safsafi said:

You can get that info from the Parsed structure (see GetExceptionObjectNew) like this :


type
  TExceptionInformation = array [0 .. 14] of UInt64; // this is a UInt64 and not NativeUInt as RTL !
  PExceptionInformation = ^TExceptionInformation;

var Params:   PExceptionInformation;
  
  P := @Parsed[0];
  Inc(P, $A8);                         
  P := PPointer(P)^;                    // -----> Internal Exception record (not like RTL record !) 
  Params := (Pointer(PByte(P) + $30));  // -----> ExceptionInformation params. 
  

I guess, you will need to adjust GetExceptionObjectNew to return the new extra information.  

This does work, thanks a lot. Now I only need a readable way to add this to the code...

Share this post


Link to post
3 hours ago, Kas Ob. said:

@dummzeuch it seems that you fixed the hex problem yesterday, and it is working just fine, now with my semi workaround it does work on each AV separately by address.

Now i tried what Mahdi suggested like this


function GetExceptionObjectNew(Thread: TThread): TAddress;
var
  PE: PExceptionRecord;
...

    C := PCardinal(Integer(P) + $18)^;
    PE := PExceptionRecord(P + $18);
    {$IFOPT D+}
      SendDebugWarning('PE = '+IntToHex(UInt64(PE),8));
      SendDebugWarning('PE.ExceptionCode = '+IntToHex(PE.ExceptionCode,8));
      SendDebugWarning('PE.ExceptionFlags = '+IntToHex(PE.ExceptionFlags,8));
      SendDebugWarning('PE.ExceptionAddress = '+IntToHex(UInt64(PE.ExceptionAddress),16));
      //SendDebugWarning('PE.ExceptionAddress = '+IntToHex(PE.ExceptionAddress,8));
      I := PE.NumberParameters;
      SendDebugWarning('PE.NumberParameters = '+IntToHex(I,1));
      if I > 0 then
        for I := 0 to PE.NumberParameters - 1 do
          SendDebugWarning('PE.ExceptionInformation['+IntToStr(I)+'] = '+IntToHex(PE.ExceptionInformation[I],1));
    {$ENDIF}
    { !!! don't optimize me !!! }

But used $18 instead of $30 and used the Delphi default Exception record and it is in fact different from the OS somehow, the result of the above is like this

I tried your code too, but it didn't work (Delphi 10.2.3) The ExceptionAddress was always 0. I'll go with Mahdi Safsafi's code. That worked, for 64 bits as well as 32 bits. But thanks anyway. Every hint is appreciated.

Share this post


Link to post
1 minute ago, dummzeuch said:

I tried your code too, but it didn't work (Delphi 10.2.3) The ExceptionAddress was always 0

Even in my screenshot it is 0, you still can get the address by using 

IThread.GetOTAThreadContextEx.win64.Rip

But this need a refactor to combine the two methods.

 

Anyway the most important thing is that the needed information are there in your hand, thank you and Mahdi, we now can use this useful tool.

 

Share this post


Link to post
3 hours ago, Kas Ob. said:

Even in my screenshot it is 0

Sorry, my fault, it's not the exception address (the one where it occurred) but the address which caused the exception.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  
×