Jump to content
Eric Grange

Tracking down exception in initialization section ? (RaiseExceptObject)

Recommended Posts

Hi,

I have an application that (probably) triggers an exception in one of its initialization sections, and of course that does not happen when debugging, only occasionally when in use. I know it's in a initialization because it happens before the "begin" of the main program, and after the initialization section of unit declares as the first in the "uses" of the main program.
There is no stack trace, just an AV, and I could reproduce something similar in the debugger by having an assert or raise in an initialization section. The exception is first raised correctly, but then the RTL goes on to trigger an AV in System._IsClass. With debug DCUs the call stack looks like:
 

System._IsClass($5DC63C0,TClass($433C10))
System.SysUtils.RaiseExceptObject(???)
System._RaiseAtExcept(???,???)
System._RaiseAgain
System.InitUnits
System._StartExe(???,???)
SysInit._InitExe(???)


this happens the second time going through RaiseExceptObject, which has a line
 

if TObject(P.ExceptObject) is Exception then


During the first time through, TObject(P.ExceptObject) is correct, but the second time it's obviously invalid, which triggers an AV in _IsClass

This happens with D10.3 and D11.2, both in Win32 (Runtime Error 217) and Win64 (Access violation)

Is there a known workaround ?

Alternatively is there a way to dump the initialization order of InitUnits so that I can bisect to the problematic initialization using a log ?
 

Share this post


Link to post

No that's the limitation of the Delphi. It doesn't support exceptions during initialization of the application. The same problem has Skia if the dll is missing (of course in case of skia there is an easy fix replace the exception with showmessage).

  • Like 1

Share this post


Link to post
49 minutes ago, Eric Grange said:

is there a way to dump the initialization order of InitUnits

Can't you just step into System.InitUnits() or put a breakpoint there?

Share this post


Link to post
Quote

Can't you just step into System.InitUnits() or put a breakpoint there?


The project has about 4000 units... this is why I would rather have a way to get a list of those units in the order InitUnits calls them, so I can bisect.
(also the issue is infrequent, I have been unable to get it when debugging)

If worse comes to worse, I will probably hack to get the raw InitUnits call addresses, and then resolve them to unit names with the detailed map file.


 

Edited by Eric Grange

Share this post


Link to post

Ok, in case anyone encounters a similar issue, here is the ghetto method I used to obtain units initialization order.
First after the "begin" of the main, program call a procedure like [ complicated approach deleted ]

Just use the map file as Uwe Raabe pointed below,to get the order of initialization, the detailed segments section lists the units in order of initialization.
 

 

Edited by Eric Grange
  • Thanks 1

Share this post


Link to post
8 hours ago, Eric Grange said:

Alternatively is there a way to dump the initialization order of InitUnits so that I can bisect to the problematic initialization using a log ?

AFAIK; the initialization order of units follows the order of the ICODE (Initialization Code-Segment) segments in the Detailed map of segments in the map file.

  • Like 1

Share this post


Link to post
1 hour ago, Anders Melander said:

Have you tried madExcept?

No, I do not have it. Do you know if it handles exception in the initialization section ?

 

35 minutes ago, Uwe Raabe said:

AFAIK; the initialization order of units follows the order of the ICODE (Initialization Code-Segment) segments in the Detailed map of segments in the map file.

You're right it does! Haha, or in the case of the 64bit map, it's just the CODE sections.

Ah well... 🙂

