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; { TDbgAddressMapHashList } TDbgAddressMapHashList = class(specialize TLazFPGHashTable) public function ItemFromNode(ANode: THTCustomNode): TDbgAddressMap; function ItemPointerFromNode(ANode: THTCustomNode): PDbgAddressMap; end; { TDbgAddressMapPointerHashList } TDbgAddressMapPointerHashList = class(specialize TLazFPGHashTable) 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 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.