unit FpImgReaderMacho;

{$mode objfpc}{$H+}
{$IFDEF INLINE_OFF}{$INLINE OFF}{$ENDIF}

interface

uses
  Classes, SysUtils, contnrs,
  LazFglHash, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, LazFileUtils, LazUTF8,
  DbgIntfBaseTypes,
  // FpDebug
  macho, FpImgReaderMachoFile, FpImgReaderBase, fpDbgSymTable, FpDbgUtil, FpDbgLoader, FpDbgCommon;

type

  { TDbgMachoDataSource }

  TDbgMachoDataSource = class(TDbgImageReader)
  private
    fSource     : TDbgFileLoader;
    FSections: TStringListUTF8Fast;
    fSubFiles: TStringList;
    fAddressMapList: TDbgAddressMapList;
    fOwnSource  : Boolean;
    fFile       : TMachoFile;
    hasSymTable : Boolean;
    StabsCmd    : symtab_command;
    fileRead    : Boolean;
    function GetSectionInfo(const SectionName: AnsiString; var Size: int64): Boolean;
    function GetSectionData(const SectionName: AnsiString; Offset, {%H-}Size: Int64; var Buf: array of byte): Int64;
    procedure ParseMainAppleDwarfDataMap;
    procedure ParseSubAppleDwarfDataMap(ADebugMap: TObject);
  protected
    procedure ReadFile;
    function GetSubFiles: TStrings; override;
    function GetAddressMapList: TDbgAddressMapList; override;
    function GetSymTableSectionInfo({%H-}StabStr: Boolean; var {%H-}SectionOffset, {%H-}SectionSize: Int64): Boolean;
    function GetSectionIndex(const SectionName: AnsiString): Integer;

    function GetSection(const AName: String): PDbgImageSection; override;
    function GetSection(const ID: integer): PDbgImageSection; override;
  public
    class function isValid(ASource: TDbgFileLoader): Boolean; override;
    class function UserName: AnsiString; override;
    class procedure LoadSubFiles(ASubFiles: TStrings; ALoaderList: TDbgImageLoaderList);
    procedure AddSubFilesToLoaderList(ALoaderList: TObject; PrimaryLoader: TObject); override;
    procedure ParseSymbolTable(AfpSymbolInfo: TfpSymbolList); override;
  public
    constructor Create(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean); override;
    destructor Destroy; override;
  end;

implementation

var
  DBG_VERBOSE, DBG_WARNINGS: PLazLoggerLogGroup;

type
  PnlistArray = ^nlist; // ^array[0..infinite] of nlist;
  PnlistArray64 = ^nlist_64; // ^array[0..infinite] of nlist_64;

const
  // Symbol-map section name
  _symbol        = '.symbols';
  _symbolstrings = '.symbolsstrings';

type
  TDebugTableState = (dtsDir, dtsSource, dtsObjectFile, dtsProc, dtsProcLen, dtsProcEnd, dtsEnd);
  TDebugTableEntry = record
    Dir: string;
    SourceFile: string;
    ObjectFile: string;
    ObjFileAge: longint;
    Offset: TDBGPtr;
  end;

  { TAppleDwarfDebugMap }

  TAppleDwarfDebugMap = class(TObject)
  private
    //FAddressMap: TDbgAddressMapList;
    FDir: string;
    FGlobalList: TDbgAddressMapHashList;
    FObjectFile: string;
    FObjFileAge: longint;
    FOffset: TDBGPtr;
    FSourceFile: string;
  public
    constructor create;
    destructor destroy; override;
    procedure clear;
    property Offset: TDBGPtr read FOffset write FOffset;
    property Dir: string read FDir write FDir;
    property ObjectFile: string read FObjectFile write FObjectFile;
    property SourceFile: string read FSourceFile write FSourceFile;
    property ObjFileAge: longint read FObjFileAge write FObjFileAge;
    // This list contains the locations for all symbols in the executable,
    // parsed from the 'stabs'-debuginfo in the executable.
    // This property is only available in the debug-map for the main
    // executable.
    property GlobalList: TDbgAddressMapHashList read FGlobalList;
    // This list maps addresses in an object-file to the addresses
    // in the main executable.
    // This property is only available in the debug-map for a specific
    // object-file.
    //property AddressMap: TDbgAddressMapList read FAddressMap;
  end;

