Question

For debugging our slow-starting Delphi XE3 application, we would like to log the initialization-phase of all used units in our system.

To complete this task, we need the Unit Name for each initialization call.

Analyzing the Data, when stepping through InitNames, we find the first Unit Name inside:

InitContext.InitTable^.TypeInfo.UnitNames

but we do not know how to fetch the appropriate Name from the Unit ID I before calling the Initialization procedure. Code Documentation says, TypeInfo.UnitNames contains the concatenated Unit Names of all Units.. But how do i travel between them? It is not an array nor a long string with seperators.

The code, where i would like to insert the log routine.

procedure InitUnits;
var
  Count, I: Integer;
  Table: PUnitEntryTable;
  P: Pointer;
begin
  if InitContext.InitTable = nil then
    exit;
  Count := InitContext.InitTable^.UnitCount;
  I := 0;
  Table := InitContext.InitTable^.UnitInfo;
{$IFDEF LINUX}
  Inc(PByte(Table), InitContext.Module^.GOT);
{$ENDIF}
  try
    while I < Count do
    begin

      /////////////////////////////////////
      MyLogCode( 'Unit: ' + Get UnitName here )  
      /////////////////////////////////////

      P := Table^[I].Init;
      Inc(I);
      InitContext.InitCount := I;
      if Assigned(P) and Assigned(Pointer(P^)) then
  begin
{$IF defined(MSWINDOWS)}
    TProc(P)();
{$ELSEIF (defined(POSIX) and defined(CPUX86))}
        CallProc(P, InitContext.Module^.GOT);
{$ELSE}
        TProc(P)();
{$ENDIF}
      end;
    end;
  except
    FinalizeUnits;
    raise;
  end;
end;

Recompiling the System.pas will be done via Arnaud Bouchez suggested solution.

Was it helpful?

Solution

UnitNames contains multiple unit names. They are Pascal short strings, concatenated. However, as we shall see, they are not the names that you need.

Break in the debugger in InitUnits and evaluate:

PAnsiChar(InitContext.InitTable.TypeInfo.UnitNames)

In my simple test project, a console app that just uses SysUtils, you see the following:

#$F'System.SysUtils'#6'System'#$18'System.Internal.ExcUtils'#$F'System.SysConst'
#7'SysInit'#$10'System.Character'#$E'Winapi.Windows'#$E'System.UITypes'
#$C'System.Types'#$10'System.RTLConsts'#$C'Winapi.PsAPI'#$F'Winapi.SHFolder'
#$F'Winapi.ImageHlp‹À'

The first character is the length of the string. They are concatenates one after the other, with a total of InitContext.InitTable.TypeInfo.UnitCount names. For my simple project InitContext.InitTable.TypeInfo.UnitCount evaluates to 13.

However, these names do not correspond to the units that are initialised. In my test project InitContext.InitTable^.UnitCount has value 18, and the units are initialized in a quite different order from that listed above. As I'm sure you know, SysInit always comes first. As you can see from the above, it is in the middle of the list. So, whilst InitContext.InitTable.TypeInfo.UnitNames gives you a list of certain units, it bears no relation to the units that require initialization, nor the order of initialization.

So as I read it, UnitNames cannot help you here. My belief is that you will need to use the detailed map file to decode this. You need to look up the name of the function Table^[I].Init. If you were using, for instance, madExcept, that would be easy to do.

Of course, you might not be able to perform the lookup inside InitUnits. You are faced with a chicken and egg situation. You may need for at least some units to be initialized before you get started with your logging.

For instance, it looks like you are attempting to allocate a string variable. That will fail because the RTL heap allocator has not been initialized. Your logging code cannot perform any dynamic allocation using the RTL heap if you expect to call it before the RTL has been initialized.


This does all seem over the top to me. If I were you I would:

  1. Identify the units by index when logging. That is log the value of I.
  2. Use the results of your profiling to work out which indexes are the problematic ones.
  3. Under the debugger, use a conditional break point to break at the call to TProc(P)() associated with the index you identified in the previous step.
  4. Step into P to find out which unit it is attached to.

OTHER TIPS

In general it is best to NOT rely on the order of class constructors, initialization sections, class destructors, and finalization sections.

In a large (1.2M LOC) project one will not be able to deduce which is the order that Delphi will use.

It is best to refactor your code to avoid such dependencies or use Singleton pattern side effect (initialize when first needed).

Yet if you really have to output the order you can manually log all

  • Initialization section entries

  • Finalization section entries

  • class constructors

  • class destructors

One safe way to do it is by using the Windows API - OutputDebugString()

You can use the following unit to help you with the logging

unit unt_Debug;
{$SCOPEDENUMS ON}

interface

uses
  // System
  System.SysUtils
  {$IFDEF MACOS}
  ,FMX.Types
  {$ELSE}
  ,Winapi.Windows
  {$ENDIF};

{$WARN SYMBOL_PLATFORM OFF}
/// <remarks>
/// Output debug string. Output debug string can be seen in Delphi
/// View|Debug Windows|Event Log or with 3-rd party programs such as
/// dbgview.exe from SysInternals (www.sysinternals.com)
/// </remarks>
procedure ODS(const Text: string);

procedure ReportClassConstructorCall(const C: TClass);

procedure ReportClassDestructorCall(const C: TClass);

procedure ReportInitializationSection(const UnitName: string);

procedure ReportFinalizationSection(const UnitName: string);

implementation

procedure ReportClassConstructorCall(const C: TClass);
begin
  ODS(Format('%0:s class constructor invoked.', [C.ClassName]));
end;

procedure ReportClassDestructorCall(const C: TClass);
begin
  ODS(Format('%0:s class destructor invoked.', [C.ClassName]));
end;

procedure ReportInitializationSection(const UnitName: string);
begin
  ODS(Format('Unit %0:s - entering Initialization section.', [UnitName]));
end;

procedure ReportFinalizationSection(const UnitName: string);
begin
  ODS(Format('Unit %0:s - entering Finalization section.', [UnitName]));
end;

procedure ODS(const Text: string);
begin
  {$IFDEF DEBUG}
  {$IFDEF MACOS}
  // http://stackoverflow.com/questions/12405447/outputdebugstring-with-delphi-for-macosunit unt_Debug;
   Log.d(Text);
  {$ENDIF}
  {$IFDEF LINUX}
  __write(stderr, AText, Length(AText));
  __write(stderr, EOL, Length(EOL));
  {$ENDIF}
  {$IFDEF MSWINDOWS}
  OutputDebugString(PWideChar(Text));
  {$ENDIF}
  {$ENDIF}
end;

{$WARN SYMBOL_PLATFORM ON}

end.

It can be used for example this way:

unit Sample;

interface

type
  TSample = class
  public
    class constructor Create;
    class destructor Destroy;
  end;

implementation

uses
  unt_Debug;

class constructor TSample.Create;
begin
  ReportClassConstructorCall(TSample);
  // Do stuff
end;

class destructor TSample.Destroy;
begin
  ReportClassDestructorCall(TSample);
  // Do stuff
end;

initialization

  ReportInitializationSection(TSample.UnitName);
  // do stuff

finalization

  ReportFinalizationSection(TSample.UnitName);
  // do stuff

end.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top