FWIW the issue was related to the TNotificationCenter, which a unit was initializing ahead of time to avoid the incorrect app name reporting (https://en.delphipraxis.net/topic/4102-embarcadero-toaster-notification-window-caption-in-win10/), and this would sometimes fail with a Delphi 10.3 exe (the issue is not present in Delphi 11.2 afaict). The issue had been in the code for about 6 months before encountering a situation where it would be problematic with a particular combo of user rights.

Share this post


Link to post
1 hour ago, Eric Grange said:

No, I do not have it. Do you know if it handles exception in the initialization section ?

It's free for non-commercial use. And yes, it does handle exceptions in the initialization section.

image.png.a85ed82e2001a22b20102a7d451de64a.png

image.thumb.png.a183745c90793821955612e041558f82.png

Edited by Anders Melander

Share this post


Link to post
16 hours ago, Eric Grange said:

First after the "begin" of the main, program call a procedure like [ complicated approach deleted ]

Why deleted ?!!

 

I am interested as i have similar approach to list the compiled units in the application for some extreme logging.

 

So would you please share again ?

 

Here is mine, which i believe can be adjusted to get the initializing order from running application itself. 

var
  ThisPackageInfo:PackageInfo;

function GetInitContext(NotUsed:Pointer = nil):Pointer;
const
  SIZE_OF_POINTER = SizeOf(Pointer);
  PLATFORM_OFFEST_CORRECTION = {$IFDEF WIN32}4{$ELSE}1{$ENDIF};
asm
  mov NotUsed , [ ESP - PLATFORM_OFFEST_CORRECTION * SIZE_OF_POINTER ]
  mov Result , NotUsed
end;

begin  // project begin
  ThisPackageInfo := GetInitContext;  // retriving the PackageInfo pointer from dirty stack, must be the first after begin.
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
procedure TForm10.EnumUnits;
var
  i, Count, NameLength: Integer;
  PUnitName: PAnsiChar;
  UnitName: AnsiString;
  UnitsArr: array of string;
begin
  if (not Assigned(ThisPackageInfo)) or (ThisPackageInfo^.UnitCount = 0) then
    exit;

  PUnitName := PAnsiChar(ThisPackageInfo.TypeInfo.UnitNames);

  Count := ThisPackageInfo.TypeInfo.UnitCount;
  SetLength(UnitsArr, Count);
  i := 0;
  while i < Count do
  begin
    NameLength := Ord(PUnitName^);
    if NameLength = 0 then
      Break;
    SetLength(UnitName, NameLength);
    Inc(PUnitName);
    Move(PUnitName^, UnitName[1], NameLength);
    Inc(PUnitName, NameLength);
    UnitsArr[i] := string(UnitName);

    Inc(i);
  end;

end;

Will work Win32 and Win64, the order of the list has nothing to do with initialization procs but in that table there is Init and FInit and may be there is a way to compare these addresses to the position of the units, this will allow to sort the units according to initialize proc. 

 

ps: this is a hack way to get PackageInfo pointer and only tested on older compilers.

Share this post


Link to post
On 10/11/2023 at 9:31 AM, Kas Ob. said:

Why deleted ?!!

Because the map file provides that information already.

For instance for Win64 binaries you will see at the top of the map file lines like
 

Detailed map of segments

 0001:00000000 0001C36C C=CODE     S=.text    G=(none)   M=System   ALIGN=4
 0001:0001C36C 00001944 C=CODE     S=.text    G=(none)   M=SysInit  ALIGN=4
 0001:0001DCB0 00003980 C=CODE     S=.text    G=(none)   M=System.Types ALIGN=4
 0001:00021630 00000D40 C=CODE     S=.text    G=(none)   M=System.UITypes ALIGN=4
 0001:00022370 000068BC C=CODE     S=.text    G=(none)   M=Winapi.Windows ALIGN=4
 0001:00028C2C 000006BC C=CODE     S=.text    G=(none)   M=FastMM4LockFreeStack ALIGN=4
 0001:000292E8 00000028 C=CODE     S=.text    G=(none)   M=FastMM4Messages ALIGN=4
 0001:00029310 00002570 C=CODE     S=.text    G=(none)   M=FastMM4  ALIGN=4
 0001:0002B880 00000200 C=CODE     S=.text    G=(none)   M=Winapi.WinSvc ALIGN=4
 0001:0002BA80 000007E0 C=CODE     S=.text    G=(none)   M=System.SysConst ALIGN=4
 0001:0002C260 00000040 C=CODE     S=.text    G=(none)   M=Winapi.ImageHlp ALIGN=4
 0001:0002C2A0 00000020 C=CODE     S=.text    G=(none)   M=Winapi.SHFolder ALIGN=4
 0001:0002C2C0 00000760 C=CODE     S=.text    G=(none)   M=Winapi.PsAPI ALIGN=4


And the unit order is the initialization order as well as the order in the compiled binary.

For listing at runtime my code would not really work, because it required not only manually obtaining the address of the interval Context variable, but also resolving against the map file.

However it's possible to bundle the MAP file in the executable, and retrieve that information, the JCL's JDBG format allows that (in addition to being useful for debug stacks)
https://stackoverflow.com/questions/6019698/access-jcl-debug-information-contained-in-executable
 

On 10/10/2023 at 6:11 PM, Anders Melander said:

It's free for non-commercial use. And yes, it does handle exceptions in the initialization section.

image.png.a85ed82e2001a22b20102a7d451de64a.png

 

Thanks, nice to know!

Share this post


Link to post
9 minutes ago, Eric Grange said:

the JCL's JDBG format allows that (in addition to being useful for debug stacks)
https://stackoverflow.com/questions/6019698/access-jcl-debug-information-contained-in-executable

The last time I looked, the JCL JDBG functions didn't directly surface the debug data; You can ask the functions to resolve addresses and so on, but you can't get a list of all units, symbols, line numbers, etc.

Isn't it correct that you had to modify the JCL source in order to get the JDBG data you needed out of it for your profiler?

Share this post


Link to post
1 hour ago, Eric Grange said:
On 10/11/2023 at 10:31 AM, Kas Ob. said:

Why deleted ?!!

Because the map file provides that information already.

For instance for Win64 binaries you will see at the top of the map file lines like

I do put some command line parameter to few of my projects, for tracking when things goes wrong, like one parameter and the exe will dump a file with few things like the build settings, versions of used 3rd party packages, also a list of included units, in these projects as have multiple units handle the same protocol or part of the protocol/structures while separated by version number in the unit name, if they are added then they will register them selves and handle backward compatibility, so i need to check after the fact is they are present, so no debug information or map file, these info from an exe that is protected by WinLicense so unless the exe itself generate these info no one can.

 

 I asked because i saw you mention after the begin in project file, and i thought you may have better solution or at least i can learn soemthing.

Share this post


Link to post

One more thing about this

1 hour ago, Eric Grange said:

Detailed map of segments

 0001:00000000 0001C36C C=CODE     S=.text    G=(none)   M=System   ALIGN=4
 0001:0001C36C 00001944 C=CODE     S=.text    G=(none)   M=SysInit  ALIGN=4
 0001:0001DCB0 00003980 C=CODE     S=.text    G=(none)   M=System.Types ALIGN=4
 0001:00021630 00000D40 C=CODE     S=.text    G=(none)   M=System.UITypes ALIGN=4
 0001:00022370 000068BC C=CODE     S=.text    G=(none)   M=Winapi.Windows ALIGN=4
 0001:00028C2C 000006BC C=CODE     S=.text    G=(none)   M=FastMM4LockFreeStack ALIGN=4
 0001:000292E8 00000028 C=CODE     S=.text    G=(none)   M=FastMM4Messages ALIGN=4
 0001:00029310 00002570 C=CODE     S=.text    G=(none)   M=FastMM4  ALIGN=4
 0001:0002B880 00000200 C=CODE     S=.text    G=(none)   M=Winapi.WinSvc ALIGN=4
 0001:0002BA80 000007E0 C=CODE     S=.text    G=(none)   M=System.SysConst ALIGN=4
 0001:0002C260 00000040 C=CODE     S=.text    G=(none)   M=Winapi.ImageHlp ALIGN=4
 0001:0002C2A0 00000020 C=CODE     S=.text    G=(none)   M=Winapi.SHFolder ALIGN=4
 0001:0002C2C0 00000760 C=CODE     S=.text    G=(none)   M=Winapi.PsAPI ALIGN=4


And the unit order is the initialization order as well as the order in the compiled binary.

In my Delphi XE8 and as i witnessed many times, the map unit list is not accurate, in this case, SysInit is initialized first before System, Winapi.Windows comes after SysInit, then System..

Share this post


Link to post

The order of units with initialization code are better retrieved by the C=ICODE entries instead of the C=CODE ones.

  • Like 1

Share this post


Link to post
26 minutes ago, Uwe Raabe said:

The order of units with initialization code are better retrieved by the C=ICODE entries instead of the C=CODE ones.

Away from the first two it looks accurate, never looked into it, thank you.

Share this post


Link to post

Hi @Eric,

Did you manage to solve the issue altogether?
 

I face the same problem in Delphi 11.3. At least: the same "call stack" listing appears as above in your initial post.

In Win32 mode my FMX application (single unit, simple form, a menu and some speedbuttons) runs normally, but when switched to Win64 it fails.

In ...source/rtl/system.pas the debug break is in: 

function _IsClass(const Child: TObject; Parent: TClass): Boolean;
begin
  Result := (Child <> nil) and Child.InheritsFrom(Parent);
end;

The error is in "Parent". Thereafter the error "...c0000005 ACCES_VIOLATION..." appears. Any ideas how to solve this?

Share this post


Link to post

I have this theory,

On 10/10/2023 at 9:21 AM, Eric Grange said:

System._IsClass($5DC63C0,TClass($433C10))
System.SysUtils.RaiseExceptObject(???)
System._RaiseAtExcept(???,???)
System._RaiseAgain
System.InitUnits
System._StartExe(???,???)
SysInit._InitExe(???)

this happens the second time going through RaiseExceptObject, which has a line


if TObject(P.ExceptObject) is Exception then

The call stack is clearly shows this sequence calls SysInit -> System -> SysUtils, but based on my observation and my lack to trust the map file, which me be unfounded,

anyway, SysUtils called few times before Initialization for class constructor for TLanguages, TEncoding, Exception.Create, TOSVersion.Create then Initialization 

 

So the question here, do you overload (define) an Exception and use it in some Initialization section for some unit, or any of the rest mentioned above ? 

This might confused the compiler and Exception is not initialized yet and its value is just arbitrary, this might explain the first success and a failure on the second.

Share this post


Link to post
On 10/10/2023 at 8:28 AM, Lajos Juhász said:

replace the exception with showmessage

I call MessageBox because ShowMessage had some issues before initialization.

Share this post


Link to post

I traced back that the problem is caused by the use of the SKIA library.

When I create and run a new Win64 multidevice application with SKIA enabled as shown here below the error occurs. Win32 runs OK.

program Project1;

uses
  System.StartUpCopy,
  FMX.Forms,
  SKIA, SKIA.FMX,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  GlobalUseSkia := TRUE;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

I have updated SKIA to the version 5.0, but this did not solve the problem.

Any ideas about the cause of this? 

 

Edited by JGMS
SKIA update and test

Share this post


Link to post

Problem solved: There is one more action required when switching to Win64, i.e. "Enable SKIA" in the Projects window of the IDE.

Good to know for all. SKIA is fantastic!

Share this post


Link to post
On 10/13/2023 at 1:13 PM, Uwe Raabe said:

The order of units with initialization code are better retrieved by the C=ICODE entries instead of the C=CODE ones.

AFAICT there are no ICODE in the map file for Delphi 64 builds.

 

On 10/14/2023 at 7:14 PM, JGMS said:

Hi @Eric,

Did you manage to solve the issue altogether? [...]
The error is in "Parent". Thereafter the error "...c0000005 ACCES_VIOLATION..." appears. Any ideas how to solve this?

I solved the issue by not having an exception raised... And found the unit that triggered the exception through "Halt" and bisection from the unit list.

There were 3000+ units in the whole project, but since it was during the initialization, bisecting didn't take long (add Halt to unit initialization, run and see if Halt reached or not, bisect and repeat)

  • Like 1

Share this post


Link to post

will it catch?

place it at the top of the uses list in the dpr, set a breakpoint at the "begin" in "TExceptionCatcher.RaiseException" and examine the call stack

 

unit ExCatcher;

interface

uses
  Winapi.Windows;

type
  TExceptionCatcher = class
  public
    class procedure RaiseException(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD; lpArguments: PDWORD); stdcall;
  end;

implementation


class procedure TExceptionCatcher.RaiseException(dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD; lpArguments: PDWORD);
begin // place breakpoint here

end;

initialization

RaiseExceptionProc := @TExceptionCatcher.RaiseException;

end.

 

Edited by Attila Kovacs

Share this post


Link to post

Thanks @Atilla,
I will add the unit to my repository. It may be of future use.

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

×