Componentes.Terceros.jcl/official/1.100/source/windows/JclDebug.pas

5085 lines
156 KiB
ObjectPascal

{**************************************************************************************************}
{ }
{ Project JEDI Code Library (JCL) }
{ }
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
{ you may not use this file except in compliance with the License. You may obtain a copy of the }
{ License at http://www.mozilla.org/MPL/ }
{ }
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
{ ANY KIND, either express or implied. See the License for the specific language governing rights }
{ and limitations under the License. }
{ }
{ The Original Code is JclDebug.pas. }
{ }
{ The Initial Developers of the Original Code are Petr Vones and Marcel van Brakel. }
{ Portions created by these individuals are Copyright (C) of these individuals. }
{ All Rights Reserved. }
{ }
{ Contributor(s): }
{ Marcel van Brakel }
{ Flier Lu (flier) }
{ Florent Ouchet (outchy) }
{ Robert Marquardt (marquardt) }
{ Robert Rossmair (rrossmair) }
{ Andreas Hausladen (ahuser) }
{ Petr Vones (pvones) }
{ Soeren Muehlbauer }
{ }
{**************************************************************************************************}
{ }
{ Various debugging support routines and classes. This includes: Diagnostics routines, Trace }
{ routines, Stack tracing and Source Locations a la the C/C++ __FILE__ and __LINE__ macros. }
{ }
{ Unit owner: Petr Vones }
{ }
{**************************************************************************************************}
// Last modified: $Date: 2007-05-22 21:58:20 +0200 (mar., 22 mai 2007) $
unit JclDebug;
interface
{$I jcl.inc}
{$R-,Q-}
uses
{$IFDEF UNITVERSIONING}
JclUnitVersioning,
{$ENDIF UNITVERSIONING}
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF MSWINDOWS}
Classes, SysUtils, Contnrs,
JclBase, JclFileUtils, JclPeImage, JclSynch, JclTD32;
// Diagnostics
procedure AssertKindOf(const ClassName: string; const Obj: TObject); overload;
procedure AssertKindOf(const ClassType: TClass; const Obj: TObject); overload;
{$IFDEF KEEP_DEPRECATED}
procedure Trace(const Msg: string);
{$EXTERNALSYM Trace}
{$ENDIF KEEP_DEPRECATED}
procedure TraceMsg(const Msg: string);
procedure TraceFmt(const Fmt: string; const Args: array of const);
procedure TraceLoc(const Msg: string);
procedure TraceLocFmt(const Fmt: string; const Args: array of const);
// Optimized functionality of JclSysInfo functions ModuleFromAddr and IsSystemModule
type
TJclModuleInfo = class(TObject)
private
FSize: Cardinal;
FEndAddr: Pointer;
FStartAddr: Pointer;
FSystemModule: Boolean;
public
property EndAddr: Pointer read FEndAddr;
property Size: Cardinal read FSize;
property StartAddr: Pointer read FStartAddr;
property SystemModule: Boolean read FSystemModule;
end;
TJclModuleInfoList = class(TObjectList)
private
FDynamicBuild: Boolean;
FSystemModulesOnly: Boolean;
function GetItems(Index: Integer): TJclModuleInfo;
function GetModuleFromAddress(Addr: Pointer): TJclModuleInfo;
protected
procedure BuildModulesList;
function CreateItemForAddress(Addr: Pointer; SystemModule: Boolean): TJclModuleInfo;
public
constructor Create(ADynamicBuild, ASystemModulesOnly: Boolean);
function AddModule(Module: HMODULE; SystemModule: Boolean): Boolean;
function IsSystemModuleAddress(Addr: Pointer): Boolean;
function IsValidModuleAddress(Addr: Pointer): Boolean;
property DynamicBuild: Boolean read FDynamicBuild;
property Items[Index: Integer]: TJclModuleInfo read GetItems;
property ModuleFromAddress[Addr: Pointer]: TJclModuleInfo read GetModuleFromAddress;
end;
function JclValidateModuleAddress(Addr: Pointer): Boolean;
// MAP file abstract parser
type
PJclMapAddress = ^TJclMapAddress;
TJclMapAddress = packed record
Segment: Word;
Offset: Integer;
end;
PJclMapString = PAnsiChar;
TJclAbstractMapParser = class(TObject)
private
FLinkerBug: Boolean;
FLinkerBugUnitName: PJclMapString;
FStream: TJclFileMappingStream;
function GetLinkerBugUnitName: string;
protected
FModule: HMODULE;
FLastUnitName: PJclMapString;
FLastUnitFileName: PJclMapString;
procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); virtual; abstract;
procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); virtual; abstract;
procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); virtual; abstract;
procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); virtual; abstract;
procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); virtual; abstract;
procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); virtual; abstract;
public
constructor Create(const MapFileName: TFileName; Module: HMODULE); overload; virtual;
constructor Create(const MapFileName: TFileName); overload;
destructor Destroy; override;
procedure Parse;
class function MapStringToStr(MapString: PJclMapString; IgnoreSpaces: Boolean = False): string;
class function MapStringToFileName(MapString: PJclMapString): string;
property LinkerBug: Boolean read FLinkerBug;
property LinkerBugUnitName: string read GetLinkerBugUnitName;
property Stream: TJclFileMappingStream read FStream;
end;
// MAP file parser
TJclMapClassTableEvent = procedure(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const SectionName, GroupName: string) of object;
TJclMapSegmentEvent = procedure(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const GroupName, UnitName: string) of object;
TJclMapPublicsEvent = procedure(Sender: TObject; const Address: TJclMapAddress; const Name: string) of object;
TJclMapLineNumberUnitEvent = procedure(Sender: TObject; const UnitName, UnitFileName: string) of object;
TJclMapLineNumbersEvent = procedure(Sender: TObject; LineNumber: Integer; const Address: TJclMapAddress) of object;
TJclMapParser = class(TJclAbstractMapParser)
private
FOnClassTable: TJclMapClassTableEvent;
FOnLineNumbers: TJclMapLineNumbersEvent;
FOnLineNumberUnit: TJclMapLineNumberUnitEvent;
FOnPublicsByValue: TJclMapPublicsEvent;
FOnPublicsByName: TJclMapPublicsEvent;
FOnSegmentItem: TJclMapSegmentEvent;
protected
procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); override;
procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); override;
procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); override;
procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); override;
procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); override;
procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); override;
public
property OnClassTable: TJclMapClassTableEvent read FOnClassTable write FOnClassTable;
property OnSegment: TJclMapSegmentEvent read FOnSegmentItem write FOnSegmentItem;
property OnPublicsByName: TJclMapPublicsEvent read FOnPublicsByName write FOnPublicsByName;
property OnPublicsByValue: TJclMapPublicsEvent read FOnPublicsByValue write FOnPublicsByValue;
property OnLineNumberUnit: TJclMapLineNumberUnitEvent read FOnLineNumberUnit write FOnLineNumberUnit;
property OnLineNumbers: TJclMapLineNumbersEvent read FOnLineNumbers write FOnLineNumbers;
end;
// MAP file scanner
PJclMapSegmentClass = ^TJclMapSegmentClass;
TJclMapSegmentClass = record
Segment: Word;
Addr: DWORD;
VA: DWORD;
Len: DWORD;
SectionName: PJclMapString;
GroupName: PJclMapString;
end;
PJclMapSegment = ^TJclMapSegment;
TJclMapSegment = record
Segment: Word;
StartVA: DWORD; // VA relative to (module base address + $10000)
EndVA: DWORD;
UnitName: PJclMapString;
end;
PJclMapProcName = ^TJclMapProcName;
TJclMapProcName = record
Segment: Word;
VA: DWORD; // VA relative to (module base address + $10000)
ProcName: PJclMapString;
end;
PJclMapLineNumber = ^TJclMapLineNumber;
TJclMapLineNumber = record
Segment: Word;
VA: DWORD; // VA relative to (module base address + $10000)
LineNumber: Integer;
end;
TJclMapScanner = class(TJclAbstractMapParser)
private
FSegmentClasses: array of TJclMapSegmentClass;
FLineNumbers: array of TJclMapLineNumber;
FProcNames: array of TJclMapProcName;
FSegments: array of TJclMapSegment;
FSourceNames: array of TJclMapProcName;
FLineNumbersCnt: Integer;
FLineNumberErrors: Integer;
FNewUnitFileName: PJclMapString;
FProcNamesCnt: Integer;
FSegmentCnt: Integer;
protected
function AddrToVA(const Addr: DWORD): DWORD;
procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); override;
procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); override;
procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); override;
procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); override;
procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); override;
procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); override;
procedure Scan;
public
constructor Create(const MapFileName: TFileName; Module: HMODULE); override;
// Addr are virtual addresses relative to (module base address + $10000)
function LineNumberFromAddr(Addr: DWORD): Integer; overload;
function LineNumberFromAddr(Addr: DWORD; var Offset: Integer): Integer; overload;
function ModuleNameFromAddr(Addr: DWORD): string;
function ModuleStartFromAddr(Addr: DWORD): DWORD;
function ProcNameFromAddr(Addr: DWORD): string; overload;
function ProcNameFromAddr(Addr: DWORD; var Offset: Integer): string; overload;
function SourceNameFromAddr(Addr: DWORD): string;
property LineNumberErrors: Integer read FLineNumberErrors;
end;
type
PJclDbgHeader = ^TJclDbgHeader;
TJclDbgHeader = packed record
Signature: DWORD;
Version: Byte;
Units: Integer;
SourceNames: Integer;
Symbols: Integer;
LineNumbers: Integer;
Words: Integer;
ModuleName: Integer;
CheckSum: Integer;
CheckSumValid: Boolean;
end;
TJclBinDebugGenerator = class(TJclMapScanner)
private
FDataStream: TMemoryStream;
FMapFileName: TFileName;
protected
procedure CreateData;
public
constructor Create(const MapFileName: TFileName; Module: HMODULE); override;
destructor Destroy; override;
function CalculateCheckSum: Boolean;
property DataStream: TMemoryStream read FDataStream;
end;
TJclBinDbgNameCache = record
Addr: DWORD;
FirstWord: Integer;
SecondWord: Integer;
end;
TJclBinDebugScanner = class(TObject)
private
FCacheData: Boolean;
FStream: TCustomMemoryStream;
FValidFormat: Boolean;
FLineNumbers: array of TJclMapLineNumber;
FProcNames: array of TJclBinDbgNameCache;
function GetModuleName: string;
protected
procedure CacheLineNumbers;
procedure CacheProcNames;
procedure CheckFormat;
function DataToStr(A: Integer): string;
function MakePtr(A: Integer): Pointer;
function ReadValue(var P: Pointer; var Value: Integer): Boolean;
public
constructor Create(AStream: TCustomMemoryStream; CacheData: Boolean);
function IsModuleNameValid(const Name: TFileName): Boolean;
function LineNumberFromAddr(Addr: DWORD): Integer; overload;
function LineNumberFromAddr(Addr: DWORD; var Offset: Integer): Integer; overload;
function ProcNameFromAddr(Addr: DWORD): string; overload;
function ProcNameFromAddr(Addr: DWORD; var Offset: Integer): string; overload;
function ModuleNameFromAddr(Addr: DWORD): string;
function ModuleStartFromAddr(Addr: DWORD): DWORD;
function SourceNameFromAddr(Addr: DWORD): string;
property ModuleName: string read GetModuleName;
property ValidFormat: Boolean read FValidFormat;
end;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName): Boolean; overload;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName; var LinkerBugUnit: string;
var LineNumberErrors: Integer): Boolean; overload;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName; var LinkerBugUnit: string;
var LineNumberErrors, MapFileSize, JdbgFileSize: Integer): Boolean; overload;
// do not change this function, it is used by the JVCL installer using dynamic
// linking (to avoid dependencies in the installer), the signature and name are
// sensible
// AnsiString and String types cannot be used because they are managed in
// memory, the memory manager of the JVCL installer is different of the memory
// manager used by the JCL package; only pointers and direct values are acceptable
function InsertDebugDataIntoExecutableFile(ExecutableFileName, MapFileName: PChar;
var MapFileSize, JclDebugDataSize: Integer): Boolean; overload;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName,
MapFileName: TFileName; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize: Integer): Boolean; overload;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName,
MapFileName: TFileName; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean; overload;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
BinDebug: TJclBinDebugGenerator; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize: Integer): Boolean; overload;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
BinDebug: TJclBinDebugGenerator; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean; overload;
// Source Locations
type
TJclDebugInfoSource = class;
PJclLocationInfo = ^TJclLocationInfo;
TJclLocationInfo = record
Address: Pointer; // Error address
UnitName: string; // Name of Delphi unit
ProcedureName: string; // Procedure name
OffsetFromProcName: Integer; // Offset from Address to ProcedureName symbol location
LineNumber: Integer; // Line number
OffsetFromLineNumber: Integer; // Offset from Address to LineNumber symbol location
SourceName: string; // Module file name
DebugInfo: TJclDebugInfoSource; // Location object
BinaryFileName: string; // Name of the binary file containing the symbol
end;
TJclDebugInfoSource = class(TObject)
private
FModule: HMODULE;
function GetFileName: TFileName;
protected
function VAFromAddr(const Addr: Pointer): DWORD; virtual;
public
constructor Create(AModule: HMODULE); virtual;
function InitializeSource: Boolean; virtual; abstract;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; virtual; abstract;
property Module: HMODULE read FModule;
property FileName: TFileName read GetFileName;
end;
TJclDebugInfoSourceClass = class of TJclDebugInfoSource;
TJclDebugInfoList = class(TObjectList)
private
function GetItemFromModule(const Module: HMODULE): TJclDebugInfoSource;
function GetItems(Index: Integer): TJclDebugInfoSource;
protected
function CreateDebugInfo(const Module: HMODULE): TJclDebugInfoSource;
public
class procedure RegisterDebugInfoSource(
const InfoSourceClass: TJclDebugInfoSourceClass);
class procedure UnRegisterDebugInfoSource(
const InfoSourceClass: TJclDebugInfoSourceClass);
class procedure RegisterDebugInfoSourceFirst(
const InfoSourceClass: TJclDebugInfoSourceClass);
class procedure NeedInfoSourceClassList;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
property ItemFromModule[const Module: HMODULE]: TJclDebugInfoSource read GetItemFromModule;
property Items[Index: Integer]: TJclDebugInfoSource read GetItems;
end;
// Various source location implementations
TJclDebugInfoMap = class(TJclDebugInfoSource)
private
FScanner: TJclMapScanner;
public
destructor Destroy; override;
function InitializeSource: Boolean; override;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; override;
end;
TJclDebugInfoBinary = class(TJclDebugInfoSource)
private
FScanner: TJclBinDebugScanner;
FStream: TCustomMemoryStream;
public
destructor Destroy; override;
function InitializeSource: Boolean; override;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; override;
end;
TJclDebugInfoExports = class(TJclDebugInfoSource)
private
FBorImage: TJclPeBorImage;
function IsAddressInThisExportedFunction(Addr: PByteArray; FunctionStartAddr: Cardinal): Boolean;
public
destructor Destroy; override;
function InitializeSource: Boolean; override;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; override;
end;
TJclDebugInfoTD32 = class(TJclDebugInfoSource)
private
FImage: TJclPeBorTD32Image;
public
destructor Destroy; override;
function InitializeSource: Boolean; override;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; override;
end;
TJclDebugInfoSymbols = class(TJclDebugInfoSource)
public
class function LoadDebugFunctions: Boolean;
class function UnloadDebugFunctions: Boolean;
class function InitializeDebugSymbols: Boolean;
class function CleanupDebugSymbols: Boolean;
function InitializeSource: Boolean; override;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; override;
end;
// Source location functions
function Caller(Level: Integer = 0; FastStackWalk: Boolean = False): Pointer;
function GetLocationInfo(const Addr: Pointer): TJclLocationInfo; overload;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean; overload;
function GetLocationInfoStr(const Addr: Pointer; IncludeModuleName: Boolean = False;
IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
IncludeVAdress: Boolean = False): string;
function DebugInfoAvailable(const Module: HMODULE): Boolean;
procedure ClearLocationData;
function FileByLevel(const Level: Integer = 0): string;
function ModuleByLevel(const Level: Integer = 0): string;
function ProcByLevel(const Level: Integer = 0): string;
function LineByLevel(const Level: Integer = 0): Integer;
function MapByLevel(const Level: Integer; var File_, Module_, Proc_: string; var Line_: Integer): Boolean;
function FileOfAddr(const Addr: Pointer): string;
function ModuleOfAddr(const Addr: Pointer): string;
function ProcOfAddr(const Addr: Pointer): string;
function LineOfAddr(const Addr: Pointer): Integer;
function MapOfAddr(const Addr: Pointer; var File_, Module_, Proc_: string; var Line_: Integer): Boolean;
function ExtractClassName(const ProcedureName: string): string;
function ExtractMethodName(const ProcedureName: string): string;
// Original function names, deprecated will be removed in V2.0; do not use!
function __FILE__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __MODULE__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __PROC__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __LINE__(const Level: Integer = 0): Integer; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __MAP__(const Level: Integer; var _File, _Module, _Proc: string; var _Line: Integer): Boolean; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __FILE_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __MODULE_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __PROC_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __LINE_OF_ADDR__(const Addr: Pointer): Integer; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
function __MAP_OF_ADDR__(const Addr: Pointer; var _File, _Module, _Proc: string;
var _Line: Integer): Boolean; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
// Stack info routines base list
type
TJclStackBaseList = class(TObjectList)
private
FThreadID: DWORD;
FTimeStamp: TDateTime;
protected
FOnDestroy: TNotifyEvent;
public
constructor Create;
destructor Destroy; override;
property ThreadID: DWORD read FThreadID;
property TimeStamp: TDateTime read FTimeStamp;
end;
// Stack info routines
type
PDWORDArray = ^TDWORDArray;
TDWORDArray = array [0..(MaxInt - $F) div SizeOf(DWORD)] of DWORD;
PStackFrame = ^TStackFrame;
TStackFrame = record
CallersEBP: DWORD;
CallerAdr: DWORD;
end;
PStackInfo = ^TStackInfo;
TStackInfo = record
CallerAdr: DWORD;
Level: DWORD;
CallersEBP: DWORD;
DumpSize: DWORD;
ParamSize: DWORD;
ParamPtr: PDWORDArray;
case Integer of
0:
(StackFrame: PStackFrame);
1:
(DumpPtr: PJclByteArray);
end;
TJclStackInfoItem = class(TObject)
private
FStackInfo: TStackInfo;
function GetCallerAdr: Pointer;
function GetLogicalAddress: DWORD;
public
property CallerAdr: Pointer read GetCallerAdr;
property LogicalAddress: DWORD read GetLogicalAddress;
property StackInfo: TStackInfo read FStackInfo;
end;
TJclStackInfoList = class(TJclStackBaseList)
private
FIgnoreLevels: DWORD;
TopOfStack: Cardinal;
BaseOfStack: Cardinal;
FStackData: PPointer;
FFrameEBP: Pointer;
FModuleInfoList: TJclModuleInfoList;
FCorrectOnAccess: Boolean;
FSkipFirstItem: Boolean;
FDelayedTrace: Boolean;
FInStackTracing: Boolean;
FRaw: Boolean;
FStackOffset: Cardinal;
function GetItems(Index: Integer): TJclStackInfoItem;
function NextStackFrame(var StackFrame: PStackFrame; var StackInfo: TStackInfo): Boolean;
procedure StoreToList(const StackInfo: TStackInfo);
procedure TraceStackFrames;
procedure TraceStackRaw;
procedure DelayStoreStack;
function ValidCallSite(CodeAddr: DWORD; var CallInstructionSize: Cardinal): Boolean;
function ValidStackAddr(StackAddr: DWORD): Boolean;
function GetCount: Integer;
procedure CorrectOnAccess(ASkipFirstItem: Boolean);
public
constructor Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer); overload;
constructor Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean); overload;
constructor Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack: Pointer); overload;
constructor Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack, ATopOfStack: Pointer); overload;
destructor Destroy; override;
procedure ForceStackTracing;
procedure AddToStrings(Strings: TStrings; IncludeModuleName: Boolean = False;
IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
IncludeVAdress: Boolean = False);
property DelayedTrace: Boolean read FDelayedTrace;
property Items[Index: Integer]: TJclStackInfoItem read GetItems; default;
property IgnoreLevels: DWORD read FIgnoreLevels;
property Count: Integer read GetCount;
property Raw: Boolean read FRaw;
end;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer): TJclStackInfoList; overload;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean): TJclStackInfoList; overload;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean; BaseOfStack: Pointer): TJclStackInfoList; overload;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean; BaseOfStack, TopOfStack: Pointer): TJclStackInfoList; overload;
function JclCreateThreadStackTrace(Raw: Boolean; const ThreadHandle: THandle): TJclStackInfoList;
function JclCreateThreadStackTraceFromID(Raw: Boolean; ThreadID: DWORD): TJclStackInfoList;
function JclLastExceptStackList: TJclStackInfoList;
function JclLastExceptStackListToStrings(Strings: TStrings; IncludeModuleName: Boolean = False;
IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
IncludeVAdress: Boolean = False): Boolean;
function JclGetExceptStackList(ThreadID: DWORD): TJclStackInfoList;
function JclGetExceptStackListToStrings(ThreadID: DWORD; Strings: TStrings;
IncludeModuleName: Boolean = False; IncludeAddressOffset: Boolean = False;
IncludeStartProcLineOffset: Boolean = False; IncludeVAdress: Boolean = False): Boolean;
// Exception frame info routines
type
PJmpInstruction = ^TJmpInstruction;
TJmpInstruction = packed record // from System.pas
OpCode: Byte;
Distance: Longint;
end;
TExcDescEntry = record // from System.pas
VTable: Pointer;
Handler: Pointer;
end;
PExcDesc = ^TExcDesc;
TExcDesc = packed record // from System.pas
JMP: TJmpInstruction;
case Integer of
0:
(Instructions: array [0..0] of Byte);
1:
(Cnt: Integer;
ExcTab: array [0..0] of TExcDescEntry);
end;
PExcFrame = ^TExcFrame;
TExcFrame = record // from System.pas
Next: PExcFrame;
Desc: PExcDesc;
HEBP: Pointer;
case Integer of
0:
();
1:
(ConstructedObject: Pointer);
2:
(SelfOfMethod: Pointer);
end;
PJmpTable = ^TJmpTable;
TJmpTable = packed record
OPCode: Word; // FF 25 = JMP DWORD PTR [$xxxxxxxx], encoded as $25FF
Ptr: Pointer;
end;
TExceptFrameKind =
(efkUnknown, efkFinally, efkAnyException, efkOnException, efkAutoException);
TJclExceptFrame = class(TObject)
private
FExcFrame: PExcFrame;
FFrameKind: TExceptFrameKind;
protected
procedure DoDetermineFrameKind;
public
constructor Create(AExcFrame: PExcFrame);
function Handles(ExceptObj: TObject): Boolean;
function HandlerInfo(ExceptObj: TObject; var HandlerAt: Pointer): Boolean;
function CodeLocation: Pointer;
property ExcFrame: PExcFrame read FExcFrame;
property FrameKind: TExceptFrameKind read FFrameKind;
end;
TJclExceptFrameList = class(TJclStackBaseList)
private
FIgnoreLevels: Integer;
function GetItems(Index: Integer): TJclExceptFrame;
protected
function AddFrame(AFrame: PExcFrame): TJclExceptFrame;
public
constructor Create(AIgnoreLevels: Integer);
procedure TraceExceptionFrames;
property Items[Index: Integer]: TJclExceptFrame read GetItems;
property IgnoreLevels: Integer read FIgnoreLevels write FIgnoreLevels;
end;
function JclCreateExceptFrameList(AIgnoreLevels: Integer): TJclExceptFrameList;
function JclLastExceptFrameList: TJclExceptFrameList;
function JclGetExceptFrameList(ThreadID: DWORD): TJclExceptFrameList;
function JclStartExceptionTracking: Boolean;
function JclStopExceptionTracking: Boolean;
function JclExceptionTrackingActive: Boolean;
function JclTrackExceptionsFromLibraries: Boolean;
// Thread exception tracking support
type
TJclDebugThread = class(TThread)
private
FSyncException: TObject;
FThreadName: string;
procedure DoHandleException;
function GetThreadInfo: string;
protected
procedure DoNotify;
procedure DoSyncHandleException; dynamic;
procedure HandleException(Sender: TObject = nil);
public
constructor Create(Suspended: Boolean; const AThreadName: string = '');
destructor Destroy; override;
property SyncException: TObject read FSyncException;
property ThreadInfo: string read GetThreadInfo;
property ThreadName: string read FThreadName;
end;
TJclDebugThreadNotifyEvent = procedure(Thread: TJclDebugThread) of object;
TJclThreadIDNotifyEvent = procedure(ThreadID: DWORD) of object;
TJclDebugThreadList = class(TObject)
private
FList: TStringList;
FLock: TJclCriticalSection;
FReadLock: TJclCriticalSection;
FRegSyncThreadID: DWORD;
FUnregSyncThreadID: DWORD;
FOnSyncException: TJclDebugThreadNotifyEvent;
FOnThreadRegistered: TJclThreadIDNotifyEvent;
FOnThreadUnregistered: TJclThreadIDNotifyEvent;
function GetThreadClassNames(ThreadID: DWORD): string;
function GetThreadInfos(ThreadID: DWORD): string;
function GetThreadNames(ThreadID: DWORD): string;
procedure DoSyncThreadRegistered;
procedure DoSyncThreadUnregistered;
function GetThreadHandle(Index: Integer): THandle;
function GetThreadID(Index: Integer): DWORD;
function GetThreadIDCount: Integer;
function GetThreadValues(ThreadID: DWORD; Index: Integer): string;
function IndexOfThreadID(ThreadID: DWORD): Integer;
protected
procedure DoSyncException(Thread: TJclDebugThread);
procedure DoThreadRegistered(Thread: TThread);
procedure DoThreadUnregistered(Thread: TThread);
procedure InternalRegisterThread(Thread: TThread; const ThreadName: string);
procedure InternalUnregisterThread(Thread: TThread);
public
constructor Create;
destructor Destroy; override;
procedure RegisterThread(Thread: TThread; const ThreadName: string);
procedure UnregisterThread(Thread: TThread);
property Lock: TJclCriticalSection read FLock;
//property ThreadClassNames[ThreadID: DWORD]: string index 1 read GetThreadValues;
property ThreadClassNames[ThreadID: DWORD]: string read GetThreadClassNames;
property ThreadHandles[Index: Integer]: DWORD read GetThreadHandle;
property ThreadIDs[Index: Integer]: DWORD read GetThreadID;
property ThreadIDCount: Integer read GetThreadIDCount;
//property ThreadInfos[ThreadID: DWORD]: string index 2 read GetThreadValues;
property ThreadInfos[ThreadID: DWORD]: string read GetThreadInfos;
//property ThreadNames[ThreadID: DWORD]: string index 0 read GetThreadValues;
property ThreadNames[ThreadID: DWORD]: string read GetThreadNames;
property OnSyncException: TJclDebugThreadNotifyEvent read FOnSyncException write FOnSyncException;
property OnThreadRegistered: TJclThreadIDNotifyEvent read FOnThreadRegistered write FOnThreadRegistered;
property OnThreadUnregistered: TJclThreadIDNotifyEvent read FOnThreadUnregistered write FOnThreadUnregistered;
end;
function JclDebugThreadList: TJclDebugThreadList;
// Miscellanuous
{$IFDEF MSWINDOWS}
function EnableCrashOnCtrlScroll(const Enable: Boolean): Boolean;
function IsDebuggerAttached: Boolean;
function IsHandleValid(Handle: THandle): Boolean;
{$ENDIF MSWINDOWS}
{$IFDEF SUPPORTS_EXTSYM}
{$EXTERNALSYM __FILE__}
{$EXTERNALSYM __LINE__}
{$ENDIF SUPPORTS_EXTSYM}
const
EnvironmentVarNtSymbolPath = '_NT_SYMBOL_PATH'; // do not localize
EnvironmentVarAlternateNtSymbolPath = '_NT_ALTERNATE_SYMBOL_PATH'; // do not localize
MaxStackTraceItems = 4096;
// JCL binary debug data generator and scanner
const
JclDbgDataSignature = $4742444A; // JDBG
JclDbgDataResName = 'JCLDEBUG'; // do not localize
JclDbgHeaderVersion = 1; // JCL 1.11 and 1.20
JclDbgFileExtension = '.jdbg'; // do not localize
JclMapFileExtension = '.map'; // do not localize
DrcFileExtension = '.drc'; // do not localize
// Global exceptional stack tracker enable routines and variables
type
TJclStackTrackingOption =
(stStack, stExceptFrame, stRawMode, stAllModules, stStaticModuleList,
stDelayedTrace, stTraceAllExceptions, stMainThreadOnly);
TJclStackTrackingOptions = set of TJclStackTrackingOption;
{$IFDEF KEEP_DEPRECATED}
const
// replaced by RemoveIgnoredException(EAbort)
stTraceEAbort = stTraceAllExceptions;
{$ENDIF KEEP_DEPRECATED}
var
JclStackTrackingOptions: TJclStackTrackingOptions = [stStack];
{ JclDebugInfoSymbolPaths specifies a list of paths, separated by ';', in
which the DebugInfoSymbol scanner should look for symbol information. }
JclDebugInfoSymbolPaths: string = '';
// functions to add/remove exception classes to be ignored if StTraceAllExceptions is not set
procedure AddIgnoredException(const ExceptionClass: TClass);
procedure RemoveIgnoredException(const ExceptionClass: TClass);
function IsIgnoredException(const ExceptionClass: TClass): Boolean;
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL: https://jcl.svn.sourceforge.net:443/svnroot/jcl/tags/JCL-1.100-Build2646/jcl/source/windows/JclDebug.pas $';
Revision: '$Revision: 2013 $';
Date: '$Date: 2007-05-22 21:58:20 +0200 (mar., 22 mai 2007) $';
LogPath: 'JCL\source\windows'
);
{$ENDIF UNITVERSIONING}
implementation
uses
ImageHlp,
{$IFDEF MSWINDOWS}
JclRegistry,
{$ENDIF MSWINDOWS}
JclHookExcept, JclLogic, JclStrings, JclSysInfo, JclSysUtils, JclWin32,
JclResources;
//=== Helper assembler routines ==============================================
const
ModuleCodeOffset = $1000;
{$STACKFRAMES OFF}
function GetEBP: Pointer;
asm
MOV EAX, EBP
end;
function GetESP: Pointer;
asm
MOV EAX, ESP
end;
function GetFS: Pointer;
asm
XOR EAX, EAX
MOV EAX, FS:[EAX]
end;
// Reference: Matt Pietrek, MSJ, Under the hood, on TIBs:
// http://www.microsoft.com/MSJ/archive/S2CE.HTM
function GetStackTop: DWORD;
asm
// TODO: 64 bit version
MOV EAX, FS:[0].NT_TIB32.StackBase
end;
{$IFDEF STACKFRAMES_ON}
{$STACKFRAMES ON}
{$ENDIF STACKFRAMES_ON}
//=== Diagnostics ===========================================================
procedure AssertKindOf(const ClassName: string; const Obj: TObject);
var
C: TClass;
begin
if not Obj.ClassNameIs(ClassName) then
begin
C := Obj.ClassParent;
while (C <> nil) and (not C.ClassNameIs(ClassName)) do
C := C.ClassParent;
Assert(C <> nil);
end;
end;
procedure AssertKindOf(const ClassType: TClass; const Obj: TObject);
begin
Assert(Obj.InheritsFrom(ClassType));
end;
{$IFDEF KEEP_DEPRECATED}
procedure Trace(const Msg: string);
begin
TraceMsg(Msg);
end;
{$ENDIF KEEP_DEPRECATED}
procedure TraceMsg(const Msg: string);
begin
OutputDebugString(PChar(StrDoubleQuote(Msg)));
end;
procedure TraceFmt(const Fmt: string; const Args: array of const);
begin
OutputDebugString(PChar(Format(StrDoubleQuote(Fmt), Args)));
end;
procedure TraceLoc(const Msg: string);
begin
OutputDebugString(PChar(Format('%s:%u (%s) "%s"',
[FileByLevel(1), LineByLevel(1), ProcByLevel(1), Msg])));
end;
procedure TraceLocFmt(const Fmt: string; const Args: array of const);
var
S: string;
begin
S := Format('%s:%u (%s) ', [FileByLevel(1), LineByLevel(1), ProcByLevel(1)]) +
Format(StrDoubleQuote(Fmt), Args);
OutputDebugString(PChar(S));
end;
//=== { TJclModuleInfoList } =================================================
constructor TJclModuleInfoList.Create(ADynamicBuild, ASystemModulesOnly: Boolean);
begin
inherited Create(True);
FDynamicBuild := ADynamicBuild;
FSystemModulesOnly := ASystemModulesOnly;
if not FDynamicBuild then
BuildModulesList;
end;
function TJclModuleInfoList.AddModule(Module: HMODULE; SystemModule: Boolean): Boolean;
begin
Result := not IsValidModuleAddress(Pointer(Module)) and
(CreateItemForAddress(Pointer(Module), SystemModule) <> nil);
end;
{function SortByStartAddress(Item1, Item2: Pointer): Integer;
begin
Result := Integer(TJclModuleInfo(Item2).StartAddr) - Integer(TJclModuleInfo(Item1).StartAddr);
end;}
procedure TJclModuleInfoList.BuildModulesList;
var
List: TStringList;
I: Integer;
CurModule: PLibModule;
begin
if FSystemModulesOnly then
begin
CurModule := LibModuleList;
while CurModule <> nil do
begin
CreateItemForAddress(Pointer(CurModule.Instance), True);
CurModule := CurModule.Next;
end;
end
else
begin
List := TStringList.Create;
try
LoadedModulesList(List, GetCurrentProcessId, True);
for I := 0 to List.Count - 1 do
CreateItemForAddress(List.Objects[I], False);
finally
List.Free;
end;
end;
//Sort(SortByStartAddress);
end;
function TJclModuleInfoList.CreateItemForAddress(Addr: Pointer; SystemModule: Boolean): TJclModuleInfo;
var
Module: HMODULE;
ModuleSize: DWORD;
begin
Result := nil;
Module := ModuleFromAddr(Addr);
if Module > 0 then
begin
ModuleSize := PeMapImgSize(Pointer(Module));
if ModuleSize <> 0 then
begin
Result := TJclModuleInfo.Create;
Result.FStartAddr := Pointer(Module);
Result.FSize := ModuleSize;
Result.FEndAddr := Pointer(Module + ModuleSize - 1);
if SystemModule then
Result.FSystemModule := True
else
Result.FSystemModule := IsSystemModule(Module);
end;
end;
if Result <> nil then
Add(Result);
end;
function TJclModuleInfoList.GetItems(Index: Integer): TJclModuleInfo;
begin
Result := TJclModuleInfo(Get(Index));
end;
function TJclModuleInfoList.GetModuleFromAddress(Addr: Pointer): TJclModuleInfo;
var
I: Integer;
Item: TJclModuleInfo;
begin
Result := nil;
for I := 0 to Count - 1 do
begin
Item := Items[I];
if (Cardinal(Item.StartAddr) <= Cardinal(Addr)) and (Cardinal(Item.EndAddr) > Cardinal(Addr)) then
begin
Result := Item;
Break;
end;
end;
if DynamicBuild and (Result = nil) then
Result := CreateItemForAddress(Addr, False);
end;
function TJclModuleInfoList.IsSystemModuleAddress(Addr: Pointer): Boolean;
var
Item: TJclModuleInfo;
begin
Item := ModuleFromAddress[Addr];
Result := (Item <> nil) and Item.SystemModule;
end;
function TJclModuleInfoList.IsValidModuleAddress(Addr: Pointer): Boolean;
begin
Result := ModuleFromAddress[Addr] <> nil;
end;
//=== { TJclAbstractMapParser } ==============================================
constructor TJclAbstractMapParser.Create(const MapFileName: TFileName; Module: HMODULE);
begin
inherited Create;
FModule := Module;
if FileExists(MapFileName) then
FStream := TJclFileMappingStream.Create(MapFileName, fmOpenRead or fmShareDenyWrite);
end;
constructor TJclAbstractMapParser.Create(const MapFileName: TFileName);
begin
Create(MapFileName, 0);
end;
destructor TJclAbstractMapParser.Destroy;
begin
FreeAndNil(FStream);
inherited Destroy;
end;
function TJclAbstractMapParser.GetLinkerBugUnitName: string;
begin
Result := MapStringToStr(FLinkerBugUnitName);
end;
class function TJclAbstractMapParser.MapStringToFileName(MapString: PJclMapString): string;
var
PStart, PEnd, PExtension: PChar;
begin
if MapString = nil then
begin
Result := '';
Exit;
end;
PEnd := MapString;
while not (PEnd^ in [AnsiCarriageReturn, '=']) do
Inc(PEnd);
if (PEnd^ = '=') then
begin
while not (PEnd^ = AnsiSpace) do
Dec(PEnd);
while ((PEnd-1)^ = AnsiSpace) do
Dec(PEnd);
end;
PExtension := PEnd;
while (not (PExtension^ in ['.', '|'])) and (PExtension >= MapString) do
Dec(PExtension);
if (PExtension^ = '.') then
PEnd := PExtension;
PExtension := PEnd;
while (not (PExtension^ in ['|','\'])) and (PExtension >= MapString) do
Dec(PExtension);
if (PExtension^ in ['|','\']) then
PStart := PExtension + 1
else PStart := MapString;
SetString(Result, PStart, PEnd - PStart);
end;
class function TJclAbstractMapParser.MapStringToStr(MapString: PJclMapString;
IgnoreSpaces: Boolean): string;
var
P: PChar;
begin
if MapString = nil then
begin
Result := '';
Exit;
end;
if MapString^ = '(' then
begin
Inc(MapString);
P := MapString;
while not (P^ in [AnsiCarriageReturn, ')']) do
Inc(P);
end
else
begin
P := MapString;
if IgnoreSpaces then
while not (P^ in [AnsiCarriageReturn, '(']) do
Inc(P)
else
while not (P^ in [AnsiSpace, AnsiCarriageReturn, '(']) do
Inc(P);
end;
SetString(Result, MapString, P - MapString);
end;
procedure TJclAbstractMapParser.Parse;
const
TableHeader : array [0..3] of string = ('Start', 'Length', 'Name', 'Class');
SegmentsHeader : array [0..3] of string = ('Detailed', 'map', 'of', 'segments');
PublicsByNameHeader : array [0..3] of string = ('Address', 'Publics', 'by', 'Name');
PublicsByValueHeader : array [0..3] of string = ('Address', 'Publics', 'by', 'Value');
LineNumbersPrefix = 'Line numbers for';
ResourceFilesHeader : array [0..2] of string = ('Bound', 'resource', 'files');
var
CurrPos, EndPos: PChar;
{$IFNDEF COMPILER9_UP}
PreviousA,
{$ENDIF COMPILER9_UP}
A: TJclMapAddress;
L: Integer;
P1, P2: PJclMapString;
procedure SkipWhiteSpace;
begin
while CurrPos^ in AnsiWhiteSpace do
Inc(CurrPos);
end;
procedure SkipEndLine;
begin
while CurrPos^ <> AnsiLineFeed do
Inc(CurrPos);
SkipWhiteSpace;
end;
function Eof: Boolean;
begin
Result := (CurrPos >= EndPos);
end;
function IsDecDigit: Boolean;
begin
Result := CurrPos^ in AnsiDecDigits;
end;
function ReadTextLine: string;
var
P: PChar;
begin
P := CurrPos;
while not (CurrPos^ in [AnsiCarriageReturn, AnsiNull]) do
Inc(CurrPos);
SetString(Result, P, CurrPos - P);
end;
function ReadDecValue: Integer;
begin
Result := 0;
while CurrPos^ in AnsiDecDigits do
begin
Result := Result * 10 + (Ord(CurrPos^) - Ord('0'));
Inc(CurrPos);
end;
end;
function ReadHexValue: Integer;
var
C: Char;
begin
Result := 0;
repeat
C := CurrPos^;
case C of
'0'..'9':
begin
Result := Result * 16;
Inc(Result, Ord(C) - Ord('0'));
end;
'A'..'F':
begin
Result := Result * 16;
Inc(Result, Ord(C) - Ord('A') + 10);
end;
'a'..'f':
begin
Result := Result * 16;
Inc(Result, Ord(C) - Ord('a') + 10);
end;
'H', 'h':
begin
Inc(CurrPos);
Break;
end;
else
Break;
end;
Inc(CurrPos);
until False;
end;
function ReadAddress: TJclMapAddress;
begin
Result.Segment := ReadHexValue;
if CurrPos^ = ':' then
begin
Inc(CurrPos);
Result.Offset := ReadHexValue;
end
else
Result.Offset := 0;
end;
function ReadString: PJclMapString;
begin
SkipWhiteSpace;
Result := CurrPos;
while not (CurrPos^ in AnsiWhiteSpace) do
Inc(CurrPos);
end;
procedure FindParam(Param: Char);
begin
while not ((CurrPos^ = Param) and ((CurrPos + 1)^ = '=')) do
Inc(CurrPos);
Inc(CurrPos, 2);
end;
function SyncToHeader(const Header: array of string): Boolean;
var
S: string;
TokenIndex, OldPosition, CurrentPosition: Integer;
begin
Result := False;
while not Eof do
begin
S := Trim(ReadTextLine);
TokenIndex := Low(Header);
CurrentPosition := 0;
OldPosition := 0;
while (TokenIndex <= High(Header)) do
begin
CurrentPosition := Pos(Header[TokenIndex],S);
if (CurrentPosition <= OldPosition) then
begin
CurrentPosition := 0;
Break;
end;
OldPosition := CurrentPosition;
Inc(TokenIndex);
end;
Result := CurrentPosition <> 0;
if Result then
Break;
SkipEndLine;
end;
if not Eof then
SkipWhiteSpace;
end;
function SyncToPrefix(const Prefix: string): Boolean;
var
I: Integer;
P: PChar;
S: string;
begin
if Eof then
begin
Result := False;
Exit;
end;
SkipWhiteSpace;
I := Length(Prefix);
P := CurrPos;
while not Eof and (not (P^ in [AnsiCarriageReturn, AnsiNull])) and (I > 0) do
begin
Inc(P);
Dec(I);
end;
SetString(S, CurrPos, Length(Prefix));
Result := (S = Prefix);
if Result then
CurrPos := P;
SkipWhiteSpace;
end;
begin
if FStream <> nil then
begin
FLinkerBug := False;
{$IFNDEF COMPILER9_UP}
PreviousA.Segment := 0;
PreviousA.Offset := 0;
{$ENDIF COMPILER9_UP}
CurrPos := FStream.Memory;
EndPos := CurrPos + FStream.Size;
if SyncToHeader(TableHeader) then
while IsDecDigit do
begin
A := ReadAddress;
SkipWhiteSpace;
L := ReadHexValue;
P1 := ReadString;
P2 := ReadString;
SkipEndLine;
ClassTableItem(A, L, P1, P2);
end;
if SyncToHeader(SegmentsHeader) then
while IsDecDigit do
begin
A := ReadAddress;
SkipWhiteSpace;
L := ReadHexValue;
FindParam('C');
P1 := ReadString;
FindParam('M');
P2 := ReadString;
SkipEndLine;
SegmentItem(A, L, P1, P2);
end;
if SyncToHeader(PublicsByNameHeader) then
while IsDecDigit do
begin
A := ReadAddress;
P1 := ReadString;
SkipEndLine; // compatibility with C++Builder MAP files
PublicsByNameItem(A, P1);
end;
if SyncToHeader(PublicsByValueHeader) then
while IsDecDigit do
begin
A := ReadAddress;
P1 := ReadString;
SkipEndLine; // compatibility with C++Builder MAP files
PublicsByValueItem(A, P1);
end;
while SyncToPrefix(LineNumbersPrefix) do
begin
FLastUnitName := CurrPos;
FLastUnitFileName := CurrPos;
while FLastUnitFileName^ <> '(' do
Inc(FLastUnitFileName);
SkipEndLine;
LineNumberUnitItem(FLastUnitName, FLastUnitFileName);
repeat
SkipWhiteSpace;
L := ReadDecValue;
SkipWhiteSpace;
A := ReadAddress;
SkipWhiteSpace;
LineNumbersItem(L, A);
{$IFNDEF COMPILER9_UP}
if (not FLinkerBug) and (A.Offset < PreviousA.Offset) then
begin
FLinkerBugUnitName := FLastUnitName;
FLinkerBug := True;
end;
PreviousA := A;
{$ENDIF COMPILER9_UP}
until not IsDecDigit;
end;
end;
end;
//=== { TJclMapParser 0 ======================================================
procedure TJclMapParser.ClassTableItem(const Address: TJclMapAddress;
Len: Integer; SectionName, GroupName: PJclMapString);
begin
if Assigned(FOnClassTable) then
FOnClassTable(Self, Address, Len, MapStringToStr(SectionName), MapStringToStr(GroupName));
end;
procedure TJclMapParser.LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress);
begin
if Assigned(FOnLineNumbers) then
FOnLineNumbers(Self, LineNumber, Address);
end;
procedure TJclMapParser.LineNumberUnitItem(UnitName, UnitFileName: PJclMapString);
begin
if Assigned(FOnLineNumberUnit) then
FOnLineNumberUnit(Self, MapStringToStr(UnitName), MapStringToStr(UnitFileName));
end;
procedure TJclMapParser.PublicsByNameItem(const Address: TJclMapAddress;
Name: PJclMapString);
begin
if Assigned(FOnPublicsByName) then
// MAP files generated by C++Builder have spaces in their identifier names
FOnPublicsByName(Self, Address, MapStringToStr(Name, True));
end;
procedure TJclMapParser.PublicsByValueItem(const Address: TJclMapAddress;
Name: PJclMapString);
begin
if Assigned(FOnPublicsByValue) then
// MAP files generated by C++Builder have spaces in their identifier names
FOnPublicsByValue(Self, Address, MapStringToStr(Name, True));
end;
procedure TJclMapParser.SegmentItem(const Address: TJclMapAddress;
Len: Integer; GroupName, UnitName: PJclMapString);
begin
if Assigned(FOnSegmentItem) then
FOnSegmentItem(Self, Address, Len, MapStringToStr(GroupName), MapStringToFileName(UnitName));
end;
//=== { TJclMapScanner } =====================================================
constructor TJclMapScanner.Create(const MapFileName: TFileName; Module: HMODULE);
begin
inherited Create(MapFileName, Module);
Scan;
end;
function TJclMapScanner.AddrToVA(const Addr: DWORD): DWORD;
begin
// MAP file format was changed in Delphi 2005
// before Delphi 2005: segments started at offset 0
// only one segment of code
// after Delphi 2005: segments started at code base address (module base address + $10000)
// 2 segments of code
if (Length(FSegmentClasses) > 0) and (FSegmentClasses[0].Addr > 0) then
// Delphi 2005 and earlier
// The first segment should be code starting at module base address + $10000
Result := Addr - FSegmentClasses[0].Addr
else
// before Delphi 2005
Result := Addr;
end;
procedure TJclMapScanner.ClassTableItem(const Address: TJclMapAddress; Len: Integer;
SectionName, GroupName: PJclMapString);
var
C: Integer;
SectionHeader: PImageSectionHeader;
begin
C := Length(FSegmentClasses);
SetLength(FSegmentClasses, C + 1);
FSegmentClasses[C].Segment := Address.Segment;
FSegmentClasses[C].Addr := Address.Offset;
FSegmentClasses[C].VA := AddrToVA(Address.Offset);
FSegmentClasses[C].Len := Len;
FSegmentClasses[C].SectionName := SectionName;
FSegmentClasses[C].GroupName := GroupName;
if FModule <> 0 then
begin
{ Fix the section addresses }
SectionHeader := PeMapImgFindSectionFromModule(Pointer(FModule), MapStringToStr(SectionName));
if SectionHeader = nil then
{ before Delphi 2005 the class names where used for the section names }
SectionHeader := PeMapImgFindSectionFromModule(Pointer(FModule), MapStringToStr(GroupName));
if SectionHeader <> nil then
begin
FSegmentClasses[C].Addr := Cardinal(FModule) + SectionHeader.VirtualAddress;
FSegmentClasses[C].VA := SectionHeader.VirtualAddress;
end;
end;
end;
function TJclMapScanner.LineNumberFromAddr(Addr: DWORD): Integer;
var
Dummy: Integer;
begin
Result := LineNumberFromAddr(Addr, Dummy);
end;
function Search_MapLineNumber(Item1, Item2: Pointer): Integer;
begin
Result := Integer(PJclMapLineNumber(Item1)^.VA) - PInteger(Item2)^;
end;
function TJclMapScanner.LineNumberFromAddr(Addr: DWORD; var Offset: Integer): Integer;
var
I: Integer;
ModuleStartAddr: DWORD;
begin
ModuleStartAddr := ModuleStartFromAddr(Addr);
Result := 0;
Offset := 0;
I := SearchDynArray(FLineNumbers, SizeOf(FLineNumbers[0]), Search_MapLineNumber, @Addr, True);
if (I <> -1) and (FLineNumbers[I].VA >= ModuleStartAddr) then
begin
Result := FLineNumbers[I].LineNumber;
Offset := Addr - FLineNumbers[I].VA;
end;
end;
procedure TJclMapScanner.LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress);
var
SegIndex, C: Integer;
VA: DWORD;
Added: Boolean;
begin
Added := False;
for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
if (FSegmentClasses[SegIndex].Segment = Address.Segment)
and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
begin
VA := AddrToVA(DWORD(Address.Offset) + FSegmentClasses[SegIndex].Addr);
{ Starting with Delphi 2005, "empty" units are listes with the last line and
the VA 0001:00000000. When we would accept 0 VAs here, System.pas functions
could be mapped to other units and line numbers. Discaring such items should
have no impact on the correct information, because there can't be a function
that starts at VA 0. }
if VA = 0 then
Continue;
if FLineNumbersCnt mod 256 = 0 then
SetLength(FLineNumbers, FLineNumbersCnt + 256);
FLineNumbers[FLineNumbersCnt].Segment := FSegmentClasses[SegIndex].Segment;
FLineNumbers[FLineNumbersCnt].VA := VA;
FLineNumbers[FLineNumbersCnt].LineNumber := LineNumber;
Inc(FLineNumbersCnt);
Added := True;
if FNewUnitFileName <> nil then
begin
C := Length(FSourceNames);
SetLength(FSourceNames, C + 1);
FSourceNames[C].Segment := FSegmentClasses[SegIndex].Segment;
FSourceNames[C].VA := VA;
FSourceNames[C].ProcName := FNewUnitFileName;
FNewUnitFileName := nil;
end;
Break;
end;
if not Added then
Inc(FLineNumberErrors);
end;
procedure TJclMapScanner.LineNumberUnitItem(UnitName, UnitFileName: PJclMapString);
begin
FNewUnitFileName := UnitFileName;
end;
function TJclMapScanner.ModuleNameFromAddr(Addr: DWORD): string;
var
I: Integer;
begin
Result := '';
for I := Length(FSegments) - 1 downto 0 do
if (FSegments[I].StartVA <= Addr) and (Addr < FSegments[I].EndVA) then
begin
Result := MapStringToStr(FSegments[I].UnitName);
Break;
end;
end;
function TJclMapScanner.ModuleStartFromAddr(Addr: DWORD): DWORD;
var
I: Integer;
begin
Result := DWORD(-1);
for I := Length(FSegments) - 1 downto 0 do
if (FSegments[I].StartVA <= Addr) and (Addr < FSegments[I].EndVA) then
begin
Result := FSegments[I].StartVA;
Break;
end;
end;
function TJclMapScanner.ProcNameFromAddr(Addr: DWORD): string;
var
Dummy: Integer;
begin
Result := ProcNameFromAddr(Addr, Dummy);
end;
function Search_MapProcName(Item1, Item2: Pointer): Integer;
begin
Result := Integer(PJclMapProcName(Item1)^.VA) - PInteger(Item2)^;
end;
function TJclMapScanner.ProcNameFromAddr(Addr: DWORD; var Offset: Integer): string;
var
I: Integer;
ModuleStartAddr: DWORD;
begin
ModuleStartAddr := ModuleStartFromAddr(Addr);
Result := '';
Offset := 0;
I := SearchDynArray(FProcNames, SizeOf(FProcNames[0]), Search_MapProcName, @Addr, True);
if (I <> -1) and (FProcNames[I].VA >= ModuleStartAddr) then
begin
Result := MapStringToStr(FProcNames[I].ProcName, True);
Offset := Addr - FProcNames[I].VA;
end;
end;
procedure TJclMapScanner.PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString);
begin
{ TODO : What to do? }
end;
procedure TJclMapScanner.PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString);
var
SegIndex: Integer;
begin
for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
if (FSegmentClasses[SegIndex].Segment = Address.Segment)
and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
begin
if FProcNamesCnt mod 256 = 0 then
SetLength(FProcNames, FProcNamesCnt + 256);
FProcNames[FProcNamesCnt].Segment := FSegmentClasses[SegIndex].Segment;
FProcNames[FProcNamesCnt].VA := AddrToVA(DWORD(Address.Offset) + FSegmentClasses[SegIndex].Addr);
FProcNames[FProcNamesCnt].ProcName := Name;
Inc(FProcNamesCnt);
Break;
end;
end;
function Sort_MapLineNumber(Item1, Item2: Pointer): Integer;
begin
Result := Integer(PJclMapLineNumber(Item1)^.VA) - Integer(PJclMapLineNumber(Item2)^.VA);
end;
function Sort_MapProcName(Item1, Item2: Pointer): Integer;
begin
Result := Integer(PJclMapProcName(Item1)^.VA) - Integer(PJclMapProcName(Item2)^.VA);
end;
function Sort_MapSegment(Item1, Item2: Pointer): Integer;
begin
Result := Integer(PJclMapSegment(Item1)^.StartVA) - Integer(PJclMapSegment(Item2)^.StartVA);
end;
procedure TJclMapScanner.Scan;
begin
FLineNumberErrors := 0;
FSegmentCnt := 0;
FProcNamesCnt := 0;
Parse;
SetLength(FLineNumbers, FLineNumbersCnt);
SetLength(FProcNames, FProcNamesCnt);
SetLength(FSegments, FSegmentCnt);
SortDynArray(FLineNumbers, SizeOf(FLineNumbers[0]), Sort_MapLineNumber);
SortDynArray(FProcNames, SizeOf(FProcNames[0]), Sort_MapProcName);
SortDynArray(FSegments, SizeOf(FSegments[0]), Sort_MapSegment);
SortDynArray(FSourceNames, SizeOf(FSourceNames[0]), Sort_MapProcName);
end;
procedure TJclMapScanner.SegmentItem(const Address: TJclMapAddress; Len: Integer;
GroupName, UnitName: PJclMapString);
var
SegIndex: Integer;
VA: DWORD;
begin
for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
if (FSegmentClasses[SegIndex].Segment = Address.Segment)
and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
begin
VA := AddrToVA(DWORD(Address.Offset) + FSegmentClasses[SegIndex].Addr);
if FSegmentCnt mod 16 = 0 then
SetLength(FSegments, FSegmentCnt + 16);
FSegments[FSegmentCnt].Segment := FSegmentClasses[SegIndex].Segment;
FSegments[FSegmentCnt].StartVA := VA;
FSegments[FSegmentCnt].EndVA := VA + DWORD(Len);
FSegments[FSegmentCnt].UnitName := UnitName;
Inc(FSegmentCnt);
Break;
end;
end;
function TJclMapScanner.SourceNameFromAddr(Addr: DWORD): string;
var
I: Integer;
ModuleStartVA: DWORD;
begin
ModuleStartVA := ModuleStartFromAddr(Addr);
Result := '';
I := SearchDynArray(FSourceNames, SizeOf(FSourceNames[0]), Search_MapProcName, @Addr, True);
if (I <> -1) and (FSourceNames[I].VA >= ModuleStartVA) then
Result := MapStringToStr(FSourceNames[I].ProcName);
end;
// JCL binary debug format string encoding/decoding routines
{ Strings are compressed to following 6bit format (A..D represents characters) and terminated with }
{ 6bit #0 char. First char = #1 indicates non compressed text, #2 indicates compressed text with }
{ leading '@' character }
{ }
{ 7 6 5 4 3 2 1 0 | }
{--------------------------------- }
{ B1 B0 A5 A4 A3 A2 A1 A0 | Data byte 0 }
{--------------------------------- }
{ C3 C2 C1 C0 B5 B4 B3 B2 | Data byte 1 }
{--------------------------------- }
{ D5 D4 D3 D2 D1 D0 C5 C4 | Data byte 2 }
{--------------------------------- }
function SimpleCryptString(const S: string): string;
var
I: Integer;
C: Byte;
P: PByte;
begin
SetLength(Result, Length(S));
P := PByte(Result);
for I := 1 to Length(S) do
begin
C := Ord(S[I]);
if C <> $AA then
C := C xor $AA;
P^ := C;
Inc(P);
end;
end;
function DecodeNameString(const S: PChar): string;
var
I, B: Integer;
C: Byte;
P: PByte;
Buffer: array [0..255] of Char;
begin
Result := '';
B := 0;
P := PByte(S);
case P^ of
1:
begin
Inc(P);
Result := SimpleCryptString(PChar(P));
Exit;
end;
2:
begin
Inc(P);
Buffer[B] := '@';
Inc(B);
end;
end;
I := 0;
C := 0;
repeat
case I and $03 of
0:
C := P^ and $3F;
1:
begin
C := (P^ shr 6) and $03;
Inc(P);
Inc(C, (P^ and $0F) shl 2);
end;
2:
begin
C := (P^ shr 4) and $0F;
Inc(P);
Inc(C, (P^ and $03) shl 4);
end;
3:
begin
C := (P^ shr 2) and $3F;
Inc(P);
end;
end;
case C of
$00:
Break;
$01..$0A:
Inc(C, Ord('0') - $01);
$0B..$24:
Inc(C, Ord('A') - $0B);
$25..$3E:
Inc(C, Ord('a') - $25);
$3F:
C := Ord('_');
end;
Buffer[B] := Chr(C);
Inc(B);
Inc(I);
until B >= SizeOf(Buffer) - 1;
Buffer[B] := AnsiNull;
Result := Buffer;
end;
function EncodeNameString(const S: string): string;
var
I, StartIndex: Integer;
C: Byte;
P: PByte;
begin
if (Length(S) > 1) and (S[1] = '@') then
StartIndex := 1
else
StartIndex := 0;
for I := StartIndex + 1 to Length(S) do
if not (S[I] in AnsiValidIdentifierLetters) then
begin
Result := #1 + SimpleCryptString(S) + #0;
Exit;
end;
SetLength(Result, Length(S) + StartIndex);
P := Pointer(Result);
if StartIndex = 1 then
P^ := 2 // store '@' leading char information
else
Dec(P);
for I := 0 to Length(S) - StartIndex do // including null char
begin
C := Byte(S[I + 1 + StartIndex]);
case Char(C) of
#0:
C := 0;
'0'..'9':
Dec(C, Ord('0') - $01);
'A'..'Z':
Dec(C, Ord('A') - $0B);
'a'..'z':
Dec(C, Ord('a') - $25);
'_':
C := $3F;
else
C := $3F;
end;
case I and $03 of
0:
begin
Inc(P);
P^ := C;
end;
1:
begin
P^ := P^ or (C and $03) shl 6;
Inc(P);
P^ := (C shr 2) and $0F;
end;
2:
begin
P^ := P^ or (C shl 4);
Inc(P);
P^ := (C shr 4) and $03;
end;
3:
P^ := P^ or (C shl 2);
end;
end;
SetLength(Result, DWORD(P) - DWORD(Pointer(Result)) + 1);
end;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName): Boolean;
var
Dummy1: string;
Dummy2, Dummy3, Dummy4: Integer;
begin
Result := ConvertMapFileToJdbgFile(MapFileName, Dummy1, Dummy2, Dummy3, Dummy4);
end;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName; var LinkerBugUnit: string;
var LineNumberErrors: Integer): Boolean;
var
Dummy1, Dummy2: Integer;
begin
Result := ConvertMapFileToJdbgFile(MapFileName, LinkerBugUnit, LineNumberErrors,
Dummy1, Dummy2);
end;
function ConvertMapFileToJdbgFile(const MapFileName: TFileName; var LinkerBugUnit: string;
var LineNumberErrors, MapFileSize, JdbgFileSize: Integer): Boolean;
var
JDbgFileName: TFileName;
Generator: TJclBinDebugGenerator;
begin
JDbgFileName := ChangeFileExt(MapFileName, JclDbgFileExtension);
Generator := TJclBinDebugGenerator.Create(MapFileName, 0);
try
MapFileSize := Generator.Stream.Size;
JdbgFileSize := Generator.DataStream.Size;
Result := (Generator.DataStream.Size > 0) and Generator.CalculateCheckSum;
if Result then
Generator.DataStream.SaveToFile(JDbgFileName);
LinkerBugUnit := Generator.LinkerBugUnitName;
LineNumberErrors := Generator.LineNumberErrors;
finally
Generator.Free;
end;
end;
// do not change this function, it is used by the JVCL installer using dynamic
// linking (to avoid dependencies in the installer), the signature and name are
// sensible
function InsertDebugDataIntoExecutableFile(ExecutableFileName, MapFileName: PChar;
var MapFileSize, JclDebugDataSize: Integer): Boolean;
var
LinkerBugUnit: string;
begin
LinkerBugUnit := '';
Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, MapFileName,
LinkerBugUnit, MapFileSize, JclDebugDataSize);
end;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName, MapFileName: TFileName;
var LinkerBugUnit: string; var MapFileSize, JclDebugDataSize: Integer): Boolean;
var
Dummy: Integer;
begin
Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, MapFileName, LinkerBugUnit,
MapFileSize, JclDebugDataSize, Dummy);
end;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName, MapFileName: TFileName;
var LinkerBugUnit: string; var MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean;
var
BinDebug: TJclBinDebugGenerator;
begin
BinDebug := TJclBinDebugGenerator.Create(MapFileName, 0);
try
Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, BinDebug,
LinkerBugUnit, MapFileSize, JclDebugDataSize, LineNumberErrors);
finally
BinDebug.Free;
end;
end;
function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
BinDebug: TJclBinDebugGenerator; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize: Integer): Boolean;
var
Dummy: Integer;
begin
Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, BinDebug, LinkerBugUnit,
MapFileSize, JclDebugDataSize, Dummy);
end;
// TODO 64 bit version
function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
BinDebug: TJclBinDebugGenerator; var LinkerBugUnit: string;
var MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean;
var
ImageStream: TMemoryStream;
NtHeaders32: PImageNtHeaders32;
Sections, LastSection, JclDebugSection: PImageSectionHeader;
VirtualAlignedSize: DWORD;
I, X, NeedFill: Integer;
procedure RoundUpToAlignment(var Value: DWORD; Alignment: DWORD);
begin
if (Value mod Alignment) <> 0 then
Value := ((Value div Alignment) + 1) * Alignment;
end;
begin
MapFileSize := 0;
JclDebugDataSize := 0;
LineNumberErrors := 0;
LinkerBugUnit := '';
if BinDebug.Stream <> nil then
begin
Result := True;
if BinDebug.LinkerBug then
begin
LinkerBugUnit := BinDebug.LinkerBugUnitName;
LineNumberErrors := BinDebug.LineNumberErrors;
end;
end
else
Result := False;
if not Result then
Exit;
ImageStream := TMemoryStream.Create;
try
try
ImageStream.LoadFromFile(ExecutableFileName);
if PeMapImgTarget(ImageStream.Memory) = taWin32 then
begin
MapFileSize := BinDebug.Stream.Size;
JclDebugDataSize := BinDebug.DataStream.Size;
NtHeaders32 := PeMapImgNtHeaders32(ImageStream.Memory);
Assert(NtHeaders32 <> nil);
Sections := PeMapImgSections32(NtHeaders32);
Assert(Sections <> nil);
// Check whether there is not a section with the name already. If so, return True (#0000069)
if PeMapImgFindSection32(NtHeaders32, JclDbgDataResName) <> nil then
begin
Result := True;
Exit;
end;
LastSection := Sections;
Inc(LastSection, NtHeaders32^.FileHeader.NumberOfSections - 1);
JclDebugSection := LastSection;
Inc(JclDebugSection);
// Increase the number of sections
Inc(NtHeaders32^.FileHeader.NumberOfSections);
FillChar(JclDebugSection^, SizeOf(TImageSectionHeader), #0);
// JCLDEBUG Virtual Address
JclDebugSection^.VirtualAddress := LastSection^.VirtualAddress + LastSection^.Misc.VirtualSize;
RoundUpToAlignment(JclDebugSection^.VirtualAddress, NtHeaders32^.OptionalHeader.SectionAlignment);
// JCLDEBUG Physical Offset
JclDebugSection^.PointerToRawData := LastSection^.PointerToRawData + LastSection^.SizeOfRawData;
RoundUpToAlignment(JclDebugSection^.PointerToRawData, NtHeaders32^.OptionalHeader.FileAlignment);
// JCLDEBUG Section name
StrPLCopy(PChar(@JclDebugSection^.Name), JclDbgDataResName, IMAGE_SIZEOF_SHORT_NAME);
// JCLDEBUG Characteristics flags
JclDebugSection^.Characteristics := IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA;
// Size of virtual data area
JclDebugSection^.Misc.VirtualSize := JclDebugDataSize;
VirtualAlignedSize := JclDebugDataSize;
RoundUpToAlignment(VirtualAlignedSize, NtHeaders32^.OptionalHeader.SectionAlignment);
// Update Size of Image
Inc(NtHeaders32^.OptionalHeader.SizeOfImage, VirtualAlignedSize);
// Raw data size
JclDebugSection^.SizeOfRawData := JclDebugDataSize;
RoundUpToAlignment(JclDebugSection^.SizeOfRawData, NtHeaders32^.OptionalHeader.FileAlignment);
// Update Initialized data size
Inc(NtHeaders32^.OptionalHeader.SizeOfInitializedData, JclDebugSection^.SizeOfRawData);
// Fill data to alignment
NeedFill := Integer(JclDebugSection^.SizeOfRawData) - JclDebugDataSize;
// Note: Delphi linker seems to generate incorrect (unaligned) size of
// the executable when adding TD32 debug data so the position could be
// behind the size of the file then.
ImageStream.Seek(JclDebugSection^.PointerToRawData, soFromBeginning);
ImageStream.CopyFrom(BinDebug.DataStream, 0);
X := 0;
for I := 1 to NeedFill do
ImageStream.WriteBuffer(X, 1);
ImageStream.SaveToFile(ExecutableFileName);
end
else
Result := False;
except
Result := False;
end;
finally
ImageStream.Free;
end;
end;
//=== { TJclBinDebugGenerator } ==============================================
constructor TJclBinDebugGenerator.Create(const MapFileName: TFileName; Module: HMODULE);
begin
inherited Create(MapFileName, Module);
FDataStream := TMemoryStream.Create;
FMapFileName := MapFileName;
if FStream <> nil then
CreateData;
end;
destructor TJclBinDebugGenerator.Destroy;
begin
FreeAndNil(FDataStream);
inherited Destroy;
end;
function TJclBinDebugGenerator.CalculateCheckSum: Boolean;
var
Header: PJclDbgHeader;
P, EndData: PChar;
CheckSum: Integer;
begin
Result := DataStream.Size >= SizeOf(TJclDbgHeader);
if Result then
begin
P := DataStream.Memory;
EndData := P + DataStream.Size;
Header := PJclDbgHeader(P);
CheckSum := 0;
Header^.CheckSum := 0;
Header^.CheckSumValid := True;
while P < EndData do
begin
Inc(CheckSum, PInteger(P)^);
Inc(PInteger(P));
end;
Header^.CheckSum := CheckSum;
end;
end;
procedure TJclBinDebugGenerator.CreateData;
var
WordList: TStringList;
WordStream: TMemoryStream;
LastSegmentID: Word;
LastSegmentStored: Boolean;
function AddWord(const S: string): Integer;
var
N: Integer;
E: string;
begin
if S = '' then
begin
Result := 0;
Exit;
end;
N := WordList.IndexOf(S);
if N = -1 then
begin
Result := WordStream.Position;
E := EncodeNameString(S);
WordStream.WriteBuffer(Pointer(E)^, Length(E));
WordList.AddObject(S, TObject(Result));
end
else
Result := DWORD(WordList.Objects[N]);
Inc(Result);
end;
procedure WriteValue(Value: Integer);
var
L: Integer;
D: DWORD;
P: array [1..5] of Byte;
begin
D := Value and $FFFFFFFF;
L := 0;
while D > $7F do
begin
Inc(L);
P[L] := (D and $7F) or $80;
D := D shr 7;
end;
Inc(L);
P[L] := (D and $7F);
FDataStream.WriteBuffer(P, L);
end;
procedure WriteValueOfs(Value: Integer; var LastValue: Integer);
begin
WriteValue(Value - LastValue);
LastValue := Value;
end;
function IsSegmentStored(SegID: Word): Boolean;
var
SegIndex: Integer;
GroupName: string;
begin
if (SegID <> LastSegmentID) then
begin
LastSegmentID := $FFFF;
LastSegmentStored := False;
for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
if FSegmentClasses[SegIndex].Segment = SegID then
begin
LastSegmentID := FSegmentClasses[SegIndex].Segment;
GroupName := MapStringToStr(FSegmentClasses[SegIndex].GroupName);
LastSegmentStored := (GroupName = 'CODE') or (GroupName = 'ICODE');
Break;
end;
end;
Result := LastSegmentStored;
end;
var
FileHeader: TJclDbgHeader;
I, D: Integer;
S: string;
L1, L2, L3: Integer;
FirstWord, SecondWord: Integer;
begin
LastSegmentID := $FFFF;
WordStream := TMemoryStream.Create;
WordList := TStringList.Create;
try
WordList.Sorted := True;
WordList.Duplicates := dupError;
FileHeader.Signature := JclDbgDataSignature;
FileHeader.Version := JclDbgHeaderVersion;
FileHeader.CheckSum := 0;
FileHeader.CheckSumValid := False;
FileHeader.ModuleName := AddWord(PathExtractFileNameNoExt(FMapFileName));
FDataStream.WriteBuffer(FileHeader, SizeOf(FileHeader));
FileHeader.Units := FDataStream.Position;
L1 := 0;
L2 := 0;
for I := 0 to Length(FSegments) - 1 do
if IsSegmentStored(FSegments[I].Segment) then
begin
WriteValueOfs(FSegments[I].StartVA, L1);
WriteValueOfs(AddWord(MapStringToStr(FSegments[I].UnitName)), L2);
end;
WriteValue(MaxInt);
FileHeader.SourceNames := FDataStream.Position;
L1 := 0;
L2 := 0;
for I := 0 to Length(FSourceNames) - 1 do
if IsSegmentStored(FSourceNames[I].Segment) then
begin
WriteValueOfs(FSourceNames[I].VA, L1);
WriteValueOfs(AddWord(MapStringToStr(FSourceNames[I].ProcName)), L2);
end;
WriteValue(MaxInt);
FileHeader.Symbols := FDataStream.Position;
L1 := 0;
L2 := 0;
L3 := 0;
for I := 0 to Length(FProcNames) - 1 do
if IsSegmentStored(FProcNames[I].Segment) then
begin
WriteValueOfs(FProcNames[I].VA, L1);
// MAP files generated by C++Builder have spaces in their names
S := MapStringToStr(FProcNames[I].ProcName, True);
D := Pos('.', S);
if D = 1 then
begin
FirstWord := 0;
SecondWord := 0;
end
else
if D = 0 then
begin
FirstWord := AddWord(S);
SecondWord := 0;
end
else
begin
FirstWord := AddWord(Copy(S, 1, D - 1));
SecondWord := AddWord(Copy(S, D + 1, Length(S)));
end;
WriteValueOfs(FirstWord, L2);
WriteValueOfs(SecondWord, L3);
end;
WriteValue(MaxInt);
FileHeader.LineNumbers := FDataStream.Position;
L1 := 0;
L2 := 0;
for I := 0 to Length(FLineNumbers) - 1 do
if IsSegmentStored(FLineNumbers[I].Segment) then
begin
WriteValueOfs(FLineNumbers[I].VA, L1);
WriteValueOfs(FLineNumbers[I].LineNumber, L2);
end;
WriteValue(MaxInt);
FileHeader.Words := FDataStream.Position;
FDataStream.CopyFrom(WordStream, 0);
I := 0;
while FDataStream.Size mod 4 <> 0 do
FDataStream.WriteBuffer(I, 1);
FDataStream.Seek(0, soFromBeginning);
FDataStream.WriteBuffer(FileHeader, SizeOf(FileHeader));
finally
WordStream.Free;
WordList.Free;
end;
end;
//=== { TJclBinDebugScanner } ================================================
constructor TJclBinDebugScanner.Create(AStream: TCustomMemoryStream; CacheData: Boolean);
begin
inherited Create;
FCacheData := CacheData;
FStream := AStream;
CheckFormat;
end;
procedure TJclBinDebugScanner.CacheLineNumbers;
var
P: Pointer;
Value, LineNumber, C, Ln: Integer;
CurrVA: DWORD;
begin
if FLineNumbers = nil then
begin
LineNumber := 0;
CurrVA := 0;
C := 0;
Ln := 0;
P := MakePtr(PJclDbgHeader(FStream.Memory)^.LineNumbers);
while ReadValue(P, Value) do
begin
Inc(CurrVA, Value);
ReadValue(P, Value);
Inc(LineNumber, Value);
if C = Ln then
begin
if Ln < 64 then
Ln := 64
else
Ln := Ln + Ln div 4;
SetLength(FLineNumbers, Ln);
end;
FLineNumbers[C].VA := CurrVA;
FLineNumbers[C].LineNumber := LineNumber;
Inc(C);
end;
SetLength(FLineNumbers, C);
end;
end;
procedure TJclBinDebugScanner.CacheProcNames;
var
P: Pointer;
Value, FirstWord, SecondWord, C, Ln: Integer;
CurrAddr: DWORD;
begin
if FProcNames = nil then
begin
FirstWord := 0;
SecondWord := 0;
CurrAddr := 0;
C := 0;
Ln := 0;
P := MakePtr(PJclDbgHeader(FStream.Memory)^.Symbols);
while ReadValue(P, Value) do
begin
Inc(CurrAddr, Value);
ReadValue(P, Value);
Inc(FirstWord, Value);
ReadValue(P, Value);
Inc(SecondWord, Value);
if C = Ln then
begin
if Ln < 64 then
Ln := 64
else
Ln := Ln + Ln div 4;
SetLength(FProcNames, Ln);
end;
FProcNames[C].Addr := CurrAddr;
FProcNames[C].FirstWord := FirstWord;
FProcNames[C].SecondWord := SecondWord;
Inc(C);
end;
SetLength(FProcNames, C);
end;
end;
procedure TJclBinDebugScanner.CheckFormat;
var
CheckSum: Integer;
Data, EndData: PChar;
Header: PJclDbgHeader;
begin
Data := FStream.Memory;
Header := PJclDbgHeader(Data);
FValidFormat := (Data <> nil) and (FStream.Size > SizeOf(TJclDbgHeader)) and
(FStream.Size mod 4 = 0) and
(Header^.Signature = JclDbgDataSignature) and (Header^.Version = JclDbgHeaderVersion);
if FValidFormat and Header^.CheckSumValid then
begin
CheckSum := -Header^.CheckSum;
EndData := Data + FStream.Size;
while Data < EndData do
begin
Inc(CheckSum, PInteger(Data)^);
Inc(PInteger(Data));
end;
CheckSum := (CheckSum shr 8) or (CheckSum shl 24);
FValidFormat := (CheckSum = Header^.CheckSum);
end;
end;
function TJclBinDebugScanner.DataToStr(A: Integer): string;
var
P: PChar;
begin
if A = 0 then
Result := ''
else
begin
P := PChar(DWORD(A) + DWORD(FStream.Memory) + DWORD(PJclDbgHeader(FStream.Memory)^.Words) - 1);
Result := DecodeNameString(P);
end;
end;
function TJclBinDebugScanner.GetModuleName: string;
begin
Result := DataToStr(PJclDbgHeader(FStream.Memory)^.ModuleName);
end;
function TJclBinDebugScanner.IsModuleNameValid(const Name: TFileName): Boolean;
begin
Result := AnsiSameText(ModuleName, PathExtractFileNameNoExt(Name));
end;
function TJclBinDebugScanner.LineNumberFromAddr(Addr: DWORD): Integer;
var
Dummy: Integer;
begin
Result := LineNumberFromAddr(Addr, Dummy);
end;
function TJclBinDebugScanner.LineNumberFromAddr(Addr: DWORD; var Offset: Integer): Integer;
var
P: Pointer;
Value, LineNumber: Integer;
CurrVA, ModuleStartVA, ItemVA: DWORD;
begin
ModuleStartVA := ModuleStartFromAddr(Addr);
LineNumber := 0;
Offset := 0;
if FCacheData then
begin
CacheLineNumbers;
for Value := Length(FLineNumbers) - 1 downto 0 do
if FLineNumbers[Value].VA <= Addr then
begin
if FLineNumbers[Value].VA >= ModuleStartVA then
begin
LineNumber := FLineNumbers[Value].LineNumber;
Offset := Addr - FLineNumbers[Value].VA;
end;
Break;
end;
end
else
begin
P := MakePtr(PJclDbgHeader(FStream.Memory)^.LineNumbers);
CurrVA := 0;
ItemVA := 0;
while ReadValue(P, Value) do
begin
Inc(CurrVA, Value);
if Addr < CurrVA then
begin
if ItemVA < ModuleStartVA then
begin
LineNumber := 0;
Offset := 0;
end;
Break;
end
else
begin
ItemVA := CurrVA;
ReadValue(P, Value);
Inc(LineNumber, Value);
Offset := Addr - CurrVA;
end;
end;
end;
Result := LineNumber;
end;
function TJclBinDebugScanner.MakePtr(A: Integer): Pointer;
begin
Result := Pointer(DWORD(FStream.Memory) + DWORD(A));
end;
function TJclBinDebugScanner.ModuleNameFromAddr(Addr: DWORD): string;
var
Value, Name: Integer;
StartAddr: DWORD;
P: Pointer;
begin
P := MakePtr(PJclDbgHeader(FStream.Memory)^.Units);
Name := 0;
StartAddr := 0;
while ReadValue(P, Value) do
begin
Inc(StartAddr, Value);
if Addr < StartAddr then
Break
else
begin
ReadValue(P, Value);
Inc(Name, Value);
end;
end;
Result := DataToStr(Name);
end;
function TJclBinDebugScanner.ModuleStartFromAddr(Addr: DWORD): DWORD;
var
Value: Integer;
StartAddr, ModuleStartAddr: DWORD;
P: Pointer;
begin
P := MakePtr(PJclDbgHeader(FStream.Memory)^.Units);
StartAddr := 0;
ModuleStartAddr := DWORD(-1);
while ReadValue(P, Value) do
begin
Inc(StartAddr, Value);
if Addr < StartAddr then
Break
else
begin
ReadValue(P, Value);
ModuleStartAddr := StartAddr;
end;
end;
Result := ModuleStartAddr;
end;
function TJclBinDebugScanner.ProcNameFromAddr(Addr: DWORD): string;
var
Dummy: Integer;
begin
Result := ProcNameFromAddr(Addr, Dummy);
end;
function TJclBinDebugScanner.ProcNameFromAddr(Addr: DWORD; var Offset: Integer): string;
var
P: Pointer;
Value, FirstWord, SecondWord: Integer;
CurrAddr, ModuleStartAddr, ItemAddr: DWORD;
begin
ModuleStartAddr := ModuleStartFromAddr(Addr);
FirstWord := 0;
SecondWord := 0;
Offset := 0;
if FCacheData then
begin
CacheProcNames;
for Value := Length(FProcNames) - 1 downto 0 do
if FProcNames[Value].Addr <= Addr then
begin
if FProcNames[Value].Addr >= ModuleStartAddr then
begin
FirstWord := FProcNames[Value].FirstWord;
SecondWord := FProcNames[Value].SecondWord;
Offset := Addr - FProcNames[Value].Addr;
end;
Break;
end;
end
else
begin
P := MakePtr(PJclDbgHeader(FStream.Memory)^.Symbols);
CurrAddr := 0;
ItemAddr := 0;
while ReadValue(P, Value) do
begin
Inc(CurrAddr, Value);
if Addr < CurrAddr then
begin
if ItemAddr < ModuleStartAddr then
begin
FirstWord := 0;
SecondWord := 0;
Offset := 0;
end;
Break;
end
else
begin
ItemAddr := CurrAddr;
ReadValue(P, Value);
Inc(FirstWord, Value);
ReadValue(P, Value);
Inc(SecondWord, Value);
Offset := Addr - CurrAddr;
end;
end;
end;
if FirstWord <> 0 then
begin
Result := DataToStr(FirstWord);
if SecondWord <> 0 then
Result := Result + '.' + DataToStr(SecondWord)
end
else
Result := '';
end;
function TJclBinDebugScanner.ReadValue(var P: Pointer; var Value: Integer): Boolean;
var
N: Integer;
I: Integer;
B: Byte;
begin
N := 0;
I := 0;
repeat
B := PByte(P)^;
Inc(PByte(P));
Inc(N, (B and $7F) shl I);
Inc(I, 7);
until B and $80 = 0;
Value := N;
Result := (Value <> MaxInt);
end;
function TJclBinDebugScanner.SourceNameFromAddr(Addr: DWORD): string;
var
Value, Name: Integer;
StartAddr, ModuleStartAddr, ItemAddr: DWORD;
P: Pointer;
Found: Boolean;
begin
ModuleStartAddr := ModuleStartFromAddr(Addr);
P := MakePtr(PJclDbgHeader(FStream.Memory)^.SourceNames);
Name := 0;
StartAddr := 0;
ItemAddr := 0;
Found := False;
while ReadValue(P, Value) do
begin
Inc(StartAddr, Value);
if Addr < StartAddr then
begin
if ItemAddr < ModuleStartAddr then
Name := 0
else
Found := True;
Break;
end
else
begin
ItemAddr := StartAddr;
ReadValue(P, Value);
Inc(Name, Value);
end;
end;
if Found then
Result := DataToStr(Name)
else
Result := '';
end;
//=== { TJclDebugInfoSource } ================================================
constructor TJclDebugInfoSource.Create(AModule: HMODULE);
begin
FModule := AModule;
end;
function TJclDebugInfoSource.GetFileName: TFileName;
begin
Result := GetModulePath(FModule);
end;
function TJclDebugInfoSource.VAFromAddr(const Addr: Pointer): DWORD;
begin
Result := DWORD(Addr) - FModule - ModuleCodeOffset;
end;
//=== { TJclDebugInfoList } ==================================================
var
DebugInfoList: TJclDebugInfoList = nil;
InfoSourceClassList: TList = nil;
DebugInfoCritSect: TJclCriticalSection;
procedure NeedDebugInfoList;
begin
if DebugInfoList = nil then
DebugInfoList := TJclDebugInfoList.Create;
end;
function TJclDebugInfoList.CreateDebugInfo(const Module: HMODULE): TJclDebugInfoSource;
var
I: Integer;
begin
NeedInfoSourceClassList;
Result := nil;
for I := 0 to InfoSourceClassList.Count - 1 do
begin
Result := TJclDebugInfoSourceClass(InfoSourceClassList.Items[I]).Create(Module);
try
if Result.InitializeSource then
Break
else
FreeAndNil(Result);
except
Result.Free;
raise;
end;
end;
end;
function TJclDebugInfoList.GetItemFromModule(const Module: HMODULE): TJclDebugInfoSource;
var
I: Integer;
TempItem: TJclDebugInfoSource;
begin
Result := nil;
if Module = 0 then
Exit;
for I := 0 to Count - 1 do
begin
TempItem := Items[I];
if TempItem.Module = Module then
begin
Result := TempItem;
Break;
end;
end;
if Result = nil then
begin
Result := CreateDebugInfo(Module);
if Result <> nil then
Add(Result);
end;
end;
function TJclDebugInfoList.GetItems(Index: Integer): TJclDebugInfoSource;
begin
Result := TJclDebugInfoSource(Get(Index));
end;
function TJclDebugInfoList.GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
var
Item: TJclDebugInfoSource;
begin
Finalize(Info);
FillChar(Info, SizeOf(Info), #0);
Item := ItemFromModule[ModuleFromAddr(Addr)];
if Item <> nil then
Result := Item.GetLocationInfo(Addr, Info)
else
Result := False;
end;
class procedure TJclDebugInfoList.NeedInfoSourceClassList;
begin
if not Assigned(InfoSourceClassList) then
begin
InfoSourceClassList := TList.Create;
{$IFNDEF DEBUG_NO_BINARY}
InfoSourceClassList.Add(Pointer(TJclDebugInfoBinary));
{$ENDIF !DEBUG_NO_BINARY}
{$IFNDEF DEBUG_NO_TD32}
InfoSourceClassList.Add(Pointer(TJclDebugInfoTD32));
{$ENDIF !DEBUG_NO_TD32}
{$IFNDEF DEBUG_NO_MAP}
InfoSourceClassList.Add(Pointer(TJclDebugInfoMap));
{$ENDIF !DEBUG_NO_MAP}
{$IFNDEF DEBUG_NO_SYMBOLS}
InfoSourceClassList.Add(Pointer(TJclDebugInfoSymbols));
{$ENDIF !DEBUG_NO_SYMBOLS}
{$IFNDEF DEBUG_NO_EXPORTS}
InfoSourceClassList.Add(Pointer(TJclDebugInfoExports));
{$ENDIF !DEBUG_NO_EXPORTS}
end;
end;
class procedure TJclDebugInfoList.RegisterDebugInfoSource(
const InfoSourceClass: TJclDebugInfoSourceClass);
begin
NeedInfoSourceClassList;
InfoSourceClassList.Add(Pointer(InfoSourceClass));
end;
class procedure TJclDebugInfoList.RegisterDebugInfoSourceFirst(
const InfoSourceClass: TJclDebugInfoSourceClass);
begin
NeedInfoSourceClassList;
InfoSourceClassList.Insert(0, Pointer(InfoSourceClass));
end;
class procedure TJclDebugInfoList.UnRegisterDebugInfoSource(
const InfoSourceClass: TJclDebugInfoSourceClass);
begin
if Assigned(InfoSourceClassList) then
InfoSourceClassList.Remove(Pointer(InfoSourceClass));
end;
//=== { TJclDebugInfoMap } ===================================================
destructor TJclDebugInfoMap.Destroy;
begin
FreeAndNil(FScanner);
inherited Destroy;
end;
function TJclDebugInfoMap.GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
var
VA: DWORD;
begin
VA := VAFromAddr(Addr);
with FScanner do
begin
Info.UnitName := ModuleNameFromAddr(VA);
Result := Info.UnitName <> '';
if Result then
begin
Info.Address := Addr;
Info.ProcedureName := ProcNameFromAddr(VA, Info.OffsetFromProcName);
Info.LineNumber := LineNumberFromAddr(VA, Info.OffsetFromLineNumber);
Info.SourceName := SourceNameFromAddr(VA);
Info.DebugInfo := Self;
Info.BinaryFileName := FileName;
end;
end;
end;
function TJclDebugInfoMap.InitializeSource: Boolean;
var
MapFileName: TFileName;
begin
MapFileName := ChangeFileExt(FileName, JclMapFileExtension);
Result := FileExists(MapFileName);
if Result then
FScanner := TJclMapScanner.Create(MapFileName, Module);
end;
//=== { TJclDebugInfoBinary } ================================================
destructor TJclDebugInfoBinary.Destroy;
begin
FreeAndNil(FScanner);
FreeAndNil(FStream);
inherited Destroy;
end;
function TJclDebugInfoBinary.GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
var
VA: DWORD;
begin
VA := VAFromAddr(Addr);
with FScanner do
begin
Info.UnitName := ModuleNameFromAddr(VA);
Result := Info.UnitName <> '';
if Result then
begin
Info.Address := Addr;
Info.ProcedureName := ProcNameFromAddr(VA, Info.OffsetFromProcName);
Info.LineNumber := LineNumberFromAddr(VA, Info.OffsetFromLineNumber);
Info.SourceName := SourceNameFromAddr(VA);
Info.DebugInfo := Self;
Info.BinaryFileName := FileName;
end;
end;
end;
function TJclDebugInfoBinary.InitializeSource: Boolean;
var
JdbgFileName: TFileName;
VerifyFileName: Boolean;
begin
VerifyFileName := False;
Result := (PeMapImgFindSectionFromModule(Pointer(Module), JclDbgDataResName) <> nil);
if Result then
FStream := TJclPeSectionStream.Create(Module, JclDbgDataResName)
else
begin
JdbgFileName := ChangeFileExt(FileName, JclDbgFileExtension);
Result := FileExists(JdbgFileName);
if Result then
begin
FStream := TJclFileMappingStream.Create(JdbgFileName, fmOpenRead or fmShareDenyWrite);
VerifyFileName := True;
end;
end;
if Result then
begin
FScanner := TJclBinDebugScanner.Create(FStream, True);
Result := FScanner.ValidFormat and
(not VerifyFileName or FScanner.IsModuleNameValid(FileName));
end;
end;
//=== { TJclDebugInfoExports } ===============================================
destructor TJclDebugInfoExports.Destroy;
begin
FreeAndNil(FBorImage);
inherited Destroy;
end;
function TJclDebugInfoExports.IsAddressInThisExportedFunction(Addr: PByteArray; FunctionStartAddr: Cardinal): Boolean;
begin
Dec(Cardinal(Addr), 6);
Result := False;
while (Cardinal(Addr) > FunctionStartAddr) do
begin
if IsBadReadPtr(Addr, 6) then
Exit;
if (Addr[0] = $C2) and // ret $xxxx
(((Addr[3] = $90) and (Addr[4] = $90) and (Addr[5] = $90)) or // nop
((Addr[3] = $CC) and (Addr[4] = $CC) and (Addr[5] = $CC))) then // int 3
Exit;
if (Addr[0] = $C3) and // ret
(((Addr[1] = $90) and (Addr[2] = $90) and (Addr[3] = $90)) or // nop
((Addr[1] = $CC) and (Addr[2] = $CC) and (Addr[3] = $CC))) then // int 3
Exit;
if (Addr[0] = $E9) and // jmp rel-far
(((Addr[5] = $90) and (Addr[6] = $90) and (Addr[7] = $90)) or // nop
((Addr[5] = $CC) and (Addr[6] = $CC) and (Addr[7] = $CC))) then // int 3
Exit;
if (Addr[0] = $EB) and // jmp rel-near
(((Addr[2] = $90) and (Addr[3] = $90) and (Addr[4] = $90)) or // nop
((Addr[2] = $CC) and (Addr[3] = $CC) and (Addr[4] = $CC))) then // int 3
Exit;
Dec(Cardinal(Addr));
end;
Result := True;
end;
function TJclDebugInfoExports.GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
var
I, BasePos: Integer;
VA: DWORD;
Desc: TJclBorUmDescription;
Unmangled: string;
RawName: Boolean;
begin
Result := False;
VA := DWORD(Addr) - FModule;
RawName := not FBorImage.IsPackage;
Info.OffsetFromProcName := 0;
Info.OffsetFromLineNumber := 0;
Info.BinaryFileName := FileName;
with FBorImage.ExportList do
begin
SortList(esAddress, False);
for I := Count - 1 downto 0 do
if Items[I].Address <= VA then
begin
if RawName then
begin
Info.ProcedureName := Items[I].Name;
Info.OffsetFromProcName := VA - Items[I].Address;
Result := True;
end
else
begin
case PeBorUnmangleName(Items[I].Name, Unmangled, Desc, BasePos) of
urOk:
begin
Info.UnitName := Copy(Unmangled, 1, BasePos - 2);
if not (Desc.Kind in [skRTTI, skVTable]) then
begin
Info.ProcedureName := Copy(Unmangled, BasePos, Length(Unmangled));
if smLinkProc in Desc.Modifiers then
Info.ProcedureName := '@' + Info.ProcedureName;
Info.OffsetFromProcName := VA - Items[I].Address;
end;
Result := True;
end;
urNotMangled:
begin
Info.ProcedureName := Items[I].Name;
Info.OffsetFromProcName := VA - Items[I].Address;
Result := True;
end;
end;
end;
if Result then
begin
Info.Address := Addr;
Info.DebugInfo := Self;
{ Check if we have a valid address in an exported function. }
if not IsAddressInThisExportedFunction(Addr, FModule + Items[I].Address) then
begin
//Info.UnitName := '[' + AnsiLowerCase(ExtractFileName(GetModulePath(FModule))) + ']'
Info.ProcedureName := Format(RsUnknownFunctionAt, [Info.ProcedureName]);
end;
Break;
end;
end;
end;
end;
function TJclDebugInfoExports.InitializeSource: Boolean;
begin
FBorImage := TJclPeBorImage.Create(True);
FBorImage.AttachLoadedModule(FModule);
Result := FBorImage.StatusOK and (FBorImage.ExportList.Count > 0);
end;
//=== { TJclDebugInfoTD32 } ==================================================
destructor TJclDebugInfoTD32.Destroy;
begin
FreeAndNil(FImage);
inherited Destroy;
end;
function TJclDebugInfoTD32.GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
var
VA: DWORD;
begin
VA := VAFromAddr(Addr);
Info.UnitName := FImage.TD32Scanner.ModuleNameFromAddr(VA);
Result := Info.UnitName <> '';
if Result then
with Info do
begin
Address := Addr;
ProcedureName := FImage.TD32Scanner.ProcNameFromAddr(VA, OffsetFromProcName);
LineNumber := FImage.TD32Scanner.LineNumberFromAddr(VA, OffsetFromLineNumber);
SourceName := FImage.TD32Scanner.SourceNameFromAddr(VA);
DebugInfo := Self;
BinaryFileName := FileName;
end;
end;
function TJclDebugInfoTD32.InitializeSource: Boolean;
begin
FImage := TJclPeBorTD32Image.Create(True);
try
FImage.AttachLoadedModule(Module);
Result := FImage.IsTD32DebugPresent;
except
Result := False;
end;
end;
//=== { TJclDebugInfoSymbols } ===============================================
type
TSymInitializeFunc = function (hProcess: THandle; UserSearchPath: LPSTR;
fInvadeProcess: Bool): Bool; stdcall;
TSymGetOptionsFunc = function: DWORD; stdcall;
TSymSetOptionsFunc = function (SymOptions: DWORD): DWORD; stdcall;
TSymCleanupFunc = function (hProcess: THandle): Bool; stdcall;
TSymGetSymFromAddrFunc = function (hProcess: THandle; dwAddr: DWORD;
pdwDisplacement: PDWORD; var Symbol: TImagehlpSymbol): Bool; stdcall;
TSymGetModuleInfoFunc = function (hProcess: THandle; dwAddr: DWORD;
var ModuleInfo: TImagehlpModule): Bool; stdcall;
TSymLoadModuleFunc = function (hProcess: THandle; hFile: THandle; ImageName,
ModuleName: LPSTR; BaseOfDll, SizeOfDll: DWORD): DWORD; stdcall;
TSymGetLineFromAddrFunc = function (hProcess: THandle; dwAddr: DWORD;
pdwDisplacement: PDWORD; var Line: TImageHlpLine): Bool; stdcall;
var
DebugSymbolsInitialized: Boolean = False;
DebugSymbolsLoadFailed: Boolean = False;
ImageHlpDllHandle: THandle = 0;
SymInitializeFunc: TSymInitializeFunc = nil;
SymGetOptionsFunc: TSymGetOptionsFunc = nil;
SymSetOptionsFunc: TSymSetOptionsFunc = nil;
SymCleanupFunc: TSymCleanupFunc = nil;
SymGetSymFromAddrFunc: TSymGetSymFromAddrFunc = nil;
SymGetModuleInfoFunc: TSymGetModuleInfoFunc = nil;
SymLoadModuleFunc: TSymLoadModuleFunc = nil;
SymGetLineFromAddrFunc: TSymGetLineFromAddrFunc = nil;
const
ImageHlpDllName = 'imagehlp.dll'; // do not localize
SymInitializeFuncName = 'SymInitialize'; // do not localize
SymGetOptionsFuncName = 'SymGetOptions'; // do not localize
SymSetOptionsFuncName = 'SymSetOptions'; // do not localize
SymCleanupFuncName = 'SymCleanup'; // do not localize
SymGetSymFromAddrFuncName = 'SymGetSymFromAddr'; // do not localize
SymGetModuleInfoFuncName = 'SymGetModuleInfo'; // do not localize
SymLoadModuleFuncName = 'SymLoadModule'; // do not localize
SymGetLineFromAddrName = 'SymGetLineFromAddr'; // do not localize
function StrRemoveEmptyPaths(const Paths: string): string;
var
List: TStrings;
I: Integer;
begin
List := TStringList.Create;
try
StrToStrings(Paths, DirSeparator, List, False);
for I := 0 to List.Count - 1 do
if Trim(List[I]) = '' then
List[I] := '';
Result := StringsToStr(List, DirSeparator, False);
finally
List.Free;
end;
end;
class function TJclDebugInfoSymbols.InitializeDebugSymbols: Boolean;
var
EnvironmentVarValue, SearchPath: string;
SymOptions: Cardinal;
begin
if DebugSymbolsLoadFailed then
Result := False
else
if not DebugSymbolsInitialized then
begin
DebugSymbolsLoadFailed := LoadDebugFunctions;
Result := not DebugSymbolsLoadFailed;
if Result then
begin
SearchPath := ''; // use default paths
if JclDebugInfoSymbolPaths <> '' then
begin
SearchPath := StrEnsureSuffix(DirSeparator, JclDebugInfoSymbolPaths);
SearchPath := StrEnsureNoSuffix(DirSeparator, SearchPath + GetCurrentFolder);
if GetEnvironmentVar(EnvironmentVarNtSymbolPath, EnvironmentVarValue) and (EnvironmentVarValue <> '') then
SearchPath := StrEnsureNoSuffix(DirSeparator, StrEnsureSuffix(DirSeparator, EnvironmentVarValue) + SearchPath);
if GetEnvironmentVar(EnvironmentVarAlternateNtSymbolPath, EnvironmentVarValue) and (EnvironmentVarValue <> '') then
SearchPath := StrEnsureNoSuffix(DirSeparator, StrEnsureSuffix(DirSeparator, EnvironmentVarValue) + SearchPath);
{ DbgHelp.dll crashes when an empty path is specified. This also means
that the SearchPath must not end with a DirSeparator. }
SearchPath := StrRemoveEmptyPaths(SearchPath);
end;
if IsWinNT then
Result := SymInitializeFunc(GetCurrentProcess, Pointer(SearchPath), False)
else
Result := SymInitializeFunc(GetCurrentProcessId, Pointer(SearchPath), False);
if Result then
begin
SymOptions := SymGetOptionsFunc or SYMOPT_DEFERRED_LOADS
or SYMOPT_FAIL_CRITICAL_ERRORS or SYMOPT_INCLUDE_32BIT_MODULES or SYMOPT_LOAD_LINES;
SymOptions := SymOptions and (not (SYMOPT_NO_UNQUALIFIED_LOADS or SYMOPT_UNDNAME));
SymSetOptionsFunc(SymOptions);
end;
DebugSymbolsInitialized := Result;
end
else
UnloadDebugFunctions;
end
else
Result := DebugSymbolsInitialized;
end;
class function TJclDebugInfoSymbols.CleanupDebugSymbols: Boolean;
begin
Result := True;
if DebugSymbolsInitialized then
Result := SymCleanupFunc(GetCurrentProcess);
UnloadDebugFunctions;
end;
function TJclDebugInfoSymbols.GetLocationInfo(const Addr: Pointer;
var Info: TJclLocationInfo): Boolean;
const
SymbolNameLength = 1000;
SymbolSize = SizeOf(TImagehlpSymbol) + SymbolNameLength;
UndecoratedLength = 100;
var
Displacement: DWORD;
Symbol: PImagehlpSymbol;
SymbolName: PChar;
ProcessHandle: THandle;
UndecoratedName: array [0..UndecoratedLength] of Char;
Line: TImageHlpLine;
begin
GetMem(Symbol, SymbolSize);
try
ProcessHandle := GetCurrentProcess;
ZeroMemory(Symbol, SymbolSize);
Symbol^.SizeOfStruct := SizeOf(TImageHlpSymbol);
Symbol^.MaxNameLength := SymbolNameLength;
Displacement := 0;
Result := SymGetSymFromAddrFunc(ProcessHandle, DWORD(Addr), @Displacement, Symbol^);
if Result then
begin
Info.DebugInfo := Self;
Info.Address := Addr;
Info.BinaryFileName := FileName;
Info.OffsetFromProcName := Displacement;
SymbolName := Symbol^.Name;
SetString(Info.ProcedureName, UndecoratedName, UnDecorateSymbolName(SymbolName, UndecoratedName, UndecoratedLength, UNDNAME_NAME_ONLY or UNDNAME_NO_ARGUMENTS));
end;
finally
FreeMem(Symbol);
end;
// line number is optional
if Result and Assigned(SymGetLineFromAddrFunc) then
begin
ZeroMemory(@Line, SizeOf(Line));
Line.SizeOfStruct := SizeOf(Line);
Displacement := 0;
if SymGetLineFromAddrFunc(ProcessHandle, DWORD(Addr), @Displacement, Line) then
begin
Info.LineNumber := Line.LineNumber;
Info.UnitName := Line.FileName;
Info.OffsetFromLineNumber := Displacement;
end;
end;
end;
function TJclDebugInfoSymbols.InitializeSource: Boolean;
var
ModuleFileName: string;
ModuleInfo: TImagehlpModule;
ProcessHandle: THandle;
begin
Result := InitializeDebugSymbols;
if Result then
begin
ProcessHandle := GetCurrentProcess;
ZeroMemory(@ModuleInfo, SizeOf(ModuleInfo));
ModuleInfo.SizeOfStruct := SizeOf(ModuleInfo);
if ((not SymGetModuleInfoFunc(ProcessHandle, Module, ModuleInfo))
or (ModuleInfo.BaseOfImage = 0)) then
begin
ModuleFileName := GetModulePath(Module);
Result := SymLoadModuleFunc(ProcessHandle, 0, PChar(ModuleFileName), nil, 0, 0) <> 0;
ZeroMemory(@ModuleInfo, SizeOf(ModuleInfo));
ModuleInfo.SizeOfStruct := SizeOf(ModuleInfo);
Result := Result and SymGetModuleInfoFunc(ProcessHandle, Module, ModuleInfo);
end;
Result := Result and not (ModuleInfo.SymType in [SymNone, SymExport]);
end;
end;
class function TJclDebugInfoSymbols.LoadDebugFunctions: Boolean;
begin
ImageHlpDllHandle := LoadLibrary(ImageHlpDllName);
if ImageHlpDllHandle <> 0 then
begin
SymInitializeFunc := GetProcAddress(ImageHlpDllHandle, SymInitializeFuncName);
SymGetOptionsFunc := GetProcAddress(ImageHlpDllHandle, SymGetOptionsFuncName);
SymSetOptionsFunc := GetProcAddress(ImageHlpDllHandle, SymSetOptionsFuncName);
SymCleanupFunc := GetProcAddress(ImageHlpDllHandle, SymCleanupFuncName);
SymGetSymFromAddrFunc := GetProcAddress(ImageHlpDllHandle, SymGetSymFromAddrFuncName);
SymGetModuleInfoFunc := GetProcAddress(ImageHlpDllHandle, SymGetModuleInfoFuncName);
SymLoadModuleFunc := GetProcAddress(ImageHlpDllHandle, SymLoadModuleFuncName);
SymGetLineFromAddrFunc := GetProcAddress(ImageHlpDllHandle, SymGetLineFromAddrName);
end;
// SymGetLineFromAddrFunc is optional
Result := (ImageHlpDllHandle = 0) or (not Assigned(SymInitializeFunc))
or (not Assigned(SymGetOptionsFunc)) or (not Assigned(SymSetOptionsFunc))
or (not Assigned(SymCleanupFunc)) or (not Assigned(SymGetSymFromAddrFunc))
or (not Assigned(SymGetModuleInfoFunc)) or (not Assigned(SymLoadModuleFunc));
end;
class function TJclDebugInfoSymbols.UnloadDebugFunctions: Boolean;
begin
Result := ImageHlpDllHandle <> 0;
if Result then
FreeLibrary(ImageHlpDllHandle);
ImageHlpDllHandle := 0;
SymInitializeFunc := nil;
SymGetOptionsFunc := nil;
SymSetOptionsFunc := nil;
SymCleanupFunc := nil;
SymGetSymFromAddrFunc := nil;
SymGetModuleInfoFunc := nil;
SymLoadModuleFunc := nil;
SymGetLineFromAddrFunc := nil;
end;
//=== Source location functions ==============================================
{$STACKFRAMES ON}
function Caller(Level: Integer; FastStackWalk: Boolean): Pointer;
var
TopOfStack: Cardinal;
BaseOfStack: Cardinal;
StackFrame: PStackFrame;
begin
Result := nil;
try
if FastStackWalk then
begin
StackFrame := GetEBP;
BaseOfStack := Cardinal(StackFrame) - 1;
TopOfStack := GetStackTop;
while (BaseOfStack < Cardinal(StackFrame)) and (Cardinal(StackFrame) < TopOfStack) do
begin
if Level = 0 then
begin
Result := Pointer(StackFrame^.CallerAdr - 1);
Break;
end;
StackFrame := PStackFrame(StackFrame^.CallersEBP);
Dec(Level);
end;
end
else
with TJclStackInfoList.Create(False, 1, nil, False, nil, nil) do
try
if Level < Count then
Result := Items[Level].CallerAdr;
finally
Free;
end;
except
Result := nil;
end;
end;
{$IFNDEF STACKFRAMES_ON}
{$STACKFRAMES OFF}
{$ENDIF ~STACKFRAMES_ON}
function GetLocationInfo(const Addr: Pointer): TJclLocationInfo;
begin
try
DebugInfoCritSect.Enter;
try
NeedDebugInfoList;
DebugInfoList.GetLocationInfo(Addr, Result)
finally
DebugInfoCritSect.Leave;
end;
except
Finalize(Result);
FillChar(Result, SizeOf(Result), #0);
end;
end;
function GetLocationInfo(const Addr: Pointer; var Info: TJclLocationInfo): Boolean;
begin
try
DebugInfoCritSect.Enter;
try
NeedDebugInfoList;
Result := DebugInfoList.GetLocationInfo(Addr, Info);
finally
DebugInfoCritSect.Leave;
end;
except
Result := False;
end;
end;
function GetLocationInfoStr(const Addr: Pointer; IncludeModuleName, IncludeAddressOffset,
IncludeStartProcLineOffset: Boolean; IncludeVAdress: Boolean): string;
var
Info, StartProcInfo: TJclLocationInfo;
OffsetStr, StartProcOffsetStr, FixedProcedureName: string;
Module : HMODULE;
begin
OffsetStr := '';
if GetLocationInfo(Addr, Info) then
with Info do
begin
FixedProcedureName := ProcedureName;
if Pos(UnitName + '.', FixedProcedureName) = 1 then
FixedProcedureName := Copy(FixedProcedureName, Length(UnitName) + 2, Length(FixedProcedureName) - Length(UnitName) - 1);
if LineNumber > 0 then
begin
if IncludeStartProcLineOffset and GetLocationInfo(Pointer(Cardinal(Info.Address) -
Cardinal(Info.OffsetFromProcName)), StartProcInfo) and (StartProcInfo.LineNumber > 0) then
StartProcOffsetStr := Format(' + %d', [LineNumber - StartProcInfo.LineNumber])
else
StartProcOffsetStr := '';
if IncludeAddressOffset then
begin
if OffsetFromLineNumber >= 0 then
OffsetStr := Format(' + $%x', [OffsetFromLineNumber])
else
OffsetStr := Format(' - $%x', [-OffsetFromLineNumber])
end;
Result := Format('[%p] %s.%s (Line %u, "%s"%s)%s', [Addr, UnitName, FixedProcedureName, LineNumber,
SourceName, StartProcOffsetStr, OffsetStr]);
end
else
begin
if IncludeAddressOffset then
OffsetStr := Format(' + $%x', [OffsetFromProcName]);
if UnitName <> '' then
Result := Format('[%p] %s.%s%s', [Addr, UnitName, FixedProcedureName, OffsetStr])
else
Result := Format('[%p] %s%s', [Addr, FixedProcedureName, OffsetStr]);
end;
end
else
begin
Result := Format('[%p]', [Addr]);
IncludeVAdress := True;
end;
if IncludeVAdress or IncludeModuleName then
begin
Module := ModuleFromAddr(Addr);
if IncludeVAdress then
begin
OffsetStr := Format('(%p) ', [Pointer(DWORD(Addr) - Module - ModuleCodeOffset)]);
Result := OffsetStr + Result;
end;
if IncludeModuleName then
Insert(Format('{%-12s}', [ExtractFileName(GetModulePath(Module))]), Result, 11);
end;
end;
function DebugInfoAvailable(const Module: HMODULE): Boolean;
begin
DebugInfoCritSect.Enter;
try
NeedDebugInfoList;
Result := (DebugInfoList.ItemFromModule[Module] <> nil);
finally
DebugInfoCritSect.Leave;
end;
end;
procedure ClearLocationData;
begin
DebugInfoCritSect.Enter;
try
if DebugInfoList <> nil then
DebugInfoList.Clear;
finally
DebugInfoCritSect.Leave;
end;
end;
{$STACKFRAMES ON}
function FileByLevel(const Level: Integer): string;
begin
Result := GetLocationInfo(Caller(Level + 1)).SourceName;
end;
function ModuleByLevel(const Level: Integer): string;
begin
Result := GetLocationInfo(Caller(Level + 1)).UnitName;
end;
function ProcByLevel(const Level: Integer): string;
begin
Result := GetLocationInfo(Caller(Level + 1)).ProcedureName;
end;
function LineByLevel(const Level: Integer): Integer;
begin
Result := GetLocationInfo(Caller(Level + 1)).LineNumber;
end;
function MapByLevel(const Level: Integer; var File_, Module_, Proc_: string;
var Line_: Integer): Boolean;
begin
Result := MapOfAddr(Caller(Level + 1), File_, Module_, Proc_, Line_);
end;
function ExtractClassName(const ProcedureName: string): string;
var
D: Integer;
begin
D := Pos('.', ProcedureName);
if D < 2 then
Result := ''
else
Result := Copy(ProcedureName, 1, D - 1);
end;
function ExtractMethodName(const ProcedureName: string): string;
begin
Result := Copy(ProcedureName, Pos('.', ProcedureName) + 1, Length(ProcedureName));
end;
function __FILE__(const Level: Integer): string;
begin
Result := FileByLevel(Level + 1);
end;
function __MODULE__(const Level: Integer): string;
begin
Result := ModuleByLevel(Level + 1);
end;
function __PROC__(const Level: Integer): string;
begin
Result := ProcByLevel(Level + 1);
end;
function __LINE__(const Level: Integer): Integer;
begin
Result := LineByLevel(Level + 1);
end;
function __MAP__(const Level: Integer; var _File, _Module, _Proc: string; var _Line: Integer): Boolean;
begin
Result := MapByLevel(Level + 1, _File, _Module, _Proc, _Line);
end;
{$IFNDEF STACKFRAMES_ON}
{$STACKFRAMES OFF}
{$ENDIF ~STACKFRAMES_ON}
function FileOfAddr(const Addr: Pointer): string;
begin
Result := GetLocationInfo(Addr).SourceName;
end;
function ModuleOfAddr(const Addr: Pointer): string;
begin
Result := GetLocationInfo(Addr).UnitName;
end;
function ProcOfAddr(const Addr: Pointer): string;
begin
Result := GetLocationInfo(Addr).ProcedureName;
end;
function LineOfAddr(const Addr: Pointer): Integer;
begin
Result := GetLocationInfo(Addr).LineNumber;
end;
function MapOfAddr(const Addr: Pointer; var File_, Module_, Proc_: string;
var Line_: Integer): Boolean;
var
LocInfo: TJclLocationInfo;
begin
NeedDebugInfoList;
Result := DebugInfoList.GetLocationInfo(Addr, LocInfo);
if Result then
begin
File_ := LocInfo.SourceName;
Module_ := LocInfo.UnitName;
Proc_ := LocInfo.ProcedureName;
Line_ := LocInfo.LineNumber;
end;
end;
function __FILE_OF_ADDR__(const Addr: Pointer): string;
begin
Result := FileOfAddr(Addr);
end;
function __MODULE_OF_ADDR__(const Addr: Pointer): string;
begin
Result := ModuleOfAddr(Addr);
end;
function __PROC_OF_ADDR__(const Addr: Pointer): string;
begin
Result := ProcOfAddr(Addr);
end;
function __LINE_OF_ADDR__(const Addr: Pointer): Integer;
begin
Result := LineOfAddr(Addr);
end;
function __MAP_OF_ADDR__(const Addr: Pointer; var _File, _Module, _Proc: string;
var _Line: Integer): Boolean;
begin
Result := MapOfAddr(Addr, _File, _Module, _Proc, _Line);
end;
//=== { TJclStackBaseList } ==================================================
constructor TJclStackBaseList.Create;
begin
inherited Create(True);
FThreadID := GetCurrentThreadId;
FTimeStamp := Now;
end;
destructor TJclStackBaseList.Destroy;
begin
if Assigned(FOnDestroy) then
FOnDestroy(Self);
inherited Destroy;
end;
//=== { TJclGlobalStackList } ================================================
type
TJclStackBaseListClass = class of TJclStackBaseList;
TJclGlobalStackList = class(TThreadList)
private
FLockedTID: DWORD;
FTIDLocked: Boolean;
function GetExceptStackInfo(TID: DWORD): TJclStackInfoList;
function GetLastExceptFrameList(TID: DWORD): TJclExceptFrameList;
procedure ItemDestroyed(Sender: TObject);
public
destructor Destroy; override;
procedure AddObject(AObject: TJclStackBaseList);
procedure LockThreadID(TID: DWORD);
procedure UnlockThreadID;
function FindObject(TID: DWORD; AClass: TJclStackBaseListClass): TJclStackBaseList;
property ExceptStackInfo[TID: DWORD]: TJclStackInfoList read GetExceptStackInfo;
property LastExceptFrameList[TID: DWORD]: TJclExceptFrameList read GetLastExceptFrameList;
end;
var
GlobalStackList: TJclGlobalStackList;
destructor TJclGlobalStackList.Destroy;
begin
with LockList do
try
while Count > 0 do
TObject(Items[0]).Free;
finally
UnlockList;
end;
inherited Destroy;
end;
procedure TJclGlobalStackList.AddObject(AObject: TJclStackBaseList);
var
ReplacedObj: TObject;
begin
AObject.FOnDestroy := ItemDestroyed;
with LockList do
try
ReplacedObj := FindObject(AObject.ThreadID, TJclStackBaseListClass(AObject.ClassType));
if ReplacedObj <> nil then
begin
Remove(ReplacedObj);
ReplacedObj.Free;
end;
Add(AObject);
finally
UnlockList;
end;
end;
function TJclGlobalStackList.FindObject(TID: DWORD; AClass: TJclStackBaseListClass): TJclStackBaseList;
var
I: Integer;
Item: TJclStackBaseList;
begin
Result := nil;
with LockList do
try
if FTIDLocked and (GetCurrentThreadId = MainThreadID) then
TID := FLockedTID;
for I := 0 to Count - 1 do
begin
Item := Items[I];
if (Item.ThreadID = TID) and (Item is AClass) then
begin
Result := Item;
Break;
end;
end;
finally
UnlockList;
end;
end;
function TJclGlobalStackList.GetExceptStackInfo(TID: DWORD): TJclStackInfoList;
begin
Result := TJclStackInfoList(FindObject(TID, TJclStackInfoList));
end;
function TJclGlobalStackList.GetLastExceptFrameList(TID: DWORD): TJclExceptFrameList;
begin
Result := TJclExceptFrameList(FindObject(TID, TJclExceptFrameList));
end;
procedure TJclGlobalStackList.ItemDestroyed(Sender: TObject);
begin
with LockList do
try
Remove(Sender);
finally
UnlockList;
end;
end;
procedure TJclGlobalStackList.LockThreadID(TID: DWORD);
begin
with LockList do
try
if GetCurrentThreadId = MainThreadID then
begin
FTIDLocked := True;
FLockedTID := TID;
end
else
FTIDLocked := False;
finally
UnlockList;
end;
end;
procedure TJclGlobalStackList.UnlockThreadID;
begin
with LockList do
try
FTIDLocked := False;
finally
UnlockList;
end;
end;
//=== { TJclGlobalModulesList } ==============================================
type
TJclGlobalModulesList = class(TObject)
private
FHookedModules: TJclModuleArray;
FLock: TJclCriticalSection;
FModulesList: TJclModuleInfoList;
public
constructor Create;
destructor Destroy; override;
function CreateModulesList: TJclModuleInfoList;
procedure FreeModulesList(var ModulesList: TJclModuleInfoList);
function ValidateAddress(Addr: Pointer): Boolean;
end;
var
GlobalModulesList: TJclGlobalModulesList;
constructor TJclGlobalModulesList.Create;
begin
FLock := TJclCriticalSection.Create;
end;
destructor TJclGlobalModulesList.Destroy;
begin
FreeAndNil(FLock);
FreeAndNil(FModulesList);
inherited Destroy;
end;
function TJclGlobalModulesList.CreateModulesList: TJclModuleInfoList;
var
I: Integer;
SystemModulesOnly: Boolean;
IsMultiThreaded: Boolean;
begin
IsMultiThreaded := IsMultiThread;
if IsMultiThreaded then
FLock.Enter;
try
if FModulesList = nil then
begin
SystemModulesOnly := not (stAllModules in JclStackTrackingOptions);
Result := TJclModuleInfoList.Create(False, SystemModulesOnly);
// Add known Borland modules collected by DLL exception hooking code
if SystemModulesOnly and JclHookedExceptModulesList(FHookedModules) then
for I := Low(FHookedModules) to High(FHookedModules) do
Result.AddModule(FHookedModules[I], True);
if stStaticModuleList in JclStackTrackingOptions then
FModulesList := Result;
end
else
Result := FModulesList;
finally
if IsMultiThreaded then
FLock.Leave;
end;
end;
procedure TJclGlobalModulesList.FreeModulesList(var ModulesList: TJclModuleInfoList);
var
IsMultiThreaded: Boolean;
begin
if FModulesList <> ModulesList then
begin
IsMultiThreaded := IsMultiThread;
if IsMultiThreaded then
FLock.Enter;
try
FreeAndNil(ModulesList);
finally
if IsMultiThreaded then
FLock.Leave;
end;
end;
end;
function TJclGlobalModulesList.ValidateAddress(Addr: Pointer): Boolean;
var
TempList: TJclModuleInfoList;
begin
TempList := CreateModulesList;
try
Result := TempList.IsValidModuleAddress(Addr);
finally
FreeModulesList(TempList);
end;
end;
function JclValidateModuleAddress(Addr: Pointer): Boolean;
begin
Result := GlobalModulesList.ValidateAddress(Addr);
end;
//=== Stack info routines ====================================================
{$STACKFRAMES OFF}
function ValidCodeAddr(CodeAddr: DWORD; ModuleList: TJclModuleInfoList): Boolean;
begin
if stAllModules in JclStackTrackingOptions then
Result := ModuleList.IsValidModuleAddress(Pointer(CodeAddr))
else
Result := ModuleList.IsSystemModuleAddress(Pointer(CodeAddr));
end;
procedure CorrectExceptStackListTop(List: TJclStackInfoList; SkipFirstItem: Boolean);
var
TopItem, I, FoundPos: Integer;
begin
FoundPos := -1;
if SkipFirstItem then
TopItem := 1
else
TopItem := 0;
with List do
begin
for I := Count - 1 downto TopItem do
if JclBelongsHookedCode(Items[I].CallerAdr) then
begin
FoundPos := I;
Break;
end;
if FoundPos <> -1 then
for I := FoundPos downto TopItem do
Delete(I);
end;
end;
{$STACKFRAMES ON}
procedure DoExceptionStackTrace(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean;
BaseOfStack: Pointer);
var
IgnoreLevels: DWORD;
FirstCaller: Pointer;
RawMode: Boolean;
Delayed: Boolean;
begin
RawMode := stRawMode in JclStackTrackingOptions;
Delayed := stDelayedTrace in JclStackTrackingOptions;
if BaseOfStack = nil then
begin
BaseOfStack := GetEBP;
IgnoreLevels := 1;
end
else
IgnoreLevels := Cardinal(-1); // because of the "IgnoreLevels + 1" in TJclStackInfoList.StoreToList()
if OSException then
begin
Inc(IgnoreLevels); // => HandleAnyException
FirstCaller := ExceptAddr;
end
else
FirstCaller := nil;
JclCreateStackList(RawMode, IgnoreLevels, FirstCaller, Delayed, BaseOfStack).CorrectOnAccess(OSException);
end;
function JclLastExceptStackList: TJclStackInfoList;
begin
Result := GlobalStackList.ExceptStackInfo[GetCurrentThreadID];
end;
function JclLastExceptStackListToStrings(Strings: TStrings; IncludeModuleName, IncludeAddressOffset,
IncludeStartProcLineOffset, IncludeVAdress: Boolean): Boolean;
var
List: TJclStackInfoList;
begin
List := JclLastExceptStackList;
Result := Assigned(List);
if Result then
List.AddToStrings(Strings, IncludeModuleName, IncludeAddressOffset, IncludeStartProcLineOffset,
IncludeVAdress);
end;
function JclGetExceptStackList(ThreadID: DWORD): TJclStackInfoList;
begin
Result := GlobalStackList.ExceptStackInfo[ThreadID];
end;
function JclGetExceptStackListToStrings(ThreadID: DWORD; Strings: TStrings;
IncludeModuleName: Boolean = False; IncludeAddressOffset: Boolean = False;
IncludeStartProcLineOffset: Boolean = False; IncludeVAdress: Boolean = False): Boolean;
var
List: TJclStackInfoList;
begin
List := JclGetExceptStackList(ThreadID);
Result := Assigned(List);
if Result then
List.AddToStrings(Strings, IncludeModuleName, IncludeAddressOffset, IncludeStartProcLineOffset,
IncludeVAdress);
end;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer): TJclStackInfoList;
begin
Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, False, nil, nil);
GlobalStackList.AddObject(Result);
end;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean): TJclStackInfoList;
begin
Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, nil, nil);
GlobalStackList.AddObject(Result);
end;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean; BaseOfStack: Pointer): TJclStackInfoList;
begin
Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, BaseOfStack, nil);
GlobalStackList.AddObject(Result);
end;
function JclCreateStackList(Raw: Boolean; AIgnoreLevels: DWORD; FirstCaller: Pointer;
DelayedTrace: Boolean; BaseOfStack, TopOfStack: Pointer): TJclStackInfoList;
begin
Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, BaseOfStack, TopOfStack);
GlobalStackList.AddObject(Result);
end;
function GetThreadFs(const Context: TContext; const Entry: TLDTEntry): DWORD;
// TODO: 64 bit version
var
FsBase: PNT_TIB32;
begin
FsBase := PNT_TIB32((DWord(Entry.BaseHi) shl 24) or (DWord(Entry.BaseMid) shl 16) or DWord(Entry.BaseLow));
Result := FsBase^.StackBase;
end;
function JclCreateThreadStackTrace(Raw: Boolean; const ThreadHandle: THandle): TJclStackInfoList;
var
C : CONTEXT;
Entry: TLDTEntry;
begin
Result := nil;
FillChar(C, SizeOf(C), 0);
FillChar(Entry, SizeOf(Entry), #0);
C.ContextFlags := CONTEXT_FULL;
if GetThreadContext(ThreadHandle, C)
and GetThreadSelectorEntry(ThreadHandle, C.SegFs, Entry) then
Result := JclCreateStackList(Raw, DWORD(-1), Pointer(C.Eip), False, Pointer(C.Ebp),
Pointer(GetThreadFs(C, Entry)));
end;
function JclCreateThreadStackTraceFromID(Raw: Boolean; ThreadID: DWORD): TJclStackInfoList;
type
TOpenThreadFunc = function(DesiredAccess: DWORD; InheritHandle: BOOL; ThreadID: DWORD): THandle; stdcall;
const
THREAD_GET_CONTEXT = $0008;
THREAD_QUERY_INFORMATION = $0040;
var
Kernel32Lib, ThreadHandle: THandle;
OpenThreadFunc: TOpenThreadFunc;
begin
Result := nil;
Kernel32Lib := GetModuleHandle(kernel32);
if Kernel32Lib <> 0 then
begin
// OpenThread only exists since Windows ME
OpenThreadFunc := GetProcAddress(Kernel32Lib, 'OpenThread');
if Assigned(OpenThreadFunc) then
begin
ThreadHandle := OpenThreadFunc(THREAD_GET_CONTEXT or THREAD_QUERY_INFORMATION, False, ThreadID);
if ThreadHandle <> 0 then
try
Result := JclCreateThreadStackTrace(Raw, ThreadHandle);
finally
CloseHandle(ThreadHandle);
end;
end;
end;
end;
//=== { TJclStackInfoItem } ==================================================
function TJclStackInfoItem.GetCallerAdr: Pointer;
begin
Result := Pointer(FStackInfo.CallerAdr);
end;
function TJclStackInfoItem.GetLogicalAddress: DWORD;
begin
Result := FStackInfo.CallerAdr - DWORD(ModuleFromAddr(CallerAdr));
end;
//=== { TJclStackInfoList } ==================================================
constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer);
begin
Create(ARaw, AIgnoreLevels, AFirstCaller, False, nil, nil);
end;
constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean);
begin
Create(ARaw, AIgnoreLevels, AFirstCaller, ADelayedTrace, nil, nil);
end;
constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack: Pointer);
begin
Create(ARaw, AIgnoreLevels, AFirstCaller, ADelayedTrace, ABaseOfStack, nil);
end;
constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: DWORD;
AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack, ATopOfStack: Pointer);
var
Item: TJclStackInfoItem;
begin
inherited Create;
FIgnoreLevels := AIgnoreLevels;
FDelayedTrace := ADelayedTrace;
FRaw := ARaw;
BaseOfStack := Cardinal(ABaseOfStack);
FStackOffset := 0;
FFrameEBP := ABaseOfStack;
if ATopOfStack = nil then
TopOfStack := GetStackTop
else
TopOfStack := Cardinal(ATopOfStack);
FModuleInfoList := GlobalModulesList.CreateModulesList;
if AFirstCaller <> nil then
begin
Item := TJclStackInfoItem.Create;
Item.FStackInfo.CallerAdr := DWORD(AFirstCaller);
Add(Item);
end;
if DelayedTrace then
DelayStoreStack
else
if Raw then
TraceStackRaw
else
TraceStackFrames;
end;
destructor TJclStackInfoList.Destroy;
begin
if Assigned(FStackData) then
FreeMem(FStackData);
GlobalModulesList.FreeModulesList(FModuleInfoList);
inherited Destroy;
end;
procedure TJclStackInfoList.ForceStackTracing;
begin
if DelayedTrace and Assigned(FStackData) and not FInStackTracing then
begin
FInStackTracing := True;
try
if Raw then
TraceStackRaw
else
TraceStackFrames;
if FCorrectOnAccess then
CorrectExceptStackListTop(Self, FSkipFirstItem);
finally
FInStackTracing := False;
FDelayedTrace := False;
end;
end;
end;
function TJclStackInfoList.GetCount: Integer;
begin
ForceStackTracing;
Result := inherited Count;
end;
procedure TJclStackInfoList.CorrectOnAccess(ASkipFirstItem: Boolean);
begin
FCorrectOnAccess := True;
FSkipFirstItem := ASkipFirstItem;
end;
procedure TJclStackInfoList.AddToStrings(Strings: TStrings; IncludeModuleName, IncludeAddressOffset,
IncludeStartProcLineOffset, IncludeVAdress: Boolean);
var
I: Integer;
begin
ForceStackTracing;
Strings.BeginUpdate;
try
for I := 0 to Count - 1 do
Strings.Add(GetLocationInfoStr(Items[I].CallerAdr, IncludeModuleName, IncludeAddressOffset,
IncludeStartProcLineOffset, IncludeVAdress));
finally
Strings.EndUpdate;
end;
end;
function TJclStackInfoList.GetItems(Index: Integer): TJclStackInfoItem;
begin
ForceStackTracing;
Result := TJclStackInfoItem(Get(Index));
end;
function TJclStackInfoList.NextStackFrame(var StackFrame: PStackFrame; var StackInfo: TStackInfo): Boolean;
var
CallInstructionSize: Cardinal;
StackFrameCallersEBP, NewEBP: Cardinal;
StackFrameCallerAdr: Cardinal;
begin
// Only report this stack frame into the StockInfo structure
// if the StackFrame pointer, EBP on the stack and return
// address on the stack are valid addresses
StackFrameCallersEBP := StackInfo.CallersEBP;
while ValidStackAddr(DWORD(StackFrame)) do
begin
// CallersEBP above the previous CallersEBP
NewEBP := StackFrame^.CallersEBP;
if NewEBP <= StackFrameCallersEBP then
Break;
StackFrameCallersEBP := NewEBP;
// CallerAdr within current process space, code segment etc.
// CallersEBP within current thread stack. Added Mar 12 2002 per Hallvard's suggestion
StackFrameCallerAdr := StackFrame^.CallerAdr;
if ValidCodeAddr(StackFrameCallerAdr, FModuleInfoList) and ValidStackAddr(StackFrameCallersEBP + FStackOffset) then
begin
Inc(StackInfo.Level);
StackInfo.StackFrame := StackFrame;
StackInfo.ParamPtr := PDWORDArray(DWORD(StackFrame) + SizeOf(TStackFrame));
if StackFrameCallersEBP > StackInfo.CallersEBP then
StackInfo.CallersEBP := StackFrameCallersEBP
else
// EBP points to an address that is below the last EBP, so it must be invalid
Break;
// Calculate the address of caller by subtracting the CALL instruction size (if possible)
if ValidCallSite(StackFrameCallerAdr, CallInstructionSize) then
StackInfo.CallerAdr := StackFrameCallerAdr - CallInstructionSize
else
StackInfo.CallerAdr := StackFrameCallerAdr;
StackInfo.DumpSize := StackFrameCallersEBP - DWORD(StackFrame);
StackInfo.ParamSize := (StackInfo.DumpSize - SizeOf(TStackFrame)) div 4;
if PStackFrame(StackFrame^.CallersEBP) = StackFrame then
Break;
// Step to the next stack frame by following the EBP pointer
StackFrame := PStackFrame(StackFrameCallersEBP + FStackOffset);
Result := True;
Exit;
end;
// Step to the next stack frame by following the EBP pointer
StackFrame := PStackFrame(StackFrameCallersEBP + FStackOffset);
end;
Result := False;
end;
procedure TJclStackInfoList.StoreToList(const StackInfo: TStackInfo);
var
Item: TJclStackInfoItem;
begin
if StackInfo.Level > IgnoreLevels + 1 then
begin
Item := TJclStackInfoItem.Create;
Item.FStackInfo := StackInfo;
Add(Item);
end;
end;
procedure TJclStackInfoList.TraceStackFrames;
var
StackFrame: PStackFrame;
StackInfo: TStackInfo;
begin
Capacity := 32; // reduce ReallocMem calls, must be > 1 because the caller's EIP register is already in the list
// Start at level 0
StackInfo.Level := 0;
StackInfo.CallersEBP := 0;
if DelayedTrace then
// Get the current stack frame from the EBP register
StackFrame := FFrameEBP
else
begin
// We define the bottom of the valid stack to be the current ESP pointer
if BaseOfStack = 0 then
BaseOfStack := DWORD(GetEBP);
// Get a pointer to the current bottom of the stack
StackFrame := PStackFrame(BaseOfStack);
end;
// We define the bottom of the valid stack to be the current EBP Pointer
// There is a TIB field called pvStackUserBase, but this includes more of the
// stack than what would define valid stack frames.
BaseOfStack := DWORD(StackFrame) - 1;
// Loop over and report all valid stackframes
while NextStackFrame(StackFrame, StackInfo) and (inherited Count <> MaxStackTraceItems) do
StoreToList(StackInfo);
end;
function SearchForStackPtrManipulation(StackPtr: Pointer; Proc: Pointer): Pointer;
{$IFDEF SUPPORTS_INLINE}
inline;
{$ENDIF SUPPORTS_INLINE}
{var
Addr: PByteArray;}
begin
{ Addr := Proc;
while (Addr <> nil) and (Cardinal(Addr) > Cardinal(Proc) - $100) and not IsBadReadPtr(Addr, 6) do
begin
if (Addr[0] = $55) and // push ebp
(Addr[1] = $8B) and (Addr[2] = $EC) then // mov ebp,esp
begin
if (Addr[3] = $83) and (Addr[4] = $C4) then // add esp,c8
begin
Result := Pointer(Integer(StackPtr) - ShortInt(Addr[5]));
Exit;
end;
Break;
end;
if (Addr[0] = $C2) and // ret $xxxx
(((Addr[3] = $90) and (Addr[4] = $90) and (Addr[5] = $90)) or // nop
((Addr[3] = $CC) and (Addr[4] = $CC) and (Addr[5] = $CC))) then // int 3
Break;
if (Addr[0] = $C3) and // ret
(((Addr[1] = $90) and (Addr[2] = $90) and (Addr[3] = $90)) or // nop
((Addr[1] = $CC) and (Addr[2] = $CC) and (Addr[3] = $CC))) then // int 3
Break;
if (Addr[0] = $E9) and // jmp rel-far
(((Addr[5] = $90) and (Addr[6] = $90) and (Addr[7] = $90)) or // nop
((Addr[5] = $CC) and (Addr[6] = $CC) and (Addr[7] = $CC))) then // int 3
Break;
if (Addr[0] = $EB) and // jmp rel-near
(((Addr[2] = $90) and (Addr[3] = $90) and (Addr[4] = $90)) or // nop
((Addr[2] = $CC) and (Addr[3] = $CC) and (Addr[4] = $CC))) then // int 3
Break;
Dec(Cardinal(Addr));
end;}
Result := StackPtr;
end;
procedure TJclStackInfoList.TraceStackRaw;
var
StackInfo: TStackInfo;
StackPtr: PDWORD;
PrevCaller: DWORD;
CallInstructionSize: Cardinal;
StackTop: DWORD;
begin
Capacity := 32; // reduce ReallocMem calls, must be > 1 because the caller's EIP register is already in the list
if DelayedTrace then
begin
if not Assigned(FStackData) then
Exit;
StackPtr := PDWORD(FStackData);
end
else
begin
// We define the bottom of the valid stack to be the current ESP pointer
if BaseOfStack = 0 then
BaseOfStack := DWORD(GetESP);
// Get a pointer to the current bottom of the stack
StackPtr := PDWORD(BaseOfStack);
end;
StackTop := TopOfStack;
if Count > 0 then
StackPtr := SearchForStackPtrManipulation(StackPtr, Pointer(Items[0].StackInfo.CallerAdr));
// We will not be able to fill in all the fields in the StackInfo record,
// so just blank it all out first
FillChar(StackInfo, SizeOf(StackInfo), 0);
// Clear the previous call address
PrevCaller := 0;
// Loop through all of the valid stack space
while (DWORD(StackPtr) < StackTop) and (inherited Count <> MaxStackTraceItems) do
begin
// If the current DWORD on the stack refers to a valid call site...
if ValidCallSite(StackPtr^, CallInstructionSize) and (StackPtr^ <> PrevCaller) then
begin
// then pick up the callers address
StackInfo.CallerAdr := StackPtr^ - CallInstructionSize;
// remember to callers address so that we don't report it repeatedly
PrevCaller := StackPtr^;
// increase the stack level
Inc(StackInfo.Level);
// then report it back to our caller
StoreToList(StackInfo);
StackPtr := SearchForStackPtrManipulation(StackPtr, Pointer(StackInfo.CallerAdr));
end;
// Look at the next DWORD on the stack
Inc(StackPtr);
end;
if Assigned(FStackData) then
begin
FreeMem(FStackData);
FStackData := nil;
end;
end;
procedure TJclStackInfoList.DelayStoreStack;
var
StackPtr: PDWORD;
StackDataSize: Cardinal;
begin
if Assigned(FStackData) then
begin
FreeMem(FStackData);
FStackData := nil;
end;
// We define the bottom of the valid stack to be the current ESP pointer
if BaseOfStack = 0 then
begin
BaseOfStack := DWORD(GetESP);
FFrameEBP := GetEBP;
end;
// Get a pointer to the current bottom of the stack
StackPtr := PDWORD(BaseOfStack);
if Cardinal(StackPtr) < TopOfStack then
begin
StackDataSize := TopOfStack - Cardinal(StackPtr);
GetMem(FStackData, StackDataSize);
System.Move(StackPtr^, FStackData^, StackDataSize);
//CopyMemory(FStackData, StackPtr, StackDataSize);
end;
FStackOffset := DWORD(FStackData) - DWORD(StackPtr);
FFrameEBP := Pointer(Cardinal(FFrameEBP) + FStackOffset);
TopOfStack := TopOfStack + FStackOffset;
end;
// Validate that the code address is a valid code site
//
// Information from Intel Manual 24319102(2).pdf, Download the 6.5 MBs from:
// http://developer.intel.com/design/pentiumii/manuals/243191.htm
// Instruction format, Chapter 2 and The CALL instruction: page 3-53, 3-54
function TJclStackInfoList.ValidCallSite(CodeAddr: DWORD; var CallInstructionSize: Cardinal): Boolean;
var
CodeDWORD4: DWORD;
CodeDWORD8: DWORD;
C4P, C8P: PDWORD;
begin
// First check that the address is within range of our code segment!
C8P := PDWORD(CodeAddr - 8);
C4P := PDWORD(CodeAddr - 4);
Result := (CodeAddr > 8) and ValidCodeAddr(DWORD(C8P), FModuleInfoList) and not IsBadReadPtr(C8P, 8);
// Now check to see if the instruction preceding the return address
// could be a valid CALL instruction
if Result then
begin
try
CodeDWORD8 := PDWORD(C8P)^;
CodeDWORD4 := PDWORD(C4P)^;
// Check the instruction prior to the potential call site.
// We consider it a valid call site if we find a CALL instruction there
// Check the most common CALL variants first
if ((CodeDWORD8 and $FF000000) = $E8000000) then // 5-byte, CALL [-$1234567]
CallInstructionSize := 5
else
if ((CodeDWORD4 and $38FF0000) = $10FF0000) then // 2 byte, CALL EAX
CallInstructionSize := 2
else
if ((CodeDWORD4 and $0038FF00) = $0010FF00) then // 3 byte, CALL [EBP+0x8]
CallInstructionSize := 3
else
if ((CodeDWORD4 and $000038FF) = $000010FF) then // 4 byte, CALL ??
CallInstructionSize := 4
else
if ((CodeDWORD8 and $38FF0000) = $10FF0000) then // 6-byte, CALL ??
CallInstructionSize := 6
else
if ((CodeDWORD8 and $0038FF00) = $0010FF00) then // 7-byte, CALL [ESP-0x1234567]
CallInstructionSize := 7
else
Result := False;
// Because we're not doing a complete disassembly, we will potentially report
// false positives. If there is odd code that uses the CALL 16:32 format, we
// can also get false negatives.}
except
Result := False;
end;
end;
end;
{$IFNDEF STACKFRAMES_ON}
{$STACKFRAMES OFF}
{$ENDIF ~STACKFRAMES_ON}
function TJclStackInfoList.ValidStackAddr(StackAddr: DWORD): Boolean;
begin
Result := (BaseOfStack < StackAddr) and (StackAddr < TopOfStack);
end;
//=== Exception frame info routines ==========================================
function JclCreateExceptFrameList(AIgnoreLevels: Integer): TJclExceptFrameList;
begin
Result := TJclExceptFrameList.Create(AIgnoreLevels);
GlobalStackList.AddObject(Result);
end;
function JclLastExceptFrameList: TJclExceptFrameList;
begin
Result := GlobalStackList.LastExceptFrameList[GetCurrentThreadID];
end;
function JclGetExceptFrameList(ThreadID: DWORD): TJclExceptFrameList;
begin
Result := GlobalStackList.LastExceptFrameList[ThreadID];
end;
procedure DoExceptFrameTrace;
begin
// Ignore first 2 levels; the First level is an undefined frame (I haven't a
// clue as to where it comes from. The second level is the try..finally block
// in DoExceptNotify.
JclCreateExceptFrameList(4);
end;
function GetJmpDest(Jmp: PJmpInstruction): DWORD;
begin
if Jmp.opCode = $E9 then
Result := Longint(Jmp) + Jmp.distance + 5
else
if Jmp.opCode = $EB then
Result := Longint(Jmp) + ShortInt(jmp.distance) + 2
else
Result := 0;
if (Result <> 0) and (PJmpTable(Result).OPCode = $25FF) then
if not IsBadReadPtr(PJmpTable(Result).Ptr, 4) then
Result := PDWORD(PJmpTable(Result).Ptr)^;
end;
//=== { TJclExceptFrame } ====================================================
constructor TJclExceptFrame.Create(AExcFrame: PExcFrame);
begin
inherited Create;
FExcFrame := AExcFrame;
DoDetermineFrameKind;
end;
procedure TJclExceptFrame.DoDetermineFrameKind;
var
Dest: Longint;
LocInfo: TJclLocationInfo;
begin
FFrameKind := efkUnknown;
if FExcFrame <> nil then
begin
Dest := GetJmpDest(@ExcFrame.desc.Jmp);
if Dest <> 0 then
begin
LocInfo := GetLocationInfo(Pointer(Dest));
if CompareText(LocInfo.UnitName, 'system') = 0 then
begin
if CompareText(LocInfo.ProcedureName, '@HandleAnyException') = 0 then
FFrameKind := efkAnyException
else
if CompareText(LocInfo.ProcedureName, '@HandleOnException') = 0 then
FFrameKind := efkOnException
else
if CompareText(LocInfo.ProcedureName, '@HandleAutoException') = 0 then
FFrameKind := efkAutoException
else
if CompareText(LocInfo.ProcedureName, '@HandleFinally') = 0 then
FFrameKind := efkFinally;
end;
end;
end;
end;
function TJclExceptFrame.Handles(ExceptObj: TObject): Boolean;
var
Handler: Pointer;
begin
Result := HandlerInfo(ExceptObj, Handler);
end;
function TJclExceptFrame.HandlerInfo(ExceptObj: TObject; var HandlerAt: Pointer): Boolean;
var
I: Integer;
VTable: Pointer;
begin
Result := FrameKind in [efkAnyException, efkAutoException];
if not Result and (FrameKind = efkOnException) then
begin
I := 0;
VTable := Pointer(Integer(ExceptObj.ClassType) + vmtSelfPtr);
while (I < ExcFrame.Desc.Cnt) and not Result and (VTable <> nil) do
begin
Result := (ExcFrame.Desc.ExcTab[I].VTable = nil) or
(ExcFrame.Desc.ExcTab[I].VTable = VTable);
if not Result then
begin
Move(PChar(VTable)[vmtParent - vmtSelfPtr], VTable, 4);
if VTable = nil then
begin
VTable := Pointer(Integer(ExceptObj.ClassType) + vmtSelfPtr);
Inc(I);
end;
end;
end;
if Result then
HandlerAt := ExcFrame.Desc.ExcTab[I].Handler;
end
else
if Result then
begin
HandlerAt := Pointer(GetJmpDest(@ExcFrame.Desc.Instructions));
if HandlerAt = nil then
HandlerAt := @ExcFrame.Desc.Instructions;
end
else
HandlerAt := nil;
end;
function TJclExceptFrame.CodeLocation: Pointer;
begin
if FrameKind <> efkUnknown then
begin
Result := Pointer(GetJmpDest(PJmpInstruction(DWORD(@ExcFrame.Desc.Instructions))));
if Result = nil then
Result := @ExcFrame.Desc.Instructions;
end
else
begin
Result := Pointer(GetJmpDest(PJmpInstruction(DWORD(@ExcFrame.Desc))));
if Result = nil then
Result := @ExcFrame.Desc;
end;
end;
//=== { TJclExceptFrameList } ================================================
constructor TJclExceptFrameList.Create(AIgnoreLevels: Integer);
begin
inherited Create;
FIgnoreLevels := AIgnoreLevels;
TraceExceptionFrames;
end;
function TJclExceptFrameList.AddFrame(AFrame: PExcFrame): TJclExceptFrame;
begin
Result := TJclExceptFrame.Create(AFrame);
Add(Result);
end;
function TJclExceptFrameList.GetItems(Index: Integer): TJclExceptFrame;
begin
Result := TJclExceptFrame(Get(Index));
end;
procedure TJclExceptFrameList.TraceExceptionFrames;
var
FS: PExcFrame;
Level: Integer;
ModulesList: TJclModuleInfoList;
begin
Clear;
ModulesList := GlobalModulesList.CreateModulesList;
try
Level := 0;
FS := GetFS;
while Longint(FS) <> -1 do
begin
if (Level >= IgnoreLevels) and ValidCodeAddr(DWORD(FS.Desc), ModulesList) then
AddFrame(FS);
Inc(Level);
FS := FS.next;
end;
finally
GlobalModulesList.FreeModulesList(ModulesList);
end;
end;
//=== Exception hooking ======================================================
var
TrackingActive: Boolean;
IgnoredExceptions: TThreadList = nil;
procedure AddIgnoredException(const ExceptionClass: TClass);
begin
if Assigned(ExceptionClass) then
begin
if not Assigned(IgnoredExceptions) then
IgnoredExceptions := TThreadList.Create;
IgnoredExceptions.Add(ExceptionClass);
end;
end;
procedure RemoveIgnoredException(const ExceptionClass: TClass);
var
ClassList: TList;
begin
if Assigned(ExceptionClass) and Assigned(IgnoredExceptions) then
begin
ClassList := IgnoredExceptions.LockList;
try
ClassList.Remove(ExceptionClass);
finally
IgnoredExceptions.UnlockList;
end;
end;
end;
function IsIgnoredException(const ExceptionClass: TClass): Boolean;
var
ClassList: TList;
Index: Integer;
begin
Result := False;
if Assigned(IgnoredExceptions) and not (stTraceAllExceptions in JclStackTrackingOptions) then
begin
ClassList := IgnoredExceptions.LockList;
try
for Index := 0 to ClassList.Count - 1 do
if ExceptionClass.InheritsFrom(TClass(ClassList.Items[Index])) then
begin
Result := True;
Break;
end;
finally
IgnoredExceptions.UnlockList;
end;
end;
end;
procedure DoExceptNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean;
BaseOfStack: Pointer);
begin
if TrackingActive and Assigned(ExceptObj) and (not IsIgnoredException(ExceptObj.ClassType)) and
(not (stMainThreadOnly in JclStackTrackingOptions) or (GetCurrentThreadId = MainThreadID)) then
begin
if stStack in JclStackTrackingOptions then
DoExceptionStackTrace(ExceptObj, ExceptAddr, OSException, BaseOfStack);
if stExceptFrame in JclStackTrackingOptions then
DoExceptFrameTrace;
end;
end;
function JclStartExceptionTracking: Boolean;
begin
if TrackingActive then
Result := False
else
begin
Result := JclHookExceptions and JclAddExceptNotifier(DoExceptNotify, npFirstChain);
TrackingActive := Result;
end;
end;
function JclStopExceptionTracking: Boolean;
begin
if TrackingActive then
begin
Result := JclRemoveExceptNotifier(DoExceptNotify);
TrackingActive := False;
end
else
Result := False;
end;
function JclExceptionTrackingActive: Boolean;
begin
Result := TrackingActive;
end;
function JclTrackExceptionsFromLibraries: Boolean;
begin
Result := TrackingActive;
if Result then
JclInitializeLibrariesHookExcept;
end;
//=== Thread exception tracking support ======================================
var
RegisteredThreadList: TJclDebugThreadList;
function JclDebugThreadList: TJclDebugThreadList;
begin
if RegisteredThreadList = nil then
RegisteredThreadList := TJclDebugThreadList.Create;
Result := RegisteredThreadList;
end;
//=== { TJclDebugThread } ====================================================
constructor TJclDebugThread.Create(Suspended: Boolean; const AThreadName: string);
begin
FThreadName := AThreadName;
inherited Create(True);
JclDebugThreadList.RegisterThread(Self, AThreadName);
if not Suspended then
Resume;
end;
destructor TJclDebugThread.Destroy;
begin
JclDebugThreadList.UnregisterThread(Self);
inherited Destroy;
end;
procedure TJclDebugThread.DoHandleException;
begin
GlobalStackList.LockThreadID(ThreadID);
try
DoSyncHandleException;
finally
GlobalStackList.UnlockThreadID;
end;
end;
procedure TJclDebugThread.DoNotify;
begin
JclDebugThreadList.DoSyncException(Self);
end;
procedure TJclDebugThread.DoSyncHandleException;
begin
// Note: JclLastExceptStackList and JclLastExceptFrameList returns information
// for this Thread ID instead of MainThread ID here to allow use a common
// exception handling routine easily.
// Any other call of those JclLastXXX routines from another thread at the same
// time will return expected information for current Thread ID.
DoNotify;
end;
function TJclDebugThread.GetThreadInfo: string;
begin
Result := JclDebugThreadList.ThreadInfos[ThreadID];
end;
procedure TJclDebugThread.HandleException(Sender: TObject);
begin
FSyncException := Sender;
try
if not Assigned(FSyncException) then
FSyncException := Exception(ExceptObject);
if Assigned(FSyncException) and not IsIgnoredException(FSyncException.ClassType) then
Synchronize(DoHandleException);
finally
FSyncException := nil;
end;
end;
//=== { TJclDebugThreadList } ================================================
type
TThreadAccess = class(TThread);
TThreadListRec = record
ThreadID: DWORD;
ThreadHandle: THandle;
end;
PThreadListRec = ^TThreadListRec;
constructor TJclDebugThreadList.Create;
begin
FLock := TJclCriticalSection.Create;
FReadLock := TJclCriticalSection.Create;
FList := TStringList.Create;
end;
destructor TJclDebugThreadList.Destroy;
var
I: Integer;
ThreadRec: PThreadListRec;
begin
if Assigned(FList) then
begin
for I := FList.Count - 1 downto 0 do
begin
ThreadRec := PThreadListRec(FList.Objects[I]);
Dispose(ThreadRec);
end;
end;
FreeAndNil(FList);
FreeAndNil(FLock);
FreeAndNil(FReadLock);
inherited Destroy;
end;
procedure TJclDebugThreadList.DoSyncException(Thread: TJclDebugThread);
begin
if Assigned(FOnSyncException) then
FOnSyncException(Thread);
end;
procedure TJclDebugThreadList.DoSyncThreadRegistered;
begin
if Assigned(FOnThreadRegistered) then
FOnThreadRegistered(FRegSyncThreadID);
end;
procedure TJclDebugThreadList.DoSyncThreadUnregistered;
begin
if Assigned(FOnThreadUnregistered) then
FOnThreadUnregistered(FUnregSyncThreadID);
end;
procedure TJclDebugThreadList.DoThreadRegistered(Thread: TThread);
begin
if Assigned(FOnThreadRegistered) then
begin
FRegSyncThreadID := Thread.ThreadID;
TThreadAccess(Thread).Synchronize(DoSyncThreadRegistered);
end;
end;
procedure TJclDebugThreadList.DoThreadUnregistered(Thread: TThread);
begin
if Assigned(FOnThreadUnregistered) then
begin
FUnregSyncThreadID := Thread.ThreadID;
TThreadAccess(Thread).Synchronize(DoSyncThreadUnregistered);
end;
end;
function TJclDebugThreadList.GetThreadClassNames(ThreadID: DWORD): string;
begin
Result := GetThreadValues(ThreadID, 1);
end;
function TJclDebugThreadList.GetThreadIDCount: Integer;
begin
FReadLock.Enter;
try
Result := FList.Count;
finally
FReadLock.Leave;
end;
end;
function TJclDebugThreadList.GetThreadHandle(Index: Integer): DWORD;
begin
FReadLock.Enter;
try
Result := PThreadListRec(FList.Objects[Index])^.ThreadHandle;
finally
FReadLock.Leave;
end;
end;
function TJclDebugThreadList.GetThreadID(Index: Integer): DWORD;
begin
FReadLock.Enter;
try
Result := PThreadListRec(FList.Objects[Index])^.ThreadID;
finally
FReadLock.Leave;
end;
end;
function TJclDebugThreadList.GetThreadInfos(ThreadID: DWORD): string;
begin
Result := GetThreadValues(ThreadID, 2);
end;
function TJclDebugThreadList.GetThreadNames(ThreadID: DWORD): string;
begin
Result := GetThreadValues(ThreadID, 0);
end;
function TJclDebugThreadList.GetThreadValues(ThreadID: DWORD; Index: Integer): string;
var
I: Integer;
function ThreadName: string;
begin
Result := FList.Strings[I];
Delete(Result, 1, Pos('=', Result));
end;
begin
FReadLock.Enter;
try
I := IndexOfThreadID(ThreadID);
if I <> -1 then
begin
case Index of
0:
Result := ThreadName;
1:
Result := FList.Names[I];
2:
Result := Format('%.8x [%s] "%s"', [ThreadID, FList.Names[I], ThreadName]);
end;
end
else
Result := '';
finally
FReadLock.Leave;
end;
end;
function TJclDebugThreadList.IndexOfThreadID(ThreadID: DWORD): Integer;
var
I: Integer;
ThreadRec: PThreadListRec;
begin
Result := -1;
for I := FList.Count - 1 downto 0 do
begin
ThreadRec := PThreadListRec(FList.Objects[I]);
if ThreadRec^.ThreadID = ThreadID then
begin
Result := I;
Break;
end;
end;
end;
procedure TJclDebugThreadList.InternalRegisterThread(Thread: TThread; const ThreadName: string);
var
I: Integer;
ThreadRec: PThreadListRec;
function FormatInternalName: string;
begin
Result := Format('%s=%s', [Thread.ClassName, ThreadName]);
end;
begin
FLock.Enter;
try
I := IndexOfThreadID(Thread.ThreadID);
if I = -1 then
begin
FReadLock.Enter;
try
New(ThreadRec);
ThreadRec^.ThreadID := Thread.ThreadID;
ThreadRec^.ThreadHandle := Thread.Handle;
FList.AddObject(FormatInternalName, TObject(ThreadRec));
finally
FReadLock.Leave;
end;
DoThreadRegistered(Thread);
end;
finally
FLock.Leave;
end;
end;
procedure TJclDebugThreadList.InternalUnregisterThread(Thread: TThread);
var
I: Integer;
ThreadRec: PThreadListRec;
begin
FLock.Enter;
try
I := IndexOfThreadID(Thread.ThreadID);
if I <> -1 then
begin
DoThreadUnregistered(Thread);
FReadLock.Enter;
try
ThreadRec := PThreadListRec(FList.Objects[I]);
Dispose(ThreadRec);
FList.Delete(I);
finally
FReadLock.Leave;
end;
end;
finally
FLock.Leave;
end;
end;
procedure TJclDebugThreadList.RegisterThread(Thread: TThread; const ThreadName: string);
begin
InternalRegisterThread(Thread, ThreadName);
end;
procedure TJclDebugThreadList.UnregisterThread(Thread: TThread);
begin
InternalUnregisterThread(Thread);
end;
//== Miscellanuous ===========================================================
{$IFDEF MSWINDOWS}
function EnableCrashOnCtrlScroll(const Enable: Boolean): Boolean;
const
CrashCtrlScrollKey = 'SYSTEM\CurrentControlSet\Services\i8042prt\Parameters';
CrashCtrlScrollName = 'CrashOnCtrlScroll';
var
Enabled: Integer;
begin
Enabled := 0;
if Enable then
Enabled := 1;
RegWriteInteger(HKEY_LOCAL_MACHINE, CrashCtrlScrollKey, CrashCtrlScrollName, Enabled);
Result := RegReadInteger(HKEY_LOCAL_MACHINE, CrashCtrlScrollKey, CrashCtrlScrollName) = Enabled;
end;
function IsDebuggerAttached: Boolean;
var
IsDebuggerPresent: function: Boolean; stdcall;
KernelHandle: THandle;
P: Pointer;
begin
KernelHandle := GetModuleHandle(kernel32);
@IsDebuggerPresent := GetProcAddress(KernelHandle, 'IsDebuggerPresent');
if @IsDebuggerPresent <> nil then
begin
// Win98+ / NT4+
Result := IsDebuggerPresent
end
else
begin
// Win9x uses thunk pointer outside the module when under a debugger
P := GetProcAddress(KernelHandle, 'GetProcAddress');
Result := DWORD(P) < KernelHandle;
end;
end;
function IsHandleValid(Handle: THandle): Boolean;
var
Duplicate: THandle;
Flags: DWORD;
begin
if IsWinNT then
Result := GetHandleInformation(Handle, Flags)
else
Result := False;
if not Result then
begin
// DuplicateHandle is used as an additional check for those object types not
// supported by GetHandleInformation (e.g. according to the documentation,
// GetHandleInformation doesn't support window stations and desktop although
// tests show that it does). GetHandleInformation is tried first because its
// much faster. Additionally GetHandleInformation is only supported on NT...
Result := DuplicateHandle(GetCurrentProcess, Handle, GetCurrentProcess,
@Duplicate, 0, False, DUPLICATE_SAME_ACCESS);
if Result then
Result := CloseHandle(Duplicate);
end;
end;
{$ENDIF MSWINDOWS}
initialization
DebugInfoCritSect := TJclCriticalSection.Create;
GlobalModulesList := TJclGlobalModulesList.Create;
GlobalStackList := TJclGlobalStackList.Create;
AddIgnoredException(EAbort);
{$IFDEF UNITVERSIONING}
RegisterUnitVersion(HInstance, UnitVersioning);
{$ENDIF UNITVERSIONING}
finalization
{$IFDEF UNITVERSIONING}
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
{ TODO -oPV -cInvestigate : Calling JclStopExceptionTracking causes linking of various classes to
the code without a real need. Although there doesn't seem to be a way to unhook exceptions
safely because we need to be covered by JclHookExcept.Notifiers critical section }
JclStopExceptionTracking;
FreeAndNil(RegisteredThreadList);
FreeAndNil(DebugInfoList);
FreeAndNil(GlobalStackList);
FreeAndNil(GlobalModulesList);
FreeAndNil(DebugInfoCritSect);
FreeAndNil(InfoSourceClassList);
FreeAndNil(IgnoredExceptions);
TJclDebugInfoSymbols.CleanupDebugSymbols;
end.