Jump to content

Mahdi Safsafi

Members
  • Content Count

    383
  • Joined

  • Last visited

  • Days Won

    10

Everything posted by Mahdi Safsafi

  1. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    If I meant it to be a good practice, I'd say its not a bad code. My point was that no one should rely on the object after a call has been made to free regardless whether an exception occurred or not ... and I still retain that position. For that point, I gave some example just for explanation but never considered it as a good practice. A good practice from my point of view is to expect the worse not to expect all people are following the good practice. BTW, those destructors for the VCL/RTL (the way are implemented) are not safe. Hope that this explicit statement clarify things for you.
  2. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    I didnt say its a good practice. All what I said there are some situations where its implemented.
  3. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    Not always a bad code ! sometime you need to make a lock/release. So you must call the inherited in between and finally free the Lock object. Sometime you need to perform a notification, ... Just take a look at the RTL/VCL, there're many destructor that call inherited in between. So if you can control your destructor about how to call inherited ... there is no way to control other descendant-destructors that do not belong to you. i.e inheriting from a VCL/RTL class that does not call inherited as a last line in its destructor.
  4. Mahdi Safsafi

    Open Type Arrays?

    Very excellent Stefan ! Just noticed that the new operand overrides the original one for integer-literal : DoSomething(1); // calls yours operand. DoSomething(a); // calls default operand. a=integer. DoSomething('test'); // calls default operand. DoSomething(str); // calls default operand. str=string. EDIT: I guess the answer is here: class operator TValue.Implicit(Value: Integer): TValue;
  5. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    Yes it may happen that a destructor throws an exception i.e: AV. But usually you would spot them when debugging. In a complex inheritance hierarchy its hard to tell whether the instance was freed or not after an exception. destructor TMyClass.Destroy(); var List: TList; begin List := FList; inherited; // free instance. for i := 0 to 10 do List[i].Free(); // <--- AV here. List.free(); end; As you can see, this class is freeing its instance before freeing its internal data. Some class can first free its internal data and finally free its instance. So if you really need to access an object field inside an exception block, the safe way to do is to move first that field to the stack before calling the free(provided that Obj.destructor does not free that field): procedure DoSomething(Obj: TMyObject); var LList: TList; begin LList := Obj.FList; try FreeThenNil(obj); except Log(LList); end; end; For me, I try to pay much more attention to my destructor code and I consider an exception thrown by a destructor as a fatal exception that requires terminating the app immediately because it may lead to a corrupted data (some fields were freed, some not), it may lead to never-ending leak, ...
  6. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    Possibly as I said before, I may misunderstanding you. My point was that you shouldn't rely on the object after a call to free was made regardless whether its destructor raised exception or not because the time the RTL frees the object A, another thread can reclaim to create a new object B and possibly it would occupy the same address space as the recent destroyed one A. Later, trying to read object A.SomeField could possibly crash with B.SomeField. As long as there was no implicit/explicit call to allocate memory by any thread (including your current thread) that happened after freeing your object and before accessing to that dead object. i.e: procedure DoSomething(Obj: TMyObject); var Data: Pointer; begin try FreeThenNil(obj); except // reference is not null ! // multithread: Log(Obj.FList); // buggy on MT as the instance could be already free and used by another object from another thread. // singlethread: Data := GetMemory(256); // if the instance was free, current call CAN use old instance data. InitData(Data); Dump(Data, Obj.Flist); // buggy too ! FList may crach with Data. end; end; In a nutshell, as it was pointed out before, a destructor shouldn't raise exception and you shouldn't try to access an object after a call to free. So nil-then-free makes more sense. EDIT: Assume FList is not destroyed inside the destructor or assume its an integer type.
  7. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    No it wouldn't on a multi-thread app ! The time the instance A is destroyed, its free-memory could be used immediately by another thread that creates a new object B. Your reference then is pointing to the new object B instead of A. This applies to all the things that are allocated on the heap.
  8. Mahdi Safsafi

    FreeAndNil or NilAndFree ?

    I really don't understand your point here. But if I'm understanding it correctly, you want to track other fields ? if so, there is no guarantee that you're going to have a valid information(Once a destructor is called ... you can no longer rely on the object).
  9. Mahdi Safsafi

    Open Type Arrays?

    @Stefan Glienke IsType<x> does not distinguish between distinct-type. And it could be a source of error. Using Overloaded function in some how can distinct at least if you specify the type explicitly: type TMyString = type string; procedure DoSomething2(const AParam: Integer); overload; begin Writeln('this is a integer :', AParam); end; procedure DoSomething2(const AParam: string); overload; begin Writeln('this is a string :', AParam); end; procedure DoSomething2(const AParam: TMyString); overload; begin Writeln('this is a TMyString :', AParam); end; var a: Integer; s: string; m: TMyString; begin s := 'string'; m := 'mystring'; DoSomething(a); DoSomething(s); DoSomething(m); DoSomething2(a); DoSomething2(s); DoSomething2(m); Readln; end.
  10. Mahdi Safsafi

    DDetours v2.2

    Hi, DDetours v2.2 is released. version 2.2(Jun 9, 2020): +Added support for older Delphi version: Now the minimal supported Delphi version is D7. +Added support for FPC. +Added recursive section feature: EnterRecursiveSection/ExitRecursiveSection. +Added param/tag feature for all InterceptCreate functions. +Added GetTrampolineParam function to get user param. +Added GetCreatorThreadIdFromTrampoline function to get thread id that created the hook/trampoline. +Added detection for non valid trampoline pointer. +Added unittest. +Replaced BeginHooks/BeginUnHooks by BeginTransaction. +Replaced EndHooks/EndUnHooks by EndTransaction. +Replaced GetNHook by GetHookCount. +Replaced TDetours<T> by TIntercept<T,U>/TIntercept<T> +Fixed many bugs related to MultiBytesNop. +Fixed wrong displacement value for some branch instructions on x64. +Fixed wrong offset size on x86 for GetJmpType function. +Removed v1 compatibility. +Now the library does not rely on Object. +Code refactoring. https://github.com/MahdiSafsafi/DDetours Some of the above features were planed for v3 that I started working on years ago. But I never get a chance to finish it. What a pity I'm rolling v2.2 instead of v3.2.
  11. Mahdi Safsafi

    DDetours v2.2

    Thanks 🙂 It happened several time for me under different IDE with different libraries, I just noticed that it happens often when code is using .inc files. I used to do a clean and rebuild and that worked in many time. Sometime I need to restart the IDE.
  12. Mahdi Safsafi

    Open Type Arrays?

    For dynamic language like typescript ... sure they're designed to work like that. Elsewhere (Delphi), this is going to be awful because you're checking the type at runtime, moreover you are mixing reference(string) with integer ... how are you expecting from the compiler to pass argument for your call (i.e 8 bytes and 4 bytes) ? Using union may solve the problem but still there is a drawback to check for the type. So I don't think it is a good idea at all.
  13. Mahdi Safsafi

    DDetours v2.2

    The issue was fired a long time ago. And its related to MadExcept and not to DDetours. DDetours on its current implementation rely on Delphi memory management (DMM). MadExcept hooks DMM and for some reason it reports fake buffer overrun/underrun when a memory page is marked as execute(at least, this is what I found when I installed MadExcept and did some investigation). For now, I made a temporary solution to work around. You need to update your DDetours version and define FIX_MADEXCEPT in DDetours.pas v3 would eventually have a new custom memory management that lets it run independently on DDM.
  14. Mahdi Safsafi

    DDetours v2.2

    @FredS Thanks man ! Indeed there is a leak and is not being reported by ReportMemoryLeaksOnShutdown. I don't use madExcept, but a quick debug suggested that there was no call to FreeMemory to clean up SuspendedThread array. I just committed a patch to fix the leak. Thanks again.
  15. Mahdi Safsafi

    GExperts supports even more laziness

    Thanks for the explanation. I clearly understood you by now. You opened my mind, I'll try to give much priority for old Delphi versions when releasing the next version of DDetours as its obviously many 3rd party plugins that hooks the IDE intend to support older Delphi versions.
  16. Mahdi Safsafi

    Regex Validate string

    Thanks man ! This "ArabicWord EnglishWord" sentence makes sense to me, But using a word that has a mixed Arabic and English letter such "ArabicLettersEnglishLetters" is much harder to make sense.
  17. Mahdi Safsafi

    GExperts supports even more laziness

    The purpose of DDetours v2 is to achieve a correct management of several hooks on the same function. Meaning if two expert are hooking the same function, DDetours guarantees that expert2 will receive the hook when calling NextHook/Trampoline from expert1. If I have a 3rd party IDE plugin, I wouldn't miss a chance to add a stack trace for the exception dialog. This could be possible together with expert1 only if both expert uses DDetours. If possible, use DDetours as DLL for older version. I think that bellow code will work with older version and is more safe : type TArrayOfShortInt = array [Byte] of ShortInt; // bound safe PArrayOfShortInt = ^TArrayOfShortInt; var P: PArrayOfShortInt; P := PArrayOfShortInt(GetParam(Obj)); P^[$A1] := 1; // safe than pointer arithmetic. You're welcome
  18. Mahdi Safsafi

    GExperts supports even more laziness

    I'm very sorry man. I loved to give you the full solution if I was able but debugging/reversing Delphi IDE is a nightmare. A lot of assembly code must be reviewed/tracked/commented, following a bunch of stack-call to find the desired method, and the Delphi IDE keeps throwing many exceptions under the debugger that forces me to make a restart. RE is a hard domain and sometime you take weeks/month to fully reverse something. The above solution I gave is the native one. Delphi IDE uses it to resume application. So I believe its your best option. Sorry for DDetours, I got many features for it but never find a time 😥 P[$A1] := 1; // resume = true. This doesn't compile. Am I right to assume that it sets PByte(P + $A1) to 1 ? It compiles perfectly under D10.3.2 so I guess you're using an old version. FWIW, you can use PByte(P + $A1)^ := 1; and you can do the same for GetParam (I used it for exploration and I forgot to replace it). Yes that's it. Just be careful for its signature : TGetExceptionName = procedure(Obj: TObject; var Msg: string);
  19. Mahdi Safsafi

    GExperts supports even more laziness

    Good news guys ! I have found a way to work around continue issue ! Now the example works like a charm 😊 You opened my mind ... thanks 😉 There is an internal function called PostDebugMessage that do the trick. type TShowException = function(Obj: TObject): Boolean; TDoShowException = function(Obj: TObject): Boolean; TGetExceptionMessage = procedure(Obj: TObject; var Msg: string); TReportExceptionNotification = function(Msg: Pointer; Unkown: ShortInt; Unkown2: Pointer): ShortInt; TPostDebugMessage = procedure(Obj: TObject; Msg: ShortInt; Param: Pointer); var TrampolineShowException: TShowException; TrampolineDoShowException: TDoShowException; TrampolineReportExceptionNotification: TReportExceptionNotification; GetExceptionMessage: TGetExceptionMessage; PostDebugMessage: TPostDebugMessage; Hooked: Boolean = False; function GetParam(Obj: Pointer): Pointer; asm mov eax, [eax + $40] end; function DoShowExceptionHooked(Obj: TObject): Boolean; var Msg: string; MsgResult: Cardinal; P: PByte; begin GetExceptionMessage(Obj, Msg); MsgResult := MessageBox(0, 'Yes=continue No=Break Cancel=OriginalDialog', PChar(Msg), MB_YESNOCANCEL); case MsgResult of IDYES: begin { continue. } Result := True; P := GetParam(Obj); P[$A1] := 1; // resume = true. PostDebugMessage(Obj, 1, P); // this will resume the app. end; IDNO: begin { break. } Result := False; end; IDCANCEL: begin { original dialog. } Result := TrampolineDoShowException(Obj); end; end; end; procedure InstallHook(); var ShowExceptionPtr: Pointer; DoShowExceptionPtr: Pointer; ReportExceptionNotificationPtr: Pointer; PostDebugMessagePtr: Pointer; begin if not Hooked then begin ShowExceptionPtr := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Debug@TDebugger@ShowException$qqrv'); DoShowExceptionPtr := GetProcAddress(GetModuleHandle('win32debugide260.bpl'), '@Win32debug@TNativeDebugger@DoShowException$qqrv'); ReportExceptionNotificationPtr := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Exceptionnotificationdialog@ReportExceptionNotification$qqrx20System@UnicodeString83System@%Set$56Exceptionnotificationdialog@TExceptionNotificationOptiont1$i0$t1$i2$%r83System@%Set$56Exceptionnotificationdialog@TExceptionNotificaTY6J18n0Wfo24G1hyGAqTA'); @GetExceptionMessage := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Debug@TDebugger@GetExceptionMessage$qqrv'); @PostDebugMessage := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Debug@TDebugger@PostDebugMessage$qqr15Debug@TDebugMsgpv'); Assert(Assigned(DoShowExceptionPtr)); Assert(Assigned(@GetExceptionMessage)); Assert(Assigned(@PostDebugMessage)); @TrampolineDoShowException := InterceptCreate(DoShowExceptionPtr, @DoShowExceptionHooked); Hooked := True; ShowMessage('hook was installed.'); end; end; procedure RemoveHook(); begin if Hooked then begin InterceptRemove(@TrampolineDoShowException); Hooked := False; end; end; N.B: The signature for ShowException was corrected as I found that it is a function and not a procedure. @dummzeuch All what we need now is your magic touch ! Perhaps you can make a custom exception dialog that replace the old one. And we would be able then to ignore all exceptions with same message only once instead of going to expert-settings and use regex.
  20. Mahdi Safsafi

    GExperts supports even more laziness

    @Stefan Glienke @dummzeuch I made some debugging and conclude to three place where a hook could be used : - ShowException : this is the top-level function that call DoShowException->ReportExceptionNotification. - DoShowException : this is called by ShowException and returns False(break) and True(continue). When returning continue, a call to Run is necessary in order to continue running the application. - ReportExceptionNotification: this is the best place (no need to call run) but unfortunately it inits internally a list that is used later. without calling the original method an AV would be thrown. type TShowException = procedure(Obj: TObject); TDoShowException = function(Obj: TObject): Integer; TGetExceptionMessage = procedure(Obj: TObject; var Msg: string); TReportExceptionNotification = function(Msg: Pointer; Unkown: ShortInt; Unkown2: Pointer): ShortInt; var TrampolineShowException: TShowException; TrampolineDoShowException: TDoShowException; TrampolineReportExceptionNotification: TReportExceptionNotification; GetExceptionMessage: TGetExceptionMessage; Hooked: Boolean = false; procedure ShowExceptionHooked(Obj: TObject); var Msg: string; begin { this is the toplevel function. calls DoShowException(). } GetExceptionMessage(Obj, Msg); ShowMessage(Msg); // TrampolineShowException(Obj); end; function DoShowExceptionHooked(Obj: TObject): Integer; var Msg: string; MsgResult: Cardinal; begin { DoShowException calls ReportExceptionNotificationHooked } GetExceptionMessage(Obj, Msg); MsgResult := MessageBox(0, 'Yes=continue No=Break Cancel=OriginalDialog', PChar(Msg), MB_YESNOCANCEL); case MsgResult of IDYES: begin Result := 1; // continue. // need to call Run(); end; IDNO: begin Result := 0; // break. end; IDCANCEL: begin Result := TrampolineDoShowException(Obj); // original dialog. end; end; end; function ReportExceptionNotificationHooked(Msg: Pointer; Unkown: ShortInt; Unkown2: TObject): ShortInt; var MsgResult: Cardinal; begin { this is the best place where to make a patch ... however internally, it inits a list if you don't call original function, an AV (list) will be thrown. } // type of Msg = string. MsgResult := MessageBox(0, 'Yes=continue No=Break Cancel=OriginalDialog', '', MB_YESNOCANCEL); case MsgResult of IDYES: begin Result := 0; // continue. // no need to call Run(). end; IDNO: begin Result := 1; // break. end; IDCANCEL: begin Result := TrampolineReportExceptionNotification(Msg, Unkown, Unkown2); // original dialog. end; end; end; procedure InstallHook(); var ShowExceptionPtr: Pointer; DoShowExceptionPtr: Pointer; ReportExceptionNotificationPtr: Pointer; begin if Hooked then exit; ShowExceptionPtr := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Debug@TDebugger@ShowException$qqrv'); DoShowExceptionPtr := GetProcAddress(GetModuleHandle('win32debugide260.bpl'), '@Win32debug@TNativeDebugger@DoShowException$qqrv'); @GetExceptionMessage := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Debug@TDebugger@GetExceptionMessage$qqrv'); ReportExceptionNotificationPtr := GetProcAddress(GetModuleHandle('dbkdebugide260.bpl'), '@Exceptionnotificationdialog@ReportExceptionNotification$qqrx20System@UnicodeString83System@%Set$56Exceptionnotificationdialog@TExceptionNotificationOptiont1$i0$t1$i2$%r83System@%Set$56Exceptionnotificationdialog@TExceptionNotificaTY6J18n0Wfo24G1hyGAqTA'); assert(ReportExceptionNotificationPtr <> nil); if Assigned(DoShowExceptionPtr) then begin @TrampolineDoShowException := InterceptCreate(DoShowExceptionPtr, @DoShowExceptionHooked); Hooked := true; ShowMessage('hook is installed'); end; end; procedure RemoveHook(); begin if Hooked then InterceptRemove(@TrampolineDoShowException); end;
  21. Mahdi Safsafi

    GExperts supports even more laziness

    Here is what I found : procedure ShowException(Obj: TObject); // @Debug@TDebugger@ShowException$qqrv procedure GetExceptionMessage (Obj: TObject; var Msg: string) // @Debug@TDebugger@GetExceptionMessage$qqrv ShowException is the one that shows the dialog. GetExceptionMessage is used to get exception message. I installed a hook on ShowException and was able to pass the dialog but the IDE debugger still break. Possibly the break is the default action. procedure ShowException(Obj: TObject); var Msg: string; begin GetExceptionMessage(Obj, Msg); ShowMessage(Msg); // TrampolineShowException(Obj); end;
  22. Mahdi Safsafi

    GExperts supports even more laziness

    Nice job ! EDIT: Sorry I didn't pay attention to "cannot directly modify IDE code" So Vectored Exception Handler wouldn't help.
  23. Mahdi Safsafi

    Problem with ExitCode

    When calling a console application from CMD, current CMD becomes a window for that application. And CMD will wait for that application to terminate in order to execute the next instruction. With that being said, CMD can have the correct ERRORLEVEL. But when calling a non-console application, CMD runs the application and returns immediately without waiting the application to be terminated. So it can't have a correct ERRORLEVEL. using START /WAIT app.exe will create a new CMD2(console) that runs app.exe inside CMD2. and current CMD will wait CMD2 to terminate. So it would have the correct ERRORLEVEL.
  24. Mahdi Safsafi

    Problem with ExitCode

    You can think a batch as its running line by line. For cmd, you need to use start wait app.exe START /WAIT VCLApp.exe ECHO %ERRORLEVEL%
  25. Mahdi Safsafi

    Regex Validate string

    \d handles number from any language.
×