FPC can't catch windows exceptions (av's) in a try/except in a dll call
Original Reporter info from Mantis: carlokok
-
Reporter name: Carlo Kok
Original Reporter info from Mantis: carlokok
- Reporter name: Carlo Kok
Description:
FPC doesn't set up fs:0 for a try/except but uses it's own stack instead, this causes av's to be passed to the host app, and completely ignore any try's in the dll functions (including dynamic type freeing).
Steps to reproduce:
Compile the code below and run. Notice that the dll try/finally is ignored.
{$MODE DELPHI}
program MYStartTest;
type mydelegate = procedure;
function GetDelegate: MyDElegate; stdcall; external 'test.dll';
var
del: MyDelegate;
begin
Del := getdelegate;
del();
end.
-------------------------------------------------------------
{$MODE DELPHI}
Library test;
uses
Sysutils;
type
MyDelegate = procedure;
procedure CauseAV;
var
r: ^Integer;
begin
R := nil;
IntToStr(r^);
end;
procedure CatchAV;
begin
try
CauseAV;
except
end;
end;
function GetDelegate: MyDElegate; stdcall;
begin
result := CatchAV;
end;
exports
GetDelegate;
begin
end.
Additional information:
10:36 &LtPos;oliebol> Does something need to be done for exceptions to work over module bounderies?
10:36 &LtPos;oliebol> Like initialize SEH per module or so?
10:37 &LtPos;Thorsten[NX]> ki9a, that's ok, as long as the capabilities are the same as under D2007 and older it'll be ok
10:38 &LtPos;ki9a> oliebol: The Host fpc app can catch the av that the dll can't.
10:39 &LtPos;Thorsten[NX]> under windows SEH doesn't need to be specially initialized, the current handler is identified by a pointer in the thread specific data (fs selector)
10:39 &LtPos;ki9a> Thorsten[NX]: it works yeah. The only thing that I don't support atm is fpc on win64 and linux64.
10:39 &LtPos;Thorsten[NX]> a try/finally or try/except block should be installing it's handler by saving the original value somewhere and putting a pointer to itself into that
10:40 &LtPos;oliebol> ki9a: so FreeBSD64 is supported? Brave!
10:40 &LtPos;ki9a> funny
10:40 &LtPos;ki9a> Thorsten[NX]: Finally isn't called either.
10:40 &LtPos;neli> hehe
10:40 &LtPos;neli> almost nothing is aware of dll boundaries afaik
10:41 &LtPos;oliebol> So it could be that it works but the attempt to unwind the stack fails?
10:41 &LtPos;ki9a> it's like it doesn't think there is an exception at all.
10:41 &LtPos;Thorsten[NX]> to get the initial handler under SEH in windows shouldn't involve the stack at all
10:42 &LtPos;ki9a> if I raise an Exception
10:42 &LtPos;ki9a> it does catch it in the dll
10:42 &LtPos;Thorsten[NX]> take a look at the asm code created for your "try" statement in the DLL, it should be emitting code putting a value into [fs:???]
10:42 &LtPos;ki9a> a plain raise Exception.Create();
10:44 &LtPos;ki9a> Thorsten[NX]: it calls FPC_PUSHEXCEPTADDR and FPC_SETJMP
10:51 &LtPos;Thorsten[NX]> I don't have a reasonable current version of the FPC source on this computer currently
10:51 &LtPos;ki9a> Thorsten[NX]: problem is that raise exception('') does get caught
10:52 &LtPos;ki9a> so its only fails to do its work for AVs
10:52 &LtPos;Thorsten[NX]> what should happen on a try is that a pointer to a 16 byte structure is placed into fs:0 on the try
10:53 &LtPos;ki9a> divide by zero doesn't get caught either.
10:54 &LtPos;Thorsten[NX]> that 16 byte structure has as first value the old value from fs:0, as 2nd value the ebp, as 3rd a pointer to code which should be executed in case of an exception and as 4th value a
paramter which will be put into eax when calling the 3rd pointer
10:55 &LtPos;Thorsten[NX]> the result is that you get a linked sequence of these handler descriptors by starting with fs:0 and following from there
10:55 &LtPos;ki9a> Thorsten[NX]: since it can properly catch an exception class, that seems to be oke.
10:55 &LtPos;Thorsten[NX]> ok.. then the issues is probably somewhere in the handler code that is being called
10:56 &LtPos;Thorsten[NX]> meaning whatever code is behind that 3rd value in the exception frame descriptor
10:56 &LtPos;fpcfan-work> looking at the RTL source, I would say that FS is only filled at startup
10:57 &LtPos;Thorsten[NX]> I'm not sure about FPC right now, but in delphi that's usually just a jump to _HandleAnyException, _HandleFinally or _HandleOnException (depending on context)
10:58 &LtPos;Thorsten[NX]> hm, fs:0 would need to be written to on every try (to install the new exception frame) and every finally/except (to remove it)
10:58 &LtPos;fpcfan-work> why?
10:58 &LtPos;fpcfan-work> a new exception frame can be installed without filling fs:0?
10:59 &LtPos;Thorsten[NX]> fs:0 always contains the pointer to the most current active exception frame, this is where the OS exception handling code will look for the active handler and call it
10:59 &LtPos;fpcfan-work> for example on hte heap?
10:59 &LtPos;Thorsten[NX]> fs:0 contains the pointer to the 16 byte exception frame descriptor
10:59 &LtPos;fpcfan-work> and put generic code in fs:0, which calls teh current exception frame pointer
10:59 &LtPos;Thorsten[NX]> which in turn as first entry contains the pointer to the next outer excpetion frame descriptor
11:00 &LtPos;Thorsten[NX]> fs:0 does not contain code
11:00 &LtPos;Thorsten[NX]> it contains a pointer to an exception frame descriptor
11:00 &LtPos;ki9a> fpcfan-work: that would mean a dll written in fpc can never catch exceptions if the host isn't written in fpc ?
11:01 &LtPos;fpcfan-work> maybe
11:02 &LtPos;fpcfan-work> I am just looking at the windows RTL and drawing some conclusions
11:02 &LtPos;fpcfan-work> I see this code for the first time
11:02 &LtPos;fpcfan-work> and it doesn't seem to map on the description from Thorsten[NX]
11:02 &LtPos;Thorsten[NX]> http://www.rohitab.com/sourcecode/seh.html this shows relatively well how SEH is supposed to work under windows
11:04 &LtPos;Thorsten[NX]> as you can see, on "try" it installes a new pointer into fs:0, and the first pointer in that memory needs to be the old value of fs:0
11:04 &LtPos;Thorsten[NX]> this is what chaines all the exception handlers togehter
11:04 &LtPos;Thorsten[NX]> uninstalling the exception handler on finally/except is done by restoring the original value of fs:0
11:06 &LtPos;ki9a> reading this, fpc indeed seems to install it only once and do it's own tree.
11:06 &LtPos;Thorsten[NX]> that is wrong for windows which has exception handling integrated into the OS expecting a very specific structure
11:07 &LtPos;Thorsten[NX]> and that would naturally explain why the DLL can't catch any exceptions, it doesn't have an handler installed, instead the OS looks at fs:0 and will find the last handler installed in
the current codepath which is probably somewhere in the host
11:09 -!- Chain|iB [n=charlie@catv-80-99-83-145.catv.broadband.hu] has quit ["[.life.support.system.failure.]"]
11:10 &LtPos;ki9a> i suppose the same issue exists on linux
11:10 &LtPos;ki9a> and other platforms, and it only installs a signal handler once?
11:11 &LtPos;Thorsten[NX]> SEH under linux is a more complex proposition IIRC because it's not part of the OS as such but handled at the RTL level
11:11 &LtPos;ki9a> right
11:11 &LtPos;ki9a> but the linux signal handlers are used for av's and other errors aren't they?
11:11 &LtPos;Thorsten[NX]> (that might not be true anymore, it's been a couple of years that I last looked at this)
11:11 &LtPos;Thorsten[NX]> AFAIK, yes
11:13 &LtPos;ki9a> if those are installed only once, like the "signals.pp" unit on windows does
11:13 &LtPos;ki9a> the same problem would exist there.
11:14 &LtPos;Thorsten[NX]> you could get away with using your own stack for exception frames as long as as the OS level exception handler is installed on every entry point that can be called from another module
11:15 &LtPos;ki9a> technically that can be at any outer try
11:15 &LtPos;Thorsten[NX]> but even then, under windows tools like e.g. WinDbg depend on this linked list of exception handler descriptors starting at fs:0
11:16 &LtPos;Thorsten[NX]> ki9a, if you acknowledge the possibility that your code might hand out pointers directly to specific methods, then yes, it can be any try, which means that the best solution is to use
SEH the way it's intended under windows
11:19 &LtPos;ki9a> the code that originally showed this requires delphi prism to be installed, vs.net installed and is loaded by the vs.net debugger during debugging.
11:19 &LtPos;ki9a> i thought this was easier.
11:26 &LtPos;Thorsten[NX]> http://www.microsoft.com/msj/0197/Exception/Exception.aspx
11:26 &LtPos;Thorsten[NX]> that seems to explain it pretty well, at least in regards to Win32
11:26 &LtPos;Thorsten[NX]> I think under Win64 it works a bit different
11:26 &LtPos;ki9a> hm. I know this same code fails in win64 equally bad
11:27 &LtPos;Thorsten[NX]> it would
11:28 &LtPos;Thorsten[NX]> as far as I know under win64 the fs:0 is not used, instead it's necessary to walk the stack to find the exception handlers, the structure of the stack under win64 is much stricter
regulated under win64 then win32 which makes this possible
11:30 &LtPos;Thorsten[NX]> only mention I found about exception handling under win64 is this:
11:30 &LtPos;Thorsten[NX]> "On amd64, the stack needs to be examined. There are no exception registration records; the return addresses are walked to find the call stack and the exception handling context for
each call frame.
11:30 &LtPos;Thorsten[NX]> So if you're a compiler, you must not generate code like this:
11:30 &LtPos;Thorsten[NX]> 0000000100001C6E: FF 15 A4 F4 FF FF call qword ptr [__imp_DoSomething]
11:30 &LtPos;Thorsten[NX]> 0000000100001C74: 66 C7 03 00 00 mov word ptr [rbx],0
11:30 &LtPos;Thorsten[NX]> ... because then the return address of the call would be the mov instruction, and exceptions raised in DoSomething() would be in the same context as exceptions raised by the mov
instruction.
11:30 &LtPos;Thorsten[NX]> So the compiler adds a nop:
11:30 &LtPos;Thorsten[NX]> 0000000100001C6E: FF 15 A4 F4 FF FF call qword ptr [__imp_DoSomething]
11:30 &LtPos;Thorsten[NX]> 0000000100001C74: 90 nop
11:30 &LtPos;Thorsten[NX]> 0000000100001C75: 66 C7 03 00 00 mov word ptr [rbx],0
11:30 &LtPos;Thorsten[NX]> That nop's sole purpose in life (so far as I can determine, anyway) is to provide a return address from the call."
11:31 &LtPos;Thorsten[NX]> I haven't done anything with win64 myself yet, so I'm not sure about the in's and out's of exception handling there
11:32 &LtPos;Thorsten[NX]> but for win32 I'm 100% sure you need to properly install/uninstall EXCEPTION_REGISTRATION records into fs:0
11:37 &LtPos;ki9a> k
11:37 &LtPos;ki9a> i suppose I should creat ean issue for this then.
11:41 &LtPos;ki9a> this also means strings are never freed
11:42 &LtPos;fpcfan-work> never freed, when an exception occurred, you mean?
11:42 &LtPos;ki9a> yes
11:42 &LtPos;ki9a> as the finally isn't ever called
11:42 &LtPos;ki9a> presuming fpc does a try/finally for that too
11:43 &LtPos;fpcfan-work> right
Mantis conversion info:
- Mantis ID: 12974
- OS: Windows
- OS Build: 5
- Build: 2.2.2 final
- Version: 2.2.2
- Fixed in version: 3.2.0
- Fixed in revision: 43830 (#171142a7)
- Monitored by: » ABel (Alexander Belyakov), » return0@pisem.net (Alex Belyakov), » rse (rse), » softvision (Bernd Engelhardt), » Giel (Giel), » @obones (obones), » sethdgrover@gmail.com (Seth Grover), » PaulIsh (Paul Ishenin), » Adriaan van Os (Adriaan van Os), » and (Andrew G. Khodotov), » parcel (Do-wan Kim), » scribly (Eric Heijnen), » AntonK (Anton Kavalenka), » @PascalDragon (Sven Barth), » 7bit (Bernd Kreuss)
- Target version: 3.2.0