function isValidMachoStream(ASource: TDbgFileLoader): Boolean;
var
  header  : mach_header;
begin
  try
    Result := Assigned(ASource);
    if not Result then Exit;
    Result := ASource.Read(0, sizeof(header), @header) = sizeof(header);
    if not Result then Exit;
    Result := (header.magic = MH_CIGAM) or (header.magic = MH_MAGIC) or
              (header.magic = MH_CIGAM_64) or (header.magic = MH_MAGIC_64);
  except
    Result := false;
  end;
end;

function FixMachoName(const macsectionname: String): String;
begin
  if Copy(macsectionName, 1, 2) = '__' then
    Result:= '.'+Copy(macsectionName, 3, length(macsectionName)-2)
  else
    Result := macsectionname;
end;

constructor TAppleDwarfDebugMap.create;
begin
  FGlobalList := TDbgAddressMapHashList.Create;
  //FAddressMap := TDbgAddressMapList.Create;
end;

destructor TAppleDwarfDebugMap.destroy;
begin
  //FAddressMap.Free;
  FGlobalList.Free;
  inherited destroy;
end;

procedure TAppleDwarfDebugMap.clear;
begin
  //FAddressMap.Clear;
  FDir := '';
  FGlobalList.Clear;
  FObjectFile := '';
  FObjFileAge := 0;
  FOffset := 0;
  FSourceFile := '';
end;

{ TDbgMachoDataSource }

class function TDbgMachoDataSource.isValid(ASource: TDbgFileLoader): Boolean;
begin
  Result := isValidMachoStream(ASource);
end;

class function TDbgMachoDataSource.UserName: AnsiString;
begin
  Result:='mach-o file';
end;

class procedure TDbgMachoDataSource.LoadSubFiles(ASubFiles: TStrings; ALoaderList: TDbgImageLoaderList);
var
  DwarfDebugMap: TAppleDwarfDebugMap;
  Loader: TDbgImageLoader;
  i: Integer;
begin
  if assigned(ASubFiles) then
    begin
    for i := 0 to ASubFiles.Count -1 do
      begin
      if (ASubFiles.Objects[i] is TAppleDwarfDebugMap) and FileExists(ASubFiles[i]) then
        begin
        DwarfDebugMap:=TAppleDwarfDebugMap(ASubFiles.Objects[i]);
        if FileAge(ASubFiles[i]) <> DwarfDebugMap.ObjFileAge then
          debugln(Format('The timestamp of the object-file "%s" does not correspond to the timestamp (%s) stored inside the executable. The debug-info in this file is not loaded.', [DwarfDebugMap.ObjectFile, DateTimeToStr(FileDatetoDateTime(DwarfDebugMap.ObjFileAge))]))
        else
          begin
          Loader := TDbgImageLoader.Create(DwarfDebugMap.ObjectFile, DwarfDebugMap);
          ALoaderList.Add(Loader);
          end;
        end
      else
        DebugLn('File with debug-info "'+ASubFiles[i]+'" does not exist. This could lead to missing debug-information.');
      end;
    end;
end;

procedure TDbgMachoDataSource.ParseSymbolTable(AfpSymbolInfo: TfpSymbolList);
var
  p: PDbgImageSection;
  ps: PDbgImageSection;
  SymbolArr: PnlistArray;
  SymbolArr64: PnlistArray64;
  SymbolStr: pointer;
  SymbolType: uint8_t;
  StringOffset: uint32_t;
  SymbolValue: TDBGPtr;
  i: integer;
  SymbolCount: integer;
begin
  p := Section[_symbol];
  ps := Section[_symbolstrings];
  if assigned(p) and assigned(ps) then
  begin
    SymbolStr:=PDbgImageSectionEx(ps)^.Sect.RawData;
    if TargetInfo.Bitness = b64 then
      begin
        SymbolArr64:=PDbgImageSectionEx(p)^.Sect.RawData;
        SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist_64);
      end
      else
      begin
        SymbolArr:=PDbgImageSectionEx(p)^.Sect.RawData;
        SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist);
      end;
    for i := 0 to SymbolCount-1 do
    begin
      if TargetInfo.Bitness = b64 then
      begin
        SymbolType := SymbolArr64[i].n_type;
        StringOffset := SymbolArr64[i].n_un.n_strx;
        SymbolValue := SymbolArr64[i].n_value;
      end
      else
      begin
        SymbolType := SymbolArr[i].n_type;
        StringOffset := SymbolArr[i].n_un.n_strx;
        SymbolValue := SymbolArr[i].n_value;
      end;
      if (SymbolType and $e0)<>0 then
        // This is a stabs-entry. Ignore.
        Continue;
      if (SymbolType and $0e)=$e then
      begin
        // Section-index is ignored for now...
        AfpSymbolInfo.Add(pchar(SymbolStr+StringOffset), SymbolValue);
      end
    end;
  end;

end;

procedure TDbgMachoDataSource.ReadFile;
var
  i : Integer;
begin
  if Assigned(fFile) then fFile.Free;
  fFile:=TMachOFile.Create;
  fFile.LoadFromFile(fSource);
  for i := 0 to fFile.header.ncmds - 1 do begin
    hasSymTable := fFile.commands[i]^.cmd = LC_SYMTAB;
    if hasSymTable then begin
      StabsCmd := psymtab_command(fFile.commands[i])^;
      Break;
    end;
  end;
  FTargetInfo := fFile.TargetInfo;
  SetUUID(fFile.UUID);
  fileRead := true;
end;

function TDbgMachoDataSource.GetSubFiles: TStrings;
begin
  if not assigned(fSubFiles) then
    begin
    fSubFiles:=TStringList.Create;
    fSubFiles.OwnsObjects:=true;
    end;
  Result:=fSubFiles;
end;

function TDbgMachoDataSource.GetAddressMapList: TDbgAddressMapList;
begin
  if not assigned(fAddressMapList) then
    begin
    fAddressMapList:=TDbgAddressMapList.Create;
    end;
  Result:=fAddressMapList;
end;

function TDbgMachoDataSource.GetSymTableSectionInfo(StabStr: Boolean;
  var SectionOffset, SectionSize: Int64): Boolean;
begin
  Result := hasSymTable;
  if not Result then Exit;
  if StabStr then begin
    SectionOffset := StabsCmd.stroff;
    SectionSize := StabsCmd.strsize;
  end else begin
    SectionOffset := StabsCmd.symoff;
    if TargetInfo.Bitness = b64 then
      SectionSize := Int64(StabsCmd.nsyms * sizeof(nlist_64))
    else
      SectionSize := Int64(StabsCmd.nsyms * sizeof(nlist));
  end;
end;

function TDbgMachoDataSource.GetSectionIndex(const SectionName: AnsiString): Integer;
var
  i     : Integer;
  Name  : AnsiString;
begin
  //todo: hash-table
  for i := 0 to fFile.Sections.Count - 1 do begin
    with TMachoSection(fFile.sections[i]) do
      if is32
        then Name := FixMachoName(sec32.sectname)
        else Name := FixMachoName(sec64.sectname);
    if Name = SectionName then begin
      Result := i;
      Exit;
    end;
  end;
  Result := -1;
end;

function TDbgMachoDataSource.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;
  fSource.LoadMemory(ex^.Offs, Result^.Size, Result^.RawData);
end;

function TDbgMachoDataSource.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
      ex^.Loaded  := True;
      fSource.LoadMemory(ex^.Offs, Result^.Size, Result^.RawData);
    end;
  end
  else
    Result := nil;
end;

procedure TDbgMachoDataSource.AddSubFilesToLoaderList(ALoaderList: TObject;
  PrimaryLoader: TObject);
var
  PLoader: TDbgImageLoader absolute PrimaryLoader;
  LList: TDbgImageLoaderList absolute ALoaderList;
  ALoader: TDbgImageLoader;
  fname, dSYMFilename: String;
  i: SizeInt;
begin
  // JvdS: Mach-O binaries do not contain DWARF-debug info. Instead this info
  // is stored inside the .o files, and the executable contains a map (in stabs-
  // format) of all these .o files. An alternative to parsing this map and reading
  // those .o files a dSYM-bundle could be used, which could be generated
  // with dsymutil.

  // PLoader.FileName in Contents/MacOS
  ALoader:=nil;

  fname := PLoader.FileName;
  i := pos('/Contents/MacOS', fname);
  if i > 0 then delete(fname, i, Length(fname));
  dSYMFilename:=ChangeFileExt(PLoader.FileName, '.dSYM');
  dSYMFilename:=dSYMFilename+'/Contents/Resources/DWARF/'+ExtractFileName(fname); // TDbgProcess.Name

  if FilenameExtIs(dSYMFilename,'.app',true) then
    dSYMFilename := ChangeFileExt(dSYMFilename,'');

  if FileExists(dSYMFilename) then
    begin
    ALoader := TDbgImageLoader.Create(dSYMFilename);
    if GUIDToString(ALoader.UUID)<>GUIDToString(PLoader.UUID) then
      begin
      AddReaderError('The unique UUID''s of the executable and the dSYM bundle with debug-info ('+dSYMFilename+') do not match.');
      debugln(DBG_WARNINGS, 'The unique UUID''s of the executable and the dSYM bundle with debug-info ('+dSYMFilename+') do not match.');
      FreeAndNil(ALoader);
      end
    else
      begin
      debugln(DBG_VERBOSE, 'Load debug-info from dSYM bundle ('+dSYMFilename+').');
      LList.Add(ALoader);
      end;
    end;

  if not assigned(ALoader) then
    begin
    debugln(DBG_VERBOSE, 'Read debug-info from separate object files.');
    TDbgMachoDataSource.LoadSubFiles(PLoader.SubFiles, LList);
    end;
end;

constructor TDbgMachoDataSource.Create(ASource: TDbgFileLoader; ADebugMap: TObject; ALoadedTargetImageAddr: TDbgPtr; OwnSource: Boolean);
const
  SymbolsSectionName : array [Boolean] of AnsiString = (_symbol, _symbolstrings);
var
  p: PDbgImageSectionEx;
  fs: TMachOsection;
  i: Integer;
  Name: String;
  soffset: int64;
  ssize: int64;
begin
  inherited Create(ASource, ADebugMap, ALoadedTargetImageAddr, OwnSource);

  fSource := ASource;
  fOwnSource := OwnSource;

  ReadFile;

  FSections := TStringListUTF8Fast.Create;
  FSections.Sorted := True;
  FSections.Duplicates := dupAccept;
  FSections.CaseSensitive := False;

  for i := 0 to fFile.sections.Count - 1 do begin
    fs := TMachoSection(fFile.sections[i]);
    New(p);

    if fs.is32 then begin
      Name := FixMachoName(fs.sec32.sectname);
      P^.Offs := fs.sec32.offset;
      p^.Sect.Size := fs.sec32.size;
    end
    else begin
      Name := FixMachoName(fs.sec64.sectname);
      P^.Offs := fs.sec64.offset;
      p^.Sect.Size := fs.sec64.size;
    end;

    p^.Sect.VirtualAddress := 0; // Todo?
    p^.Sect.IsLoadable := False; // Todo - needed for remote uploads via RSP only
    p^.Loaded := False;
    FSections.AddObject(Name, TObject(p));
  end;

  if GetSymTableSectionInfo(false, soffset, ssize) then begin
    new(p);
    p^.Offs:=soffset;
    p^.Sect.Size:=ssize;
    p^.Sect.VirtualAddress:=0;
    p^.Loaded:=false;
    FSections.AddObject(SymbolsSectionName[false], TObject(p));
  end;

  if GetSymTableSectionInfo(true, soffset, ssize) then begin
    new(p);
    p^.Offs:=soffset;
    p^.Sect.Size:=ssize;
    p^.Sect.VirtualAddress:=0;
    p^.Loaded:=false;
    FSections.AddObject(SymbolsSectionName[true], TObject(p));
  end;

  if assigned(ADebugMap) then
    ParseSubAppleDwarfDataMap(ADebugMap)
  else
    ParseMainAppleDwarfDataMap;
end;

destructor TDbgMachoDataSource.Destroy;
begin
  if assigned(fSubFiles) then
    fSubFiles.Free;
  if assigned(fAddressMapList) then
    fAddressMapList.Free;
  if Assigned(fFile) then fFile.Free;
  if fOwnSource then fSource.Free;
  while FSections.Count > 0 do begin
    Freemem(FSections.Objects[0]);
    FSections.Delete(0);
  end;
  FreeAndNil(FSections);
  inherited Destroy;
end;

{function TDbgMachoDataSource.SectionsCount: Integer;
begin
  if not Assigned(fFile) then ReadFile;
  Result := fFile.Sections.Count;
  if isStabs then inc(Result, 2);
end;

function TDbgMachoDataSource.GetSection(Index: Integer; var Name: AnsiString; var Size: Int64): Boolean;
var
  cnt   : Integer;
  sstr  : Boolean;
const
  StabSectionName : array [Boolean] of AnsiString = (_stab, _stabstr);
begin
  if not Assigned(fFile) then ReadFile;
  cnt := fFile.Sections.Count;
  if isStabs then inc(cnt, 2);
  Result := (Index >= 0) and (Index < cnt);
  if not Result then Exit;

  if Index < fFile.Sections.Count then begin
    with TMachoSection(fFile.sections[index]) do
      if is32 then begin
        Name := FixMachoName(sec32.sectname);
        Size := sec32.size;
      end else begin
        Name := FixMachoName(sec64.sectname);
        Size := sec64.size;
      end;
  end else begin
    sstr := Index = cnt - 1;
    Name := StabSectionName[sstr];
    Result := GetStabSectionInfo(sstr, Size);
  end;
end;

function TDbgMachoDataSource.GetSectionData(index: Integer; outStream: TStream): Boolean;
var
  ofs : Int64;
  sz  : Int64;
begin
  //todo: method will be removed
  if not Assigned(outStream) then begin
    Result := false;
    Exit;
  end;
  if not Assigned(fFile) then ReadFile;
  Result := (Index >= 0) and (Index < fFile.Sections.Count);
  if not Result then Exit;

  with TMachOsection(fFile.sections[index]) do begin
    if is32 then begin
      ofs := sec32.offset;
      sz := sec32.size;
    end else begin
      ofs := sec64.offset;
      sz := sec64.size;
    end;
  end;
  if ofs > 0 then begin
    fSource.Position:=ofs;
    outStream.CopyFrom(fSource, sz);
  end;
end;}

function TDbgMachoDataSource.GetSectionInfo(const SectionName: AnsiString; var Size: int64): Boolean;
var
  idx     : integer;
  symtablestr : Boolean;
  dummy   : int64;
begin
  if not fileRead then ReadFile;

  symtablestr := (SectionName = _symbolstrings);
  if symtablestr or (SectionName = _symbol) then
    Result := GetSymTableSectionInfo(symtablestr, dummy, Size)
  else begin
    idx := GetSectionIndex(SectionName);
    Result := idx >= 0;
    if not Result then Exit;

    with TMachOsection(fFile.sections[idx]) do
      if is32
        then Size := sec32.size
        else Size := sec64.size;
  end;
end;

function TDbgMachoDataSource.GetSectionData(const SectionName: AnsiString; Offset, Size: Int64; var Buf: array of byte): Int64;
var
  idx     : Integer;
  sofs    : int64;
  ssize   : int64;
  symstr : Boolean;
  sz      : Int64;
  s       : TMachOsection;
begin
  if not fileRead then ReadFile;

  Result := 0;
  symstr := SectionName = _symbolstrings;
  if symstr or (SectionName = _symbol)  then begin
    if not GetSymTableSectionInfo(symstr, sofs, ssize) then
      Exit;
  end else begin
    idx := GetSectionIndex(SectionName);
    s := TMachOsection(fFile.sections[idx]);
    if s.is32 then begin
      ssize := s.sec32.size;
      sofs := s.sec32.offset;
    end else begin
      sofs := s.sec64.offset;
      ssize := s.sec64.size;
    end;
  end;

  sz := ssize - Offset;
  if sz < 0 then Exit;

  //fSource.Position := sofs + Offset;
  //Result := fSource.Read(Buf[0], sz);
  Result := fSource.Read(sofs + Offset, sz, @Buf[0]);
end;

