{ This unit contains the types needed for reading Elf images. This file was ported from DUBY. See svn log for details --------------------------------------------------------------------------- *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** } unit FpImgReaderElf; {$mode objfpc}{$H+} {$IFDEF INLINE_OFF}{$INLINE OFF}{$ENDIF} interface uses Classes, SysUtils, LazUTF8, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, DbgIntfBaseTypes, // FpDebug FpImgReaderBase, fpDbgSymTable, FpImgReaderElfTypes, FpDbgCommon, FpDbgLoader; type TElfSection = packed record name : AnsiString; FileOfs : QWord; Address : QWord; Size : QWord; SectionType : QWord; Flags : QWord; end; PElfSection = ^TElfSection; { TElfFile } TElfFile = class(TObject) private FTargetInfo: TTargetDescriptor; function FElfToMachineType(machinetype: word): TMachineType; protected function Load32BitFile(ALoader: TDbgFileLoader): Boolean; function Load64BitFile(ALoader: TDbgFileLoader): Boolean; procedure AddSection(const name: AnsiString; FileOffset, Address, Size, SectionType, Flags: Qword); public sections : array of TElfSection; seccount : Integer; function LoadFromFile(ALoader: TDbgFileLoader): Boolean; function FindSection(const Name: String): Integer; end; { TElfDbgSource } TElfDbgSource = class(TDbgImageReader) // executable parser private FSections: TStringListUTF8Fast; FFileLoader : TDbgFileLoader; fOwnSource : Boolean; fElfFile : TElfFile; FImgLoaderDbgFile: TDbgImageLoader; protected function GetSection(const AName: String): PDbgImageSection; override; function GetSection(const ID: integer): PDbgImageSection; override; procedure LoadSections; procedure ClearSections; procedure ClearSectionsButInterP; public class function isValid(ASource: TDbgFileLoader): Boolean; override; class function UserName: AnsiString; override; constructor Create(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); override; constructor CreateForDbgFile(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); destructor Destroy; override; procedure ParseSymbolTable(AFpSymbolInfo: TfpSymbolList); override; procedure AddSubFilesToLoaderList(ALoaderList: TObject; PrimaryLoader: TObject); override; //function GetSectionInfo(const SectionName: AnsiString; var Size: int64): Boolean; override; //function GetSectionData(const SectionName: AnsiString; Offset, Size: Int64; var Buf: array of byte): Int64; override; end; implementation var DBG_WARNINGS: PLazLoggerLogGroup; type TElf32symbol=record st_name : longword; st_value : longword; st_size : longword; st_info : byte; { bit 0-3: type, 4-7: bind } st_other : byte; st_shndx : word; end; PElf32symbolArray = ^TElf32symbolArray; TElf32symbolArray = array[0..maxSmallint] of TElf32symbol; TElf64symbol=record st_name : longword; st_info : byte; { bit 0-3: type, 4-7: bind } st_other : byte; st_shndx : word; st_value : qword; st_size : qword; end; PElf64symbolArray = ^TElf64symbolArray; TElf64symbolArray = array[0..maxSmallint] of TElf64symbol; const // Symbol-map section name _symbol = '.symtab'; _symbolstrings = '.strtab'; _symboldyn = '.dynsym'; _symboldynstrings = '.dynstr'; { TElfFile } function TElfFile.FElfToMachineType(machinetype: word): TMachineType; begin case machinetype of EM_386: result := mt386; EM_68K: result := mt68K; EM_PPC: result := mtPPC; EM_PPC64: result := mtPPC64; EM_ARM: result := mtARM; EM_OLD_ALPHA: result := mtOLD_ALPHA; EM_IA_64: result := mtIA_64; EM_X86_64: result := mtX86_64; EM_AVR: result := mtAVR8; EM_XTENSA: result := mtXTENSA; EM_RISCV: result := mtRISCV; EM_ALPHA: result := mtALPHA; else result := mtNone; end; // If OS is not encoded in header, take some guess based on machine type if FTargetInfo.OS = osNone then begin if result in [mtAVR8, mtXTENSA, mtRISCV] then FTargetInfo.OS := osEmbedded else // Default to the same as host... FTargetInfo.OS := {$if defined(Linux)}osLinux {$elseif defined(Darwin)}osDarwin {$else}osWindows{$endif}; end; end; function TElfFile.Load32BitFile(ALoader: TDbgFileLoader): Boolean; var hdr : Elf32_Ehdr; sect : array of Elf32_shdr; i, j : integer; nm : string; sz : LongWord; strs : array of byte; begin Result := ALoader.Read(0, sizeof(hdr), @hdr) = sizeof(hdr); if not Result then Exit; FTargetInfo.machineType := FElfToMachineType(hdr.e_machine); SetLength(sect, hdr.e_shnum); //ALoader.Position := hdr.e_shoff; sz := hdr.e_shetsize * hdr.e_shnum; if sz > LongWord(length(sect)*sizeof(Elf32_shdr)) then begin debugln(DBG_WARNINGS, ['TElfFile.Load32BitFile Size of SectHdrs is ', sz, ' expected ', LongWord(length(sect)*sizeof(Elf32_shdr))]); sz := LongWord(length(sect)*sizeof(Elf32_shdr)); end; //ALoader.Read(sect[0], sz); ALoader.Read(hdr.e_shoff, sz, @sect[0]); i := sect[hdr.e_shstrndx].sh_offset; j := sect[hdr.e_shstrndx].sh_size; SetLength(strs, j); //ALoader.Position:=i; //ALoader.Read(strs[0], j); ALoader.Read(i, j, @strs[0]); for i := 0 to hdr.e_shnum - 1 do with sect[i] do begin nm := PChar( @strs[sh_name] ); AddSection(nm, sh_offset, sh_addr, sh_size, sh_type, sh_flags); end; end; function TElfFile.Load64BitFile(ALoader: TDbgFileLoader): Boolean; var hdr : Elf64_Ehdr; sect : array of Elf64_shdr; i, j : integer; nm : string; sz : LongWord; strs : array of byte; begin Result := ALoader.Read(0, sizeof(hdr), @hdr) = sizeof(hdr); if not Result then Exit; FTargetInfo.machineType := FElfToMachineType(hdr.e_machine); SetLength(sect, hdr.e_shnum); //ALoader.Position := hdr.e_shoff; sz := hdr.e_shentsize * hdr.e_shnum; if sz > LongWord(length(sect)*sizeof(Elf64_shdr)) then begin debugln(DBG_WARNINGS, ['TElfFile.Load64BitFile Size of SectHdrs is ', sz, ' expected ', LongWord(length(sect)*sizeof(Elf64_shdr))]); sz := LongWord(length(sect)*sizeof(Elf64_shdr)); end; //ALoader.Read(sect[0], sz); ALoader.Read(hdr.e_shoff, sz, @sect[0]); i := sect[hdr.e_shstrndx].sh_offset; j := sect[hdr.e_shstrndx].sh_size; SetLength(strs, j); //ALoader.Position:=i; //ALoader.Read(strs[0], j); ALoader.Read(i, j, @strs[0]); for i := 0 to hdr.e_shnum - 1 do with sect[i] do begin nm := PChar( @strs[sh_name] ); AddSection(nm, sh_offset, sh_address, sh_size, sh_type, sh_flags); end; end; procedure TElfFile.AddSection(const name: AnsiString; FileOffset, Address, Size, SectionType, Flags: Qword); begin if seccount=Length(sections) then begin if seccount = 0 then SetLength(sections, 4) else SetLength(sections, seccount*2); end; sections[seccount].Address:= Address; sections[seccount].name:=name; sections[seccount].FileOfs:=FileOffset; sections[seccount].Size:=Size; sections[seccount].SectionType:=SectionType; sections[seccount].Flags:=Flags; inc(seccount); end; function TElfFile.LoadFromFile(ALoader: TDbgFileLoader): Boolean; var ident : array [0..EINDENT-1] of byte; begin try Result := ALoader.Read(0, sizeof(ident), @ident[0]) = sizeof(ident); if not Result then Exit; Result := (ident[EI_MAG0] = $7f) and (ident[EI_MAG1] = byte('E')) and (ident[EI_MAG2] = byte('L')) and (ident[EI_MAG3] = byte('F')); if not Result then Exit; Result := False; case ident[EI_DATA] of ELFDATA2LSB: FTargetInfo.ByteOrder := boLSB; ELFDATA2MSB: FTargetInfo.ByteOrder := boMSB; else FTargetInfo.byteOrder := boNone; end; case ident[EI_OSABI] of ELFOSABI_LINUX: FTargetInfo.OS := osLinux; ELFOSABI_STANDALONE: FTargetInfo.OS := osEmbedded; else FTargetInfo.OS := osNone; // Will take a guess after machine type is available end; if ident[EI_CLASS] = ELFCLASS32 then begin FTargetInfo.bitness := b32; Result := Load32BitFile(ALoader); exit; end; if ident[EI_CLASS] = ELFCLASS64 then begin FTargetInfo.bitness := b64; Result := Load64BitFile(ALoader); exit; end; except Result := false; end; end; function TElfFile.FindSection(const Name: String): Integer; var i : Integer; begin Result := -1; for i := 0 to seccount - 1 do if sections[i].name = Name then begin Result := i; Exit; end; end; { TElfDbgSource } function TElfDbgSource.GetSection(const AName: String): PDbgImageSection; var i: Integer; ex: PDbgImageSectionEx; begin Result := nil; i := FSections.IndexOf(AName); if i < 0 then exit; ex := PDbgImageSectionEx(FSections.Objects[i]); Result := @ex^.Sect; if ex^.Loaded then exit; ex^.Loaded := True; FFileLoader.LoadMemory(ex^.Offs, Result^.Size, Result^.RawData); end; function TElfDbgSource.GetSection(const ID: integer): PDbgImageSection; var ex: PDbgImageSectionEx; begin if (ID >= 0) and (ID < FSections.Count) then begin ex := PDbgImageSectionEx(FSections.Objects[ID]); Result := @ex^.Sect; Result^.Name := FSections[ID]; if not ex^.Loaded then begin FFileLoader.LoadMemory(ex^.Offs, Result^.Size, Result^.RawData); ex^.Loaded := True; end; end else Result := nil; end; procedure TElfDbgSource.LoadSections; var p: PDbgImageSectionEx; idx: integer; i: Integer; fs: TElfSection; begin for i := 0 to fElfFile.seccount - 1 do begin fs := fElfFile.sections[i]; idx := FSections.AddObject(fs.name, nil); New(p); P^.Offs := fs.FileOfs; p^.Sect.Size := fs.Size; p^.Sect.VirtualAddress := fs.Address; //0; // Todo? fs.Address - ImageBase p^.Sect.IsLoadable := ((fs.SectionType and SHT_PROGBITS) > 0) and ((fs.Flags and SHF_ALLOC) > 0) and ((fs.SectionType and SHT_NOBITS) = 0); p^.Loaded := False; FSections.Objects[idx] := TObject(p); end; end; procedure TElfDbgSource.ClearSections; var i: Integer; begin for i := 0 to FSections.Count-1 do Freemem(FSections.Objects[i]); FSections.Clear; end; procedure TElfDbgSource.ClearSectionsButInterP; var i: Integer; begin i := 0; while i < FSections.Count do begin if FSections[i] <> '.interp' then begin Freemem(FSections.Objects[i]); FSections.Delete(i); end else inc(i); end; end; class function TElfDbgSource.isValid(ASource: TDbgFileLoader): Boolean; var buf : array [0..3+sizeof(Elf32_EHdr)] of byte; begin try Result := Assigned(ASource) and (ASource.Read(0, sizeof(Elf32_EHdr), @buf[0]) = sizeof(Elf32_EHdr)); if not Result then Exit; Result := (buf[EI_MAG0] = $7f) and (buf[EI_MAG1] = byte('E')) and (buf[EI_MAG2] = byte('L')) and (buf[EI_MAG3] = byte('F')); except Result := false; end; end; class function TElfDbgSource.UserName: AnsiString; begin Result := 'ELF executable'; end; constructor TElfDbgSource.Create(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); var DbgFileName, SourceFileName: String; crc: Cardinal; NewFileLoader: TDbgFileLoader; ImgReadeDbgFile: TElfDbgSource; begin inherited Create(ASource, ADebugMap, ALoadedTargetImageAddr, OwnSource); FSections := TStringListUTF8Fast.Create; FSections.Sorted := True; //FSections.Duplicates := dupError; FSections.CaseSensitive := False; // Elf-binaries do not have an internal offset encoded into the binary (ImageBase) // so their reloction-offset is just equal to the location at which the binary // has been loaded into memory. (The LoadedTargetImageAddr) SetRelocationOffset(ALoadedTargetImageAddr); FFileLoader := ASource; fOwnSource := OwnSource; fElfFile := TElfFile.Create; fElfFile.LoadFromFile(FFileLoader); LoadSections; // check external debug file if ReadGnuDebugLinkSection(DbgFileName, crc) then begin SourceFileName := ASource.FileName; if SourceFileName<>'' then SourceFileName := ExtractFilePath(SourceFileName); NewFileLoader := LoadGnuDebugLink(SourceFileName, DbgFileName, crc); if NewFileLoader <> nil then begin ImgReadeDbgFile := TElfDbgSource.CreateForDbgFile(NewFileLoader, ADebugMap, ALoadedTargetImageAddr, True); FImgLoaderDbgFile := TDbgImageLoader.Create(ImgReadeDbgFile); ClearSectionsButInterP; end; end; FTargetInfo := fElfFile.FTargetInfo; end; constructor TElfDbgSource.CreateForDbgFile(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); begin inherited Create(ASource, ADebugMap, ALoadedTargetImageAddr, OwnSource); FSections := TStringListUTF8Fast.Create; FSections.Sorted := True; //FSections.Duplicates := dupError; FSections.CaseSensitive := False; // Elf-binaries do not have an internal offset encoded into the binary (ImageBase) // so their reloction-offset is just equal to the location at which the binary // has been loaded into memory. (The LoadedTargetImageAddr) SetRelocationOffset(ALoadedTargetImageAddr); FFileLoader := ASource; fOwnSource := OwnSource; fElfFile := TElfFile.Create; fElfFile.LoadFromFile(FFileLoader); LoadSections; FTargetInfo := fElfFile.FTargetInfo; end; destructor TElfDbgSource.Destroy; begin if fOwnSource then FFileLoader.Free; fElfFile.Free; ClearSections; FreeAndNil(FSections); inherited Destroy; end; procedure TElfDbgSource.ParseSymbolTable(AFpSymbolInfo: TfpSymbolList); var p: PDbgImageSection; ps: PDbgImageSection; SymbolArr32: PElf32symbolArray; SymbolArr64: PElf64symbolArray; SymbolStr: pointer; i: integer; SymbolCount: integer; SymbolName: AnsiString; SectIdx: Word; Sect: PElfSection; begin AfpSymbolInfo.SetAddressBounds(1, High(AFpSymbolInfo.HighAddr)); // always search / TODO: iterate all sections for bounds p := Section[_symbol]; ps := Section[_symbolstrings]; if (p = nil) or (ps = nil) then begin p := Section[_symboldyn]; ps := Section[_symboldynstrings]; end; if assigned(p) and assigned(ps) then begin SymbolStr:=PDbgImageSectionEx(ps)^.Sect.RawData; if FTargetInfo.Bitness = b64 then begin SymbolArr64:=PDbgImageSectionEx(p)^.Sect.RawData; SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(TElf64symbol); for i := 0 to SymbolCount-1 do begin begin {$push} {$R-} if SymbolArr64^[i].st_name<>0 then begin SectIdx := SymbolArr64^[i].st_shndx; if (SectIdx < 0) or (SectIdx >= fElfFile.seccount) then continue; Sect := @fElfFile.sections[SectIdx]; if (Sect^.Flags and SHF_ALLOC) = 0 then continue; // not loaded, symbol not in memory SymbolName:=pchar(SymbolStr+SymbolArr64^[i].st_name); {$Q-} AfpSymbolInfo.Add(SymbolName, TDbgPtr(SymbolArr64^[i].st_value+RelocationOffset), Sect^.Address + Sect^.Size + RelocationOffset); end; {$pop} end end; end else begin SymbolArr32:=PDbgImageSectionEx(p)^.Sect.RawData; SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(TElf32symbol); for i := 0 to SymbolCount-1 do begin begin {$push} {$R-} if SymbolArr32^[i].st_name<>0 then begin SectIdx := SymbolArr32^[i].st_shndx; if (SectIdx < 0) or (SectIdx >= fElfFile.seccount) then continue; Sect := @fElfFile.sections[SectIdx]; if (Sect^.Flags and SHF_ALLOC) = 0 then continue; // not loaded, symbol not in memory SymbolName:=pchar(SymbolStr+SymbolArr32^[i].st_name); {$Q-}{$R-} AfpSymbolInfo.Add(SymbolName, DWord(SymbolArr32^[i].st_value+RelocationOffset), DWORD(Sect^.Address + Sect^.Size+RelocationOffset)); end; {$pop} end end; end; end; end; procedure TElfDbgSource.AddSubFilesToLoaderList(ALoaderList: TObject; PrimaryLoader: TObject); begin inherited AddSubFilesToLoaderList(ALoaderList, PrimaryLoader); if FImgLoaderDbgFile <> nil then TDbgImageLoaderList(ALoaderList).Add(FImgLoaderDbgFile); end; initialization DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} ); RegisterImageReaderClass( TElfDbgSource ); end.