Stefan Glienke 2007 Posted September 11, 2020 (edited) Since the introduction of the Filter Exception expert I am often getting this error during debugging: 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: I don't have any filters specified in the configuration of the expert. Edited September 11, 2020 by Stefan Glienke Share this post Link to post
dummzeuch 1505 Posted September 11, 2020 Is this in a particular Delphi version or in several? Share this post Link to post
Stefan Glienke 2007 Posted September 11, 2020 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
Guest Posted September 11, 2020 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
Guest Posted September 11, 2020 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 Share this post Link to post
dummzeuch 1505 Posted September 12, 2020 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
Guest Posted September 12, 2020 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. Share this post Link to post
Mahdi Safsafi 225 Posted September 12, 2020 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 . 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
dummzeuch 1505 Posted September 12, 2020 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
dummzeuch 1505 Posted September 13, 2020 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
dummzeuch 1505 Posted September 13, 2020 The problem(s) should now be fixed. @Stefan Glienke Share this post Link to post
Guest Posted September 13, 2020 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 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
Guest Posted September 13, 2020 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
Mahdi Safsafi 225 Posted September 13, 2020 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
Mahdi Safsafi 225 Posted September 13, 2020 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
dummzeuch 1505 Posted September 13, 2020 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
dummzeuch 1505 Posted September 13, 2020 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. Share this post Link to post
Mahdi Safsafi 225 Posted September 13, 2020 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. 1 Share this post Link to post
Guest Posted September 14, 2020 @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 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
dummzeuch 1505 Posted September 14, 2020 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
dummzeuch 1505 Posted September 14, 2020 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
Guest Posted September 14, 2020 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
dummzeuch 1505 Posted September 14, 2020 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