procedure TDbgMachoDataSource.ParseMainAppleDwarfDataMap;
var
  p: PDbgImageSection;
  ps: PDbgImageSection;
  SymbolArr: PnlistArray;
  SymbolArr64: PnlistArray64;
  SymbolStr: pointer;
  i: integer;
  SymbolCount: integer;
  SymbolType, SymbolSect: uint8_t;
  StringOffset: uint32_t;
  SymbolValue: QWord;
  State: TDebugTableState;
  AddressMap, AddressMapTmp: TDbgAddressMap;
  ProcName: string;
  DwarfDebugMap: TAppleDwarfDebugMap;
  FullDwarfDebugMap: TDbgAddressMapPointerHashList;
  TmpAddressMapList: TDbgAddressMapHashList;
  ind: THTCustomNode;
begin
  DwarfDebugMap:=nil;
  FullDwarfDebugMap := TDbgAddressMapPointerHashList.Create;
  TmpAddressMapList := TDbgAddressMapHashList.Create;
  p := Section[_symbol];
  ps := Section[_symbolstrings];
  if assigned(p) and assigned(ps) then
  begin
    SymbolStr:=PDbgImageSectionEx(ps)^.Sect.RawData;
    if TargetInfo.Bitness = b64 then
    begin
      SymbolArr64:=PDbgImageSectionEx(p)^.Sect.RawData;
      SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist_64);
    end
    else
    begin
      SymbolArr:=PDbgImageSectionEx(p)^.Sect.RawData;
      SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist);
    end;
    state := dtsEnd;
    for i := 0 to SymbolCount-1 do
    begin
      if TargetInfo.Bitness = b64 then
      begin
        SymbolType := SymbolArr64[i].n_type;
        StringOffset := SymbolArr64[i].n_un.n_strx;
        SymbolValue := SymbolArr64[i].n_value;
        SymbolSect := SymbolArr64[i].n_sect;
      end
      else
      begin
        SymbolType := SymbolArr[i].n_type;
        StringOffset := SymbolArr[i].n_un.n_strx;
        SymbolValue := SymbolArr[i].n_value;
        SymbolSect := SymbolArr[i].n_sect;
      end;
      case state of
        dtsEnd:
          begin
            if SymbolType = N_SO then
            begin
              if not assigned(DwarfDebugMap) then
                DwarfDebugMap := TAppleDwarfDebugMap.Create
              else
                DwarfDebugMap.clear;
              DwarfDebugMap.Dir := pchar(SymbolStr+StringOffset);
              state := dtsDir;
            end
            else if (SymbolType and not uint8_t(N_EXT or N_PEXT) = N_SECT) and (SymbolValue <> 0) then begin
              ind := FullDwarfDebugMap.Find(pchar(SymbolStr+StringOffset));
              if (ind = nil) then begin
                ind := TmpAddressMapList.Find(pchar(SymbolStr+StringOffset));
                if (ind = nil) then begin
                  AddressMapTmp.NewAddr:=SymbolValue;
                  AddressMapTmp.OrgAddr:=0;
                  AddressMapTmp.Length:=0;
                  TmpAddressMapList.Add(pchar(SymbolStr+StringOffset), AddressMapTmp);
                end
                else
                  TmpAddressMapList.ItemPointerFromNode(ind)^.NewAddr := SymbolValue;
              end
              else
              if (FullDwarfDebugMap.ItemPointerFromNode(ind)^.NewAddr = 0) then begin
                FullDwarfDebugMap.ItemPointerFromNode(ind)^.NewAddr := SymbolValue;
              end;
            end;
          end;
        dtsDir:
          begin
            if SymbolType = N_SO then
            begin
              DwarfDebugMap.SourceFile:=pchar(SymbolStr+StringOffset);
              inc(state);
            end
            else
              state := dtsEnd;
          end;
        dtsSource:
          begin
            if SymbolType = N_OSO then
            begin
              DwarfDebugMap.ObjectFile:=pchar(SymbolStr+StringOffset);
              DwarfDebugMap.ObjFileAge:=SymbolValue;
              inc(state);
            end;
          end;
        dtsObjectFile:
          begin
            if (SymbolType = N_BNSYM) then
            begin
              inc(state);
            end
            else if (SymbolType = N_STSYM) or (SymbolType = N_GSYM) then
            begin
              AddressMap.NewAddr:=SymbolValue;
              AddressMap.OrgAddr:=0;
              AddressMap.Length:=0;
              if (SymbolType = N_GSYM) and (SymbolValue = 0) then begin
                ind := TmpAddressMapList.Find(pchar(SymbolStr+StringOffset));
                if (ind <> nil) then begin
                  AddressMap.NewAddr:=TmpAddressMapList.ItemPointerFromNode(ind)^.NewAddr ;
                end;
              end;
              DwarfDebugMap.GlobalList.Add(pchar(SymbolStr+StringOffset), AddressMap);
              if (SymbolType = N_GSYM) and (AddressMap.NewAddr = 0) then begin
                ind := DwarfDebugMap.GlobalList.Find(pchar(SymbolStr+StringOffset));
                FullDwarfDebugMap.Add(pchar(SymbolStr+StringOffset),
                  DwarfDebugMap.GlobalList.ItemPointerFromNode(ind));
              end;
            end
            else if (SymbolType = N_SO) and (SymbolSect=1) then
            begin
              state := dtsEnd;
              SubFiles.AddObject(DwarfDebugMap.ObjectFile, DwarfDebugMap);
              DwarfDebugMap:=nil;
            end;
          end;
        dtsProc:
          begin
            if (SymbolType = N_FUN) and (SymbolSect=1) then
            begin
              AddressMap.NewAddr:=SymbolValue;
              ProcName:=pchar(SymbolStr+StringOffset);
              inc(state);
            end;
          end;
        dtsProcLen:
          begin
            if (SymbolType = N_FUN) and (SymbolSect=0) then
            begin
              AddressMap.Length:=SymbolValue;
              inc(state);
            end;
          end;
        dtsProcEnd:
          begin
            if (SymbolType = N_ENSYM) and (SymbolSect=1) then
            begin
              DwarfDebugMap.GlobalList.Add(ProcName, AddressMap);
              state := dtsObjectFile;
            end;
          end;
      end;
    end;
  end;
  FullDwarfDebugMap.Free;
  TmpAddressMapList.Free
end;

procedure TDbgMachoDataSource.ParseSubAppleDwarfDataMap(ADebugMap: TObject);
var
  p: PDbgImageSection;
  ps: PDbgImageSection;
  SymbolArr: PnlistArray;
  SymbolArr64: PnlistArray64;
  SymbolStr: pointer;
  SymbolType: uint8_t;
  StringOffset: uint32_t;
  SymbolValue: QWord;
  i: integer;
  SymbolCount: integer;
  MainDwarfDebugMap: TAppleDwarfDebugMap;
  ind: THTCustomNode;
  AddressMap: TDbgAddressMap;
  s: string;
begin
  MainDwarfDebugMap:=TAppleDwarfDebugMap(ADebugMap);
  p := Section[_symbol];
  ps := Section[_symbolstrings];
  if assigned(p) and assigned(ps) then
  begin
    SymbolStr:=PDbgImageSectionEx(ps)^.Sect.RawData;
    if TargetInfo.Bitness = b64 then
    begin
      SymbolArr64:=PDbgImageSectionEx(p)^.Sect.RawData;
      SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist_64);
    end
    else
    begin
      SymbolArr:=PDbgImageSectionEx(p)^.Sect.RawData;
      SymbolCount := PDbgImageSectionEx(p)^.Sect.Size div sizeof(nlist);
    end;
    for i := 0 to SymbolCount-1 do
    begin
      if TargetInfo.Bitness = b64 then
      begin
        SymbolType := SymbolArr64[i].n_type;
        StringOffset := SymbolArr64[i].n_un.n_strx;
        SymbolValue := SymbolArr64[i].n_value;
      end
      else
      begin
        SymbolType := SymbolArr[i].n_type;
        StringOffset := SymbolArr[i].n_un.n_strx;
        SymbolValue := SymbolArr[i].n_value;
      end;
      if SymbolType and not uint8_t(N_EXT or N_PEXT) = N_SECT then
      begin
        s := pchar(SymbolStr+StringOffset);
        ind := MainDwarfDebugMap.GlobalList.Find(s);
        if assigned(ind) then
          begin
            AddressMap:=MainDwarfDebugMap.GlobalList.ItemFromNode(ind);
            AddressMap.OrgAddr:=SymbolValue;
            AddressMapList.Add(AddressMap);
          end;
      end;
    end;
  end;
end;

initialization
  RegisterImageReaderClass( TDbgMachoDataSource );

  DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} );
  DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );
end.

