从InitTable获取单元名称 - hack System.pas

时间:2014-04-11 09:37:11

标签: delphi delphi-xe3

为了调试我们的慢启动Delphi XE3应用程序,我们想记录系统中所有使用过的单元的初始化阶段。

要完成此任务,我们需要每个初始化调用的单位名称。

分析数据,当单步执行 InitNames 时,我们会在里面找到第一个单位名称:

InitContext.InitTable^.TypeInfo.UnitNames

但在调用初始化过程之前,我们不知道如何从单元ID I 中获取相应的名称。 代码文档说,TypeInfo.UnitNames包含所有单位的连接单位名称..但我如何在它们之间旅行?它不是一个数组,也不是一个带分隔符的长字符串。

代码,我想插入日志例程。

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;

重新编译System.pas将通过Arnaud Bouchez suggested solution完成。

2 个答案:

答案 0 :(得分:3)

UnitNames包含多个单位名称。它们是Pascal短串,连接在一起。但是,正如我们将要看到的,它们不是您需要的名称。

InitUnits中打破调试器并评估:

PAnsiChar(InitContext.InitTable.TypeInfo.UnitNames)

在我的简单测试项目中,只使用SysUtils的控制台应用,您会看到以下内容:

#$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‹À'

第一个字符是字符串的长度。它们是一个接一个地连接在一起,总共有InitContext.InitTable.TypeInfo.UnitCount个名字。对于我的简单项目InitContext.InitTable.TypeInfo.UnitCount,评估为13。

但是,这些名称与初始化的单位不对应。在我的测试项目中,InitContext.InitTable^.UnitCount的值为18,单位的初始化顺序与上面列出的顺序完全不同。我相信你知道,SysInit永远是第一位的。从上面可以看出,它位于列表的中间。因此,虽然InitContext.InitTable.TypeInfo.UnitNames给出了某些单位的列表,但它与需要初始化的单位和初始化顺序无关。

所以当我读到它时,UnitNames无法帮助你。我的信念是你需要使用详细的地图文件来解码它。您需要查找函数Table^[I].Init的名称。例如,如果你使用madExcept,这很容易做到。

当然,您可能无法在InitUnits内执行查找。你面临鸡和鸡蛋的情况。在开始记录之前,您可能需要至少初始化一些单元。

例如,看起来您正在尝试分配字符串变量。这将失败,因为RTL堆分配器尚未初始化。如果您希望在RTL初始化之前调用它,则您的日志记录代码无法使用RTL堆执行任何动态分配。


这对我来说似乎都是最重要的。如果我是你,我会:

  1. 记录时按索引标识单位。这是记录I
  2. 的值
  3. 使用分析结果来确定哪些索引是有问题的索引。
  4. 在调试器下,使用条件断点来中断与您在上一步中确定的索引关联的TProc(P)()的调用。
  5. 进入P以找出它所附加的单位。

答案 1 :(得分:0)

通常,最好不要依赖类构造函数,初始化部分,类析构函数和终结部分的顺序。

在一个大型(1.2M LOC)项目中,人们将无法推断出Delphi将使用的订单是什么。

最好重构代码以避免此类依赖或使用Singleton模式副作用(首次需要时初始化)。

然而,如果你真的必须输出订单,你可以手动记录所有

  • 初始化部分条目

  • 最终确定部分条目

  • 类构造函数

  • 类析构函数

一种安全的方法是使用Windows API - OutputDebugString()

您可以使用以下单元来帮助您进行日志记录

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.

它可以这样使用:

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.
相关问题