Page 1 of 2 12 LastLast
Results 1 to 10 of 14

Thread: Exception mayhem. Beware of DLLs.

  1. #1

    Exception mayhem. Beware of DLLs.

    (first, I should note that this all is about Free Pascal 2.0.4. I didn't test it in Delphi, but that doesn't mean Delphi cannot have the same issues)


    Did you know that there are types of exceptions that shoot straight through all your try..except blocks as if they just aren't there? :shock:

    I stumbled onto one such pitfall a few days ago.

    The host program loads a dll, they interact tightly, set alot of callback hooks so that it mostly the dll that calls the functions from the host program, not vice versa as it usually is...

    You may remember that I convert strings to PChars in these functions and avoid the classes like plague. Because the class definitions are not the same in the host program and the dll, both having its own RTL and memory manager.

    So, that's not the end of the story. The exception handling mechanisms are different too! And more, the exceptions in Pascal are classes too... Guess, what?.. (ExceptObject as Exception) in the host program and (ExceptObject as Exception) in the dll are totally different things. And not only that.

    I use the matryoshka-like structure of nested Try..except blocks to build comprehensible error messages. Each except block adds its own opinion to the error log, then raises an exception to relay the event to the next block... Now imagine this situation:

    The host program calls procedure A from the dll, which in turn calls the procedure B from the host program. In B, some crap happens and it calls my procedure Die(message: string);

    Now, the Die, essentially does the following:
    -- if dying flag not set then try to add (ExceptObject as Exception).Message to the log (plus, some info on GetLastError() on win32 platform).
    -- add the message to the error log
    -- set the dying flag
    -- Raise Exception.Create('');

    And guess what?.. The exception raised with the last line shoots right through all the elaborate traps and safeguards in the A procedure, and is caught only in the host program, in the first block that surrounds the call to A. The cleanup doesn't happen, the files are left open... many bad things.

    So, I (aside from adding some elaborate callback dancing to determine whose ExceptObject is raised and passing its message safely) switched from the Pascal mechanism of raise Exception.Create('') to calling OS -- because those exceptions are caught normally in any block.

    Ok, In Win32 it's RaiseException(0, 0, 0, nil); . But what for Linux...? Being lazy as I am, I didn't bother to search for its equivalent and simpy invoked
    inc(Byte(nil^)); Blam! and Crash To Desktop, my program died instant death. :shock: :cry:
    Hell, I didn't have such accident for years! My engine has so much layers of defense that you need at least a nuke (or something equally prominent like SIGKILL) to force it down without even a squeak. And there we are. Some shitty inc(Byte(nil^)); shoots it dead.
    Next I tried in Win32 (Wine 0.9.36, to be exact). It worked without a notch. "W.T.F." I thought, and replaced it with
    [pascal] procedure RaiseSystemException;
    var
    a: integer;
    p: pinteger = nil;
    begin
    a:=p^;
    //inc(Byte(nil^));
    end;[/pascal]
    And guess what?.. It works now in Linux without any complaints, displaying my own, OpenGL-based Blue Screen of Death, as intended.

    Now, anybody knows why so dramatic difference between inc(Byte(nil^)); and invoking the same AV via variables?? :?:

  2. #2

    Exception mayhem. Beware of DLLs.

    ask in #fpc.
    This is my game project - Top Down City:
    http://www.pascalgamedevelopment.com...y-Topic-Reboot

    My OpenAL audio wrapper with Intelligent Source Manager to use unlimited:
    http://www.pascalgamedevelopment.com...source+manager

  3. #3

    Exception mayhem. Beware of DLLs.

    to be honest it was very hard to follow what you wrote

    Linux has no builtin-support for exceptions, so I guess exception-handling is program-counter based in FPC just like in Kylix....I personally prefer to not use exceptions in dlls and safeguard all callback executions in them as well as a top-level catch block in every exported function which simply sets the return value of the function to some constant....this way no matter what you do, all exceptions are caught....an exception thrown from C++ host for example would be reported as an EExternal with error code matching magic exception code shared by all C++ exceptions....I did not quite understand your problem with the raise Exception.Create('') and bypassing all installed handlers, I'd say a snippet of code is better than thousands of words

  4. #4

    Exception mayhem. Beware of DLLs.

    'd say a snippet of code is better than thousand of words
    I'm afraid a snippet enough to understand my monster would take a few full forum pages and send dr. Frankenshtein running screaming in terror.

    The matter is, usual way is to define some functionality, wrap it into a dll, an then make the program load and use it. Everybody knows how this works: you export a function from the dll, the host program calls it...

    Well, I turned this upside down. It's the dll that uses the host program like a sack of functions. The host program exports its own functions to the dll via custom-made mechanism (don't ask to show it. It's a veawe of macros includes that would make your eyes hurt).

    So, aside from the host program initially loading the dll, feeding it the callback hook address and then calling its OnIdle function repeatedly (and except the fact that the main loop belongs to the host app, because it owns the window and receives all messages), the active participant here is the dll. It calls a lot of host program's functions that have their own try..except blocks used to dispatch and comment errors.

    But if something crashes seriously, the exception goes up to the host program that unloads the dll and asks for further instructions.

    and safeguard all callback executions in them as well as a top-level catch block in every exported function which simply sets the return value of the function to some constant...
    I'm afraid, if else fails I'll have to take that way. A lot, A hell of a lot of work. There are more than hundred functions that i would need to revise, including all their calls... I'd better not.

    Btw., I finally found the variant that works 100%. It seems.
    [pascal] procedure RaiseSystemException;
    var
    a: integer;
    p: pinteger = nil;
    begin
    {$ifdef win32}
    RaiseException(0, 0, 0, nil);
    {$else}
    if CurrentOwner = NOT_A_MODULE //true when the execution goes
    // in the host program's main loop
    //false when the callto the module dll is
    // being performed
    then raise EDying.Create('')
    else a:=p^;
    {$endif}
    end; [/pascal]

    The result of all this shamanistic dancing:



  5. #5

    Exception mayhem. Beware of DLLs.

    Well... Don't do that at home, kids... :x

    In the host app:

    [pascal]
    Procedure Die(YellID :TMessageID; Param: array of const);
    var
    U: WideString;
    Ey: AnsiString;
    begin
    U:=PervertedFormat(MessageContainer[YellID], Param);
    if not Dying then begin
    CheckForGenericDyingYells(U);
    AddLog(MI_SHIT_HAPPENS_SEE_BELOW, [EChar]);
    Dying:=Yes;
    end;
    Ey:=WideToAnsi(U);
    WarningQueue.Add(U);
    RaiseSystemException;
    end; [/pascal]

    In the module dll:
    [pascal] procedure TModule.OnCycle;
    begin
    PrevFrameTime:=FrameTime;
    f_FrameTime:=0.0;
    if Assigned(Menu) then Menu.Cycle;
    if Assigned(Game) and not GamePaused then Game.Cycle(PrevFrameTime);
    if Assigned(Game) then try
    Game.Render;
    except
    AddLog(RuEn(
    'Крах при отрисовке игрового мира...'
    ,'Game render crashed...'));
    try
    SaveSession;
    except
    Die(RuEn(
    'Крах при отрисовке игрового мира.'#10#13'Песец, аварийное сохранение не удалось '
    ,'Game render crashed.'#10#13'Emergency save attempt failed '))
    end;
    Die(RuEn(
    'Крах при отрисовке игрового мира.'#10#13'Аварийное сохранение удалось! '
    ,'Game render crashed.'#10#13'Emergency save sucsess! '))
    end
    else RenderBackground;
    if Assigned(Menu) then Menu.Render;
    end;[/pascal]

    In the host app's function used by the module DLL:
    [pascal] procedure CgeFilePropByType(t: AnsiChar; name: PAnsiChar);
    begin
    C.Header^.sign:='CGE';
    {$ifndef fpc}
    if t='s' then t:='0';
    {$endif}
    Case t of
    '0': with C do begin
    Compression:=cfc_None;
    DoMd5:=No;
    end;
    's': with C do begin
    Compression:=cfc_None;
    DoMd5:=Yes;
    end;
    else
    Die(MI_CGEFILE_UNSUPPORTED, [Name, 'CGE' + t])
    end;
    end;[/pascal]

    How else could it be done?

    ...aaand we have deviated from the topic, going straight in the "your projects" direction.

  6. #6

    Exception mayhem. Beware of DLLs.

    now this looks HACKY (especially the first function - and what is PervertedFormat...forget it )....what do the dlls actually do?....you said you reversed the logic, so what's the purpose of the libs now when the main code is inside the host application and dlls use it...that makes sense only for some plugin architecture in which case I'd go with interfaces....

  7. #7

    Exception mayhem. Beware of DLLs.

    Well, the host app is a mere thrall. The main game code is in the dll (a "module" as I call it). It can be recompiled and reloaded "on the fly" in the matter of milliseconds. For this to be possible, we need:
    a). That the code won't be locked in Windows (does it by first copying the dll to a new location and only then launching it).
    b). That the resources that take a long time to load (sounds, textures, the application window, the loaded and initialized OpenGL with its context) won't be unloaded together with the game code. So, the host application owns them all, serving as a storage basket for the game module when it gets unloaded, then loaded again.
    c). That the session (the entire game state) is 100% saveable and is fully restored at reload. For this, I built my persistency system.

    The goal?
    To create a system where you can modify your algorithms on the fly, without restarting the game. (ever tried to build something for Morrowind or Neverwinter? Debugging eats up 95% of your time, because you need to restart the game each time).

    now this looks HACKY
    You didn't see chepersy then. It hacks into the VMT structure, injecting its own data there, the Delphi version needs to modify memory access rights to allows that.

    PervertedFormat...? A bit similar to format. It takes a widestring (directly of from a text manager) where the parameters to be inserted marked with %0, %1 and so on, and a random list of parameters of random type ("array of const") and composes a message from it. Initially returned an array of widestrings (thus the name).

    I started working on this thing when FreePascal was still in its diapers, wihout widestring and dynamic array support. Some things are left untouched since then.

  8. #8

    Exception mayhem. Beware of DLLs.

    I have no idea wtf that thing you wrote is, but if it is supposed to be a exception handler replacement - i made one which worls as replacement to delphi's (handles all runtime and try..except errors).
    This is my game project - Top Down City:
    http://www.pascalgamedevelopment.com...y-Topic-Reboot

    My OpenAL audio wrapper with Intelligent Source Manager to use unlimited:
    http://www.pascalgamedevelopment.com...source+manager

  9. #9

    Exception mayhem. Beware of DLLs.

    yes I saw your persistency system...I wouldn't put it in production code that is (I mean some normal payed project) )))

    no he had problems with exceptions, he did not want an alternative exception handling system...

  10. #10

    Exception mayhem. Beware of DLLs.

    I wouldn't put it in production code that is (I mean some normal payed project)
    And why exactly?
    Plus, it's still in the alpha stage, not even feature-complete, not to say about the real, in-use testing. :roll:

    no he had problems with exceptions, he did not want an alternative exception handling system
    That's true. Not that I wouldn't want, but such things must be incorporated into project from the very start. It would require too many changes now. Not to mention that I use the Vampyre Imaging library that itself uses exceptions to report errors.

Page 1 of 2 12 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •