lazarus/components/fpdebug/fpimgreaderbase.pas

595 lines
17 KiB
ObjectPascal

unit FpImgReaderBase;
{$mode objfpc}{$H+}
{$IFDEF INLINE_OFF}{$INLINE OFF}{$ENDIF}
{$modeswitch advancedrecords}
interface
{$ifdef CD_Cocoa}{$DEFINE MacOS}{$ENDIF}
{$IFDEF Darwin}{$DEFINE MacOS}{$ENDIF}
uses
{$ifdef windows}
Windows, // After LCLType
{$endif}
fgl, LazFglHash, LazFileUtils, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif},
fpDbgSymTable,
Classes, SysUtils, DbgIntfBaseTypes, contnrs,
FpDbgCommon, crc;
type
TDbgImageSection = record
RawData: Pointer;
Size: QWord;
VirtualAddress: QWord;
// Use this flag to identify sections that should be uploaded via RSP
// This is probably only relevant for uploads to low level targets (embedded, FreeRTOS...)
IsLoadable: Boolean;
Name: String;
end;
PDbgImageSection = ^TDbgImageSection;
TDbgImageSectionEx = record
Sect: TDbgImageSection;
Offs: QWord;
Loaded: Boolean;
end;
PDbgImageSectionEx = ^TDbgImageSectionEx;
type
TDbgAddressMap = record
OrgAddr: QWord;
Length: QWord;
NewAddr: QWord;
class operator =(r1,r2: TDbgAddressMap) : boolean;
end;
PDbgAddressMap = ^TDbgAddressMap;
TDbgAddressMapList = specialize TFPGList<TDbgAddressMap>;
{ TDbgAddressMapHashList }
TDbgAddressMapHashList = class(specialize TLazFPGHashTable<TDbgAddressMap>)
public
function ItemFromNode(ANode: THTCustomNode): TDbgAddressMap;
function ItemPointerFromNode(ANode: THTCustomNode): PDbgAddressMap;
end;
{ TDbgAddressMapPointerHashList }
TDbgAddressMapPointerHashList = class(specialize TLazFPGHashTable<PDbgAddressMap>)
public
function ItemPointerFromNode(ANode: THTCustomNode): PDbgAddressMap;
end;
{ TDbgFileLoader }
{$ifdef windows}
{$define USE_WIN_FILE_MAPPING}
{$endif}
TDbgFileLoader = class(TObject)
private
{$ifdef USE_WIN_FILE_MAPPING}
FFileHandle: THandle;
FMapHandle: THandle;
FModulePtr: Pointer;
{$endif}
FStream: TStream;
FList: TList;
FFileName: String;
function GetFileName: String;
procedure OpenStream;
public
constructor Create(AFileName: String);
{$ifdef USE_WIN_FILE_MAPPING}
constructor Create(AFileHandle: THandle);
{$endif}
destructor Destroy; override;
procedure Close;
function Read(AOffset, ASize: QWord; AMem: Pointer): QWord;
function LoadMemory(AOffset, ASize: QWord; out AMem: Pointer): QWord;
procedure UnloadMemory({%H-}AMem: Pointer);
property FileName: String read GetFileName;
end;
{ TDbgImageReader }
TDbgImageReader = class(TObject) // executable parser
private
FImageBase: QWord;
FImageSize: QWord;
FRelocationOffset: QWord;
FLoadedTargetImageAddr: TDBGPtr;
FReaderErrors: String;
FUUID: TGuid;
protected
FTargetInfo: TTargetDescriptor;
function GetSubFiles: TStrings; virtual;
function GetAddressMapList: TDbgAddressMapList; virtual;
function GetSection(const AName: String): PDbgImageSection; virtual; abstract;
function GetSection(const ID: integer): PDbgImageSection; virtual; abstract;
procedure SetUUID(AGuid: TGuid);
procedure SetImageBase(ABase: QWord);
procedure SetImageSize(ASize: QWord);
procedure SetRelocationOffset(AnOffset: QWord);
procedure AddReaderError(AnError: String);
function ReadGnuDebugLinkSection(out AFileName: String; out ACrc: Cardinal): Boolean;
function LoadGnuDebugLink(ASearchPath, AFileName: String; ACrc: Cardinal): TDbgFileLoader;
property LoadedTargetImageAddr: TDBGPtr read FLoadedTargetImageAddr;
public
class function isValid(ASource: TDbgFileLoader): Boolean; virtual; abstract;
class function UserName: AnsiString; virtual; abstract;
procedure ParseSymbolTable(AFpSymbolInfo: TfpSymbolList); virtual;
procedure ParseLibrarySymbolTable(AFpSymbolInfo: TfpSymbolList); virtual;
// The LoadedTargetImageAddr is the start-address of the data of the binary
// once loaded into memory.
// On Linux it is 0 for executables, and had another value for libraries
// which are relocated into another position.
// On Windows it is the same as the ImageBase, except when the binary has
// been relocated. This only happens if two libraries with the same ImageBase
// are loaded. In that case, one of them is being relocated to another
// LoadedTargetImageAddr.
constructor Create({%H-}ASource: TDbgFileLoader; {%H-}ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); virtual;
procedure AddSubFilesToLoaderList(ALoaderList: TObject; PrimaryLoader: TObject); virtual;
function EnclosesAddressRange(AStartAddress, AnEndAddress: TDBGPtr): Boolean; virtual;
// The ImageBase is the address at which the linker assumed the binary will be
// loaded at. So it is stored inside the binary itself and all addresses inside
// the binary assume that once loaded into memory, it is loaded at this
// address. (Which is not the case for relocated libraries, see the comments
// for the LoadedTargetImageAddr)
// On Linux it is always 0, on Windows it is set by the linker/compiler.
property ImageBase: QWord read FImageBase;
property ImageSize: QWord read FImageSize;
// The RelocationOffset is the offset between the addresses as they are encoded
// into the binary, and their real addresses once loaded in memory.
// On linux it is equal to the LoadedTargetImageAddr.
// On Windows it is 0, except for libraries which are re-located. In that
// case the offset is LoadedTargetImageAddr-ImageBase.
property RelocationOffset: QWord read FRelocationOffset;
property TargetInfo: TTargetDescriptor read FTargetInfo;
property UUID: TGuid read FUUID;
property Section[const AName: String]: PDbgImageSection read GetSection;
property SectionByID[const ID: integer]: PDbgImageSection read GetSection;
property SubFiles: TStrings read GetSubFiles;
property AddressMapList: TDbgAddressMapList read GetAddressMapList;
property ReaderErrors: String read FReaderErrors;
end;
TDbgImageReaderClass = class of TDbgImageReader;
function GetImageReader(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean): TDbgImageReader; overload;
procedure RegisterImageReaderClass(DataSource: TDbgImageReaderClass);
implementation
var
DBG_WARNINGS: PLazLoggerLogGroup;
const
// Symbol-map section name
_gnu_dbg_link = '.gnu_debuglink';
var
RegisteredImageReaderClasses : TFPList;
{ TDbgAddressMapPointerHashList }
function TDbgAddressMapPointerHashList.ItemPointerFromNode(ANode: THTCustomNode
): PDbgAddressMap;
begin
Result := THTGNode(ANode).Data;
end;
{ TDbgAddressMapHashList }
function TDbgAddressMapHashList.ItemFromNode(ANode: THTCustomNode
): TDbgAddressMap;
begin
Result := THTGNode(ANode).Data;
end;
function TDbgAddressMapHashList.ItemPointerFromNode(ANode: THTCustomNode
): PDbgAddressMap;
begin
Result := @THTGNode(ANode).Data;
end;
class operator TDbgAddressMap.=(r1,r2: TDbgAddressMap) : boolean;
begin
result := (r1.OrgAddr=r2.OrgAddr) and (r1.Length=r2.Length) and (r1.NewAddr=r2.NewAddr);
end;
function GetImageReader(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean): TDbgImageReader;
var
i : Integer;
cls : TDbgImageReaderClass;
begin
Result := nil;
if not Assigned(ASource) then Exit;
for i := 0 to RegisteredImageReaderClasses.Count - 1 do begin
cls := TDbgImageReaderClass(RegisteredImageReaderClasses[i]);
try
if cls.isValid(ASource) then begin
Result := cls.Create(ASource, ADebugMap, ALoadedTargetImageAddr, OwnSource);
ASource.Close;
Exit;
end
else
;
except
on e: exception do begin
//writeln('exception! WHY? ', e.Message);
end;
end;
end;
ASource.Close;
Result := nil;
end;
procedure RegisterImageReaderClass( DataSource: TDbgImageReaderClass);
begin
if Assigned(DataSource) and (RegisteredImageReaderClasses.IndexOf(DataSource) < 0) then
RegisteredImageReaderClasses.Add(DataSource)
end;
{$ifdef USE_WIN_FILE_MAPPING}
type
TGetFinalPathNameByHandle = function(hFile: THandle; lpszFilePath: PWideChar; cchFilePath, dwFlags: DWORD): DWORD; stdcall;
var
GetFinalPathNameByHandle: TGetFinalPathNameByHandle = nil;
function GetFinalPathNameByHandleDummy(hFile: THandle; lpszFilePath: PWideChar; cchFilePath, dwFlags: DWORD): DWORD; stdcall;
begin
Result := 0;
end;
function FileHandleToFileName(Handle : THandle): string;
var
U: WideString;
hmod: HMODULE;
begin
if not Assigned(GetFinalPathNameByHandle) then
begin
hmod := GetModuleHandle('kernel32');
if hmod <> 0 then
Pointer(GetFinalPathNameByHandle) := GetProcAddress(hmod,'GetFinalPathNameByHandleW');
if not Assigned(GetFinalPathNameByHandle) then
GetFinalPathNameByHandle := @GetFinalPathNameByHandleDummy;
end;
SetLength(U, MAX_PATH+1);
SetLength(U, GetFinalPathNameByHandle(Handle, @U[1], Length(U), 0));
// if Copy(U, 1, 4)='\\?\' then
// Delete(U, 1, 4);
Result := U;
end;
{$endif}
{ TDbgFileLoader }
{$ifdef USE_WIN_FILE_MAPPING}
function CreateFileW(lpFileName:LPCWSTR; dwDesiredAccess:DWORD; dwShareMode:DWORD; lpSecurityAttributes:LPSECURITY_ATTRIBUTES; dwCreationDisposition:DWORD;dwFlagsAndAttributes:DWORD; hTemplateFile:HANDLE):HANDLE; stdcall; external 'kernel32' name 'CreateFileW';
{$ENDIF}
function TDbgFileLoader.GetFileName: String;
begin
{$ifdef USE_WIN_FILE_MAPPING}
if (FFileName = '') and (FFileHandle <> 0) then begin
FFileName := FileHandleToFileName(FFileHandle);
end;
{$endif}
Result := FFileName;
end;
constructor TDbgFileLoader.Create(AFileName: String);
{$IFDEF MacOS}
var
s: String;
{$ENDIF}
{$ifdef USE_WIN_FILE_MAPPING}
var
s: UnicodeString;
{$ENDIF}
begin
{$ifdef USE_WIN_FILE_MAPPING}
FFileName := AFileName;
s := UTF8Decode(AFileName);
FFileHandle := CreateFileW(PWideChar(s), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Create(FFileHandle);
{$else}
FList := TList.Create;
{$IFDEF MacOS}
if (RightStr(AFileName,4) = '.app') then begin
s := ExtractFileName(AFileName);
s := AFileName + PathDelim + 'Contents' + PathDelim + 'MacOS' + PathDelim + copy(s, 1, Length(s) - 4);
if (FileExists(s)) then AFileName := s
end;
{$ENDIF}
FFileName := AFileName;
FStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
inherited Create;
{$endif}
end;
{$ifdef USE_WIN_FILE_MAPPING}
constructor TDbgFileLoader.Create(AFileHandle: THandle);
begin
FFileHandle := AFileHandle;
if FFileHandle = INVALID_HANDLE_VALUE
then begin
raise Exception.Create('Invalid file handle');
end;
FMapHandle := CreateFileMapping(FFileHandle, nil, PAGE_READONLY{ or SEC_IMAGE}, 0, 0, nil);
if FMapHandle = 0
then begin
if GetLastError = 8 then begin // not enough memory, use a stream instead
FList := TList.Create;
FStream := THandleStream.Create(AFileHandle);
inherited Create;
exit;
end;
raise Exception.CreateFmt('Could not create module mapping, error %d', [GetLastError]);
Exit;
end;
FModulePtr := MapViewOfFile(FMapHandle, FILE_MAP_READ, 0, 0, 0);
if FModulePtr = nil
then begin
if GetLastError = 8 then begin // not enough memory, use a stream instead
CloseHandle(FMapHandle);
FMapHandle := 0;
FList := TList.Create;
FStream := THandleStream.Create(AFileHandle);
inherited Create;
exit;
end;
raise Exception.Create('Could not map view: ' + IntToStr(GetLastOSError));
Exit;
end;
inherited Create;
end;
{$endif}
destructor TDbgFileLoader.Destroy;
begin
{$ifdef USE_WIN_FILE_MAPPING}
if FModulePtr <> nil
then UnmapViewOfFile(FModulePtr);
if FMapHandle <> 0
then CloseHandle(FMapHandle);
if FFileHandle <> INVALID_HANDLE_VALUE
then CloseHandle(FFileHandle);
{$endif}
if FList <> nil then
while FList.Count > 0 do
UnloadMemory(FList[0]);
FreeAndNil(FList);
FreeAndNil(FStream);
inherited Destroy;
end;
procedure TDbgFileLoader.OpenStream;
begin
if FStream <> nil then
exit;
if FFileName <> '' then
FStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyNone)
{$ifdef USE_WIN_FILE_MAPPING}
else
FStream := THandleStream.Create(FFileHandle)
{$endif}
;
end;
procedure TDbgFileLoader.Close;
begin
FreeAndNil(FStream);
end;
function TDbgFileLoader.Read(AOffset, ASize: QWord; AMem: Pointer): QWord;
begin
{$ifdef USE_WIN_FILE_MAPPING}
if FMapHandle <> 0 then begin
move((FModulePtr + AOffset)^, AMem^, ASize);
Result := ASize;
exit;
end;
{$endif}
Result := 0;
if AMem = nil then
exit;
if FStream = nil then
OpenStream;
FStream.Position := AOffset;
Result := FStream.Read(AMem^, ASize);
end;
function TDbgFileLoader.LoadMemory(AOffset, ASize: QWord; out AMem: Pointer): QWord;
begin
{$ifdef USE_WIN_FILE_MAPPING}
if FMapHandle <> 0 then begin
AMem := FModulePtr + AOffset;
Result := ASize;
exit;
end;
{$endif}
Result := 0;
AMem := AllocMem(ASize);
if AMem = nil then
exit;
if FStream = nil then
OpenStream;
FList.Add(AMem);
FStream.Position := AOffset;
Result := FStream.Read(AMem^, ASize);
end;
procedure TDbgFileLoader.UnloadMemory(AMem: Pointer);
begin
if FList = nil then
exit;
FList.Remove(AMem);
Freemem(AMem);
end;
{ TDbgImageReader }
function TDbgImageReader.GetAddressMapList: TDbgAddressMapList;
begin
result := nil;
end;
function TDbgImageReader.GetSubFiles: TStrings;
begin
result := nil;
end;
procedure TDbgImageReader.SetUUID(AGuid: TGuid);
begin
FUUID := AGuid;
end;
procedure TDbgImageReader.SetImageBase(ABase: QWord);
begin
FImageBase := ABase;
if FLoadedTargetImageAddr = 0 then
FLoadedTargetImageAddr := ABase;
end;
procedure TDbgImageReader.SetImageSize(ASize: QWord);
begin
FImageSize := ASize;
end;
procedure TDbgImageReader.SetRelocationOffset(AnOffset: QWord);
begin
FRelocationOffset := AnOffset;
end;
procedure TDbgImageReader.AddReaderError(AnError: String);
begin
if FReaderErrors <> '' then
FReaderErrors := FReaderErrors + LineEnding;
FReaderErrors := FReaderErrors + AnError;
end;
function TDbgImageReader.ReadGnuDebugLinkSection(out AFileName: String; out
ACrc: Cardinal): Boolean;
var
p: PDbgImageSectionEx;
i: Integer;
begin
p := PDbgImageSectionEx(Section[_gnu_dbg_link]);
Result := p <> nil;
if Result then
begin
i := IndexByte(p^.Sect.RawData^, p^.Sect.Size-4, 0);
Result := i > 0;
if Result then
begin
SetLength(AFileName, i);
move(PDbgImageSectionEx(p)^.Sect.RawData^, AFileName[1], i);
i := align(i+1, 4);
Result := (i+4) <= p^.Sect.Size;
if Result then
move((p^.Sect.RawData+i)^, ACrc, 4);
end;
end;
end;
function TDbgImageReader.LoadGnuDebugLink(ASearchPath, AFileName: String;
ACrc: Cardinal): TDbgFileLoader;
function LoadFile(AFullName: String): TDbgFileLoader;
var
i, j: Int64;
c: Cardinal;
mem: Pointer;
begin
Result := TDbgFileLoader.Create(AFullName);
i := FileSizeUtf8(AFullName) - 4096;
j := 0;
c:=0;
while j < i do begin
Result.LoadMemory(j, 4096, mem);
c:=Crc32(c, mem, 4096);
Result.UnloadMemory(mem);
inc(j, 4096)
end;
i := i - j + 4096;
Result.LoadMemory(j, i, mem);
c:=Crc32(c, mem, i);
Result.UnloadMemory(mem);
DebugLn(DBG_WARNINGS and (c <> ACrc), ['Invalid CRC for ext debug info: ', AFullName]);
if c <> ACrc then
FreeAndNil(Result);
end;
begin
Result := nil;
if FileExists(AppendPathDelim(ASearchPath) + AFileName) then
Result := LoadFile(AppendPathDelim(ASearchPath) + AFileName);
if (Result = nil) and
FileExists(AppendPathDelim(ASearchPath) + AppendPathDelim('.debug') + AFileName)
then
Result := LoadFile(AppendPathDelim(ASearchPath) + AppendPathDelim('.debug') + AFileName);
end;
procedure TDbgImageReader.ParseSymbolTable(AFpSymbolInfo: TfpSymbolList);
begin
// The format of the symbol-table-section(s) can be different on each
// platform. That's why parsing the data is done in TDbgImageReader.
end;
procedure TDbgImageReader.ParseLibrarySymbolTable(AFpSymbolInfo: TfpSymbolList);
begin
//
end;
constructor TDbgImageReader.Create(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean);
begin
inherited Create;
FLoadedTargetImageAddr := ALoadedTargetImageAddr;
end;
procedure TDbgImageReader.AddSubFilesToLoaderList(ALoaderList: TObject;
PrimaryLoader: TObject);
begin
//
end;
function TDbgImageReader.EnclosesAddressRange(AStartAddress, AnEndAddress: TDBGPtr): Boolean;
begin
Result := False;
end;
procedure InitDebugInfoLists;
begin
RegisteredImageReaderClasses := TFPList.Create;
end;
procedure ReleaseDebugInfoLists;
begin
FreeAndNil(RegisteredImageReaderClasses);
end;
initialization
DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );
InitDebugInfoLists;
finalization
ReleaseDebugInfoLists;
end.