
/*--------------------------------------------------------------------*/
/*--- Reading of syms & debug info from Mach-O files.              ---*/
/*---                                                  readmacho.c ---*/
/*--------------------------------------------------------------------*/

/*
   This file is part of Valgrind, a dynamic binary instrumentation
   framework.

   Copyright (C) 2005-2017 Apple Inc.
      Greg Parker gparker@apple.com

   This program 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 3 of the
   License, or (at your option) any later version.

   This program 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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>.

   The GNU General Public License is contained in the file COPYING.
*/

#if defined(VGO_darwin)

#include "pub_core_basics.h"
#include "pub_core_vki.h"
#include "pub_core_libcbase.h"
#include "pub_core_libcprint.h"
#include "pub_core_libcassert.h"
#include "pub_core_libcfile.h"
#include "pub_core_libcproc.h"
#include "pub_core_aspacemgr.h"    /* for mmaping debuginfo files */
#include "pub_core_mach.h"         /* VG_(dyld_cache_get_slide) */
#include "pub_core_machine.h"      /* VG_ELF_CLASS */
#include "pub_core_options.h"
#include "pub_core_oset.h"
#include "pub_core_tooliface.h"    /* VG_(needs) */
#include "pub_core_xarray.h"
#include "pub_core_clientstate.h"
#include "pub_core_debuginfo.h"

#include "priv_misc.h"
#include "priv_image.h"
#include "priv_d3basics.h"
#include "priv_tytypes.h"
#include "priv_storage.h"
#include "priv_readmacho.h"
#include "priv_readdwarf.h"
#include "priv_readdwarf3.h"

/* --- !!! --- EXTERNAL HEADERS start --- !!! --- */
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/fat.h>
/* --- !!! --- EXTERNAL HEADERS end --- !!! --- */

#if VG_WORDSIZE == 4
# define MAGIC MH_MAGIC
# define MACH_HEADER mach_header
# define LC_SEGMENT_CMD LC_SEGMENT
# define SEGMENT_COMMAND segment_command
# define SECTION section
# define NLIST nlist
#else
# define MAGIC MH_MAGIC_64
# define MACH_HEADER mach_header_64
# define LC_SEGMENT_CMD LC_SEGMENT_64
# define SEGMENT_COMMAND segment_command_64
# define SECTION section_64
# define NLIST nlist_64
#endif


/*------------------------------------------------------------*/
/*---                                                      ---*/
/*--- Mach-O file mapping/unmapping helpers                ---*/
/*---                                                      ---*/
/*------------------------------------------------------------*/

static Int count_rw_loads(const struct load_command* macho_load_commands, unsigned int ncmds)
{
  Int rw_loads = 0;
  const struct load_command* lc = (const struct load_command*)macho_load_commands;
  for (unsigned int i = 0U; i < ncmds; ++i) {
      if (lc->cmd == LC_SEGMENT_CMD) {
        const struct SEGMENT_COMMAND* sc = (const struct SEGMENT_COMMAND*)lc;
        if (sc->initprot == 3 && sc->filesize
#if DARWIN_VERS >= DARWIN_10_15
// FIXME: somehow __DATA_CONST appears as rw- in most binaries in macOS 10.15
// so we ignore it otherwise some binaries don't get symbols
            && VG_(strcmp)(sc->segname, "__DATA_CONST") != 0
#endif
        ) {
          rw_loads += 1;
        }
      }
      const char* tmp = (const char*)lc + lc->cmdsize;
      lc = (const struct load_command*)tmp;
  }
  return rw_loads;
}

static Bool check_fat_macho_and_get_rw_loads(const void* macho_header, Int* rw_loads)
{
  const struct fat_header*  fh_be = (const struct fat_header*)macho_header;
  vg_assert(fh_be);
  if (VG_(ntohl)(fh_be->magic) == FAT_MAGIC) {
     // @todo PJF not yet handled, previous behaviour was to assume that the count is 1
     *rw_loads = 1;
     return True;
  }
  return False;
}

/* A DiSlice is used to handle the thin/fat distinction for MachO images.
   (1) the entire mapped-in ("primary") image, fat headers, kitchen sink,
       whatnot: the entire file.  This is the DiImage* that is the backing
       for the DiSlice.
   (2) the Mach-O object of interest, which is presumably somewhere inside
       the primary image.  map_image_aboard() below, which generates this
       info, will carefully check that the macho_ fields denote a section of
       memory that falls entirely inside the primary image.
*/

STATIC_ASSERT(sizeof(struct fat_header) <= sizeof(struct MACH_HEADER));

Bool ML_(check_macho_and_get_rw_loads)( Int fd, Int* rw_loads )
{
   vg_assert(rw_loads);

   HChar macho_header[sizeof(struct MACH_HEADER)];
   SysRes preadres = VG_(pread)( fd, macho_header, sizeof(struct MACH_HEADER), 0 );

   if (sr_isError(preadres) || sr_Res(preadres) < sizeof(struct MACH_HEADER)) {
      return False;
   }

   if (check_fat_macho_and_get_rw_loads(macho_header, rw_loads)) {
      return True;
   }

   const struct MACH_HEADER* mh = (const struct MACH_HEADER*)macho_header;
   vg_assert(mh);
   if (mh->magic != MAGIC) {
     return False;
   }

   HChar* macho_load_commands = ML_(dinfo_zalloc)("di.readmacho.macho_load_commands", mh->sizeofcmds);
   preadres = VG_(pread)( fd, macho_load_commands, mh->sizeofcmds, sizeof(struct MACH_HEADER) );
   if (sr_isError(preadres) || sr_Res(preadres) < mh->sizeofcmds) {
       ML_(dinfo_free)(macho_load_commands);
       return False;
   }
   *rw_loads = count_rw_loads((const struct load_command*)macho_load_commands, mh->ncmds);
   ML_(dinfo_free)(macho_load_commands);
   return True;
}

Bool ML_(check_macho_and_get_rw_loads_from_memory)( const void* buf, SizeT szB, Int* rw_loads )
{
  vg_assert(buf);
  vg_assert(rw_loads);

  if (szB < sizeof(struct fat_header)) {
    return False;
  }
  if (check_fat_macho_and_get_rw_loads(buf, rw_loads)) {
    return True;
  }

  const struct MACH_HEADER* mh = (const struct MACH_HEADER*)buf;
  vg_assert(mh);
  if (mh->magic != MAGIC) {
    return False;
  }

  *rw_loads = count_rw_loads((const struct load_command*)((const HChar*)mh + sizeof(struct MACH_HEADER)), mh->ncmds);
  return True;
}


/* Unmap an image mapped in by map_image_aboard. */
static void unmap_image ( /*MOD*/DiSlice* sli )
{
   vg_assert(sli);
   if (ML_(sli_is_valid)(*sli)) {
      ML_(img_done)(sli->img);
      *sli = DiSlice_INVALID;
   }
}

/* Open the given file, find the thin part if necessary, do some
   checks, and return a DiSlice containing details of both the thin
   part and (implicitly, via the contained DiImage*) the fat part.
   returns DiSlice_INVALID if it fails.  If it succeeds, the returned
   slice is guaranteed to refer to a valid(ish) Mach-O image. */
static DiSlice map_image_aboard ( DebugInfo* di, /* only for err msgs */
                                  const HChar* filename,
                                  const DebugInfoMapping* rx_map )
{
   DiSlice sli = DiSlice_INVALID;

   /* First off, try to map the thing in. */
   DiImage* mimg;

   if (rx_map != NULL) {
     // FIXME: basically, we make a slice from the place where the mach_header is until the end of the memory space
     // unfortunately, all the data needed for parsing from the DSC is spread across many places in memory
     // and there is no way to know for sure the size of the DSC perfectly, so this is the best method at the moment
     // and it's _very_ unsafe
#if (DARWIN_VERS >= DARWIN_10_15)
     mimg = ML_(img_from_memory)(rx_map->avma, MACH_DSC_END - rx_map->avma, filename);
#endif
   } else {
     mimg = ML_(img_from_local_file)(filename);
   }
   if (mimg == NULL) {
      VG_(message)(Vg_UserMsg, "warning: connection to image %s failed\n",
                               filename );
      VG_(message)(Vg_UserMsg, "         no symbols or debug info loaded\n" );
      return DiSlice_INVALID;
   }

   /* Now we have a viable DiImage* for it.  Look for the embedded
      Mach-O object.  If not findable, close the image and fail. */
   DiOffT            fh_be_ioff = 0;
   struct fat_header fh_be;
   struct fat_header fh;

   // Assume initially that we have a thin image, and narrow
   // the bounds if it turns out to be fat.  This stores |mimg| as
   // |sli.img|, so NULL out |mimg| after this point, for the sake of
   // clarity.
   sli  = ML_(sli_from_img)(mimg);
   mimg = NULL;

   // Check for fat header.
   if (ML_(img_size)(sli.img) < sizeof(struct fat_header)) {
      ML_(symerr)(di, True, "Invalid Mach-O file (0 too small).");
      goto close_and_fail;
   }

   // Fat header is always BIG-ENDIAN
   ML_(img_get)(&fh_be, sli.img, fh_be_ioff, sizeof(fh_be));
   VG_(memset)(&fh, 0, sizeof(fh));
   fh.magic     = VG_(ntohl)(fh_be.magic);
   fh.nfat_arch = VG_(ntohl)(fh_be.nfat_arch);
   if (fh.magic == FAT_MAGIC) {
      // Look for a good architecture.
      if (ML_(img_size)(sli.img) < sizeof(struct fat_header)
                                   + fh.nfat_arch * sizeof(struct fat_arch)) {
         ML_(symerr)(di, True, "Invalid Mach-O file (1 too small).");
         goto close_and_fail;
      }
      DiOffT arch_be_ioff;
      Int    f;
      for (f = 0, arch_be_ioff = sizeof(struct fat_header);
           f < fh.nfat_arch;
           f++, arch_be_ioff += sizeof(struct fat_arch)) {
#        if defined(VGA_ppc)
         Int cputype = CPU_TYPE_POWERPC;
#        elif defined(VGA_ppc64be)
         Int cputype = CPU_TYPE_POWERPC64BE;
#        elif defined(VGA_ppc64le)
         Int cputype = CPU_TYPE_POWERPC64LE;
#        elif defined(VGA_x86)
         Int cputype = CPU_TYPE_X86;
#        elif defined(VGA_amd64)
         Int cputype = CPU_TYPE_X86_64;
#        elif defined(VGA_arm64)
         Int cputype = CPU_TYPE_ARM64;
#        else
#          error "unknown architecture"
#        endif
         struct fat_arch arch_be;
         struct fat_arch arch;
         ML_(img_get)(&arch_be, sli.img, arch_be_ioff, sizeof(arch_be));
         VG_(memset)(&arch, 0, sizeof(arch));
         arch.cputype    = VG_(ntohl)(arch_be.cputype);
         arch.cpusubtype = VG_(ntohl)(arch_be.cpusubtype);
         arch.offset     = VG_(ntohl)(arch_be.offset);
         arch.size       = VG_(ntohl)(arch_be.size);
         if (arch.cputype == cputype) {
            if (ML_(img_size)(sli.img) < arch.offset + arch.size) {
               ML_(symerr)(di, True, "Invalid Mach-O file (2 too small).");
               goto close_and_fail;
            }
            /* Found a suitable arch.  Narrow down the slice accordingly. */
            sli.ioff = arch.offset;
            sli.szB  = arch.size;
            break;
         }
      }
      if (f == fh.nfat_arch) {
         ML_(symerr)(di, True,
                     "No acceptable architecture found in fat file.");
         goto close_and_fail;
      }
   }

   /* Sanity check what we found. */

   /* assured by logic above */
   vg_assert(ML_(img_size)(sli.img) >= sizeof(struct fat_header));

   if (sli.szB < sizeof(struct MACH_HEADER)) {
      ML_(symerr)(di, True, "Invalid Mach-O file (3 too small).");
      goto close_and_fail;
   }

   if (sli.szB > ML_(img_size)(sli.img)) {
      ML_(symerr)(di, True, "Invalid Mach-O file (thin bigger than fat).");
      goto close_and_fail;
   }

   if (sli.ioff >= 0 && sli.ioff + sli.szB <= ML_(img_size)(sli.img)) {
      /* thin entirely within fat, as expected */
   } else {
      ML_(symerr)(di, True, "Invalid Mach-O file (thin not inside fat).");
      goto close_and_fail;
   }

   /* Peer at the Mach header for the thin object, starting at the
      beginning of the slice, to check it's at least marginally
      sane. */
   struct MACH_HEADER mh;
   ML_(cur_read_get)(&mh, ML_(cur_from_sli)(sli), sizeof(mh));
   if (mh.magic != MAGIC) {
      ML_(symerr)(di, True, "Invalid Mach-O file (bad magic).");
      goto close_and_fail;
   }

   if (sli.szB < sizeof(struct MACH_HEADER) + mh.sizeofcmds) {
      ML_(symerr)(di, True, "Invalid Mach-O file (4 too small).");
      goto close_and_fail;
   }

   /* "main image is plausible" */
   vg_assert(sli.img);
   vg_assert(ML_(img_size)(sli.img) > 0);
   /* "thin image exists and is a sub-part (or all) of main image" */
   vg_assert(sli.ioff >= 0);
   vg_assert(sli.szB > 0);
   vg_assert(sli.ioff + sli.szB <= ML_(img_size)(sli.img));
   return sli;  /* success */
   /*NOTREACHED*/

  close_and_fail:
   unmap_image(&sli);
   return DiSlice_INVALID; /* bah! */
}


/*------------------------------------------------------------*/
/*---                                                      ---*/
/*--- Mach-O symbol table reading                          ---*/
/*---                                                      ---*/
/*------------------------------------------------------------*/

static
void add_symbol( /*OUT*/XArray* /* DiSym */ syms,
                 struct _DebugInfo* di,
                 struct NLIST* nl, Addr sym_addr,
                 const HChar* prefix,
                 DiCursor strtab_cur, UInt strtab_sz )
{
  DiSym  disym;

  // "start_according_to_valgrind"
  static const HChar* s_a_t_v = NULL; /* do not make non-static */

  Bool inside_text = di->text_present && sym_addr >= di->text_avma && sym_addr < di->text_avma + di->text_size;
  Bool inside_data = di->data_present && sym_addr >= di->data_avma && sym_addr < di->data_avma + di->data_size;
  Bool inside_d_data = di->sdata_present && sym_addr >= di->sdata_avma && sym_addr < di->sdata_avma + di->sdata_size;

  if (di->trace_symtab) {
      HChar* str = ML_(cur_read_strdup)(
                      ML_(cur_plus)(strtab_cur, nl->n_un.n_strx),
                      "di.read_symtab.1");
      VG_(printf)("nlist raw: avma %010lx  %s in %s %s\n",
                  sym_addr, str,
                  inside_text ? "__TEXT" : inside_data ? "__DATA" : inside_d_data ? "__DATA_DIRTY" : "???",
                  prefix
      );
      ML_(dinfo_free)(str);
  }

  /* If no part of the symbol falls within the mapped range,
      ignore it. */
  if (!inside_text && !inside_data && !inside_d_data) {
      return;
  }

  /* skip names which point outside the string table;
      following these risks segfaulting Valgrind */
  if (nl->n_un.n_strx < 0 || nl->n_un.n_strx >= strtab_sz) {
      return;
  }

  HChar* name
      = ML_(cur_read_strdup)( ML_(cur_plus)(strtab_cur, nl->n_un.n_strx),
                              "di.read_symtab.2");

  /* skip nameless symbols; these appear to be common, but
      useless */
  if (*name == 0) {
      ML_(dinfo_free)(name);
      return;
  }

  if (prefix[0]) {
      HChar* newname = ML_(dinfo_zalloc)("di.read_symtab.3",
                                         VG_(strlen)(prefix) + VG_(strlen)(name));
      VG_(strcpy)(newname, prefix);
      VG_(strcat)(newname, name);
      ML_(dinfo_free)(name);
      name = newname;
  }

  VG_(bzero_inline)(&disym, sizeof(disym));
  disym.avmas.main = sym_addr;
  SET_TOCPTR_AVMA(disym, 0);
  SET_LOCAL_EP_AVMA(disym, 0);
  disym.pri_name   = ML_(addStr)(di, name, -1);
  disym.sec_names  = NULL;
  disym.size       = // let canonicalize fix it
                      di->text_avma+di->text_size - sym_addr;
  disym.isText     = inside_text;
  disym.isIFunc    = False;
  disym.isGlobal   = (nl->n_type & N_EXT) != 0;
  // Lots of user function names get prepended with an underscore.  Eg. the
  // function 'f' becomes the symbol '_f'.  And the "below main"
  // function is called "start".  So we skip the leading underscore, and
  // if we see 'start' and --show-below-main=no, we rename it as
  // "start_according_to_valgrind", which makes it easy to spot later
  // and display as "(below main)".
  if (disym.pri_name[0] == '_') {
      disym.pri_name++;
  }
  else if (!VG_(clo_show_below_main) && VG_STREQ(disym.pri_name, "start")) {
      if (s_a_t_v == NULL)
        s_a_t_v = ML_(addStr)(di, "start_according_to_valgrind", -1);
      vg_assert(s_a_t_v);
      disym.pri_name = s_a_t_v;
  }

  vg_assert(disym.pri_name);
  VG_(addToXA)( syms, &disym );
  ML_(dinfo_free)(name);
}

/* Read a symbol table (nlist).  Add the resulting candidate symbols
   to 'syms'; the caller will post-process them and hand them off to
   ML_(addSym) itself. */
static
void read_symtab( /*OUT*/XArray* /* DiSym */ syms,
                  struct _DebugInfo* di,
                  DiCursor symtab_cur, UInt symtab_count,
                  DiCursor strtab_cur, UInt strtab_sz )
{
   Int    i;

   for (i = 0; i < symtab_count; i++) {
      struct NLIST nl;
      ML_(cur_read_get)(&nl,
                        ML_(cur_plus)(symtab_cur, i * sizeof(struct NLIST)),
                        sizeof(nl));

      Addr sym_addr = 0;
      if ((nl.n_type & N_TYPE) == N_SECT) {
         sym_addr = di->text_bias + nl.n_value;
      /*} else if ((nl.n_type & N_TYPE) == N_ABS) {
         GrP fixme don't ignore absolute symbols?
         sym_addr = nl.n_value; */
      } else {
         continue;
      }

      add_symbol(syms, di, &nl, sym_addr, "", strtab_cur, strtab_sz);
   }
}

// See reason for disabling later in this file
#if 0
static
void add_indirect_symbols( /*OUT*/XArray* /* DiSym */ syms,
                  struct _DebugInfo* di,
                  struct SECTION* section, SizeT entry_size,
                  DiCursor indir_cur, UInt indir_count,
                  DiCursor symtab_cur, UInt symtab_count,
                  DiCursor strtab_cur, UInt strtab_sz )
{
   for (Int i = 0; i < indir_count; i++) {
      Int index;
      struct NLIST nl;
      ML_(cur_read_get)(&index,
                        ML_(cur_plus)(indir_cur, i * sizeof(Int)),
                        sizeof(index));
      ML_(cur_read_get)(&nl,
                        ML_(cur_plus)(symtab_cur, index * sizeof(struct NLIST)),
                        sizeof(nl));

      Addr sym_addr = di->text_bias + section->addr + i * entry_size;

      add_symbol(syms, di, &nl, sym_addr, section->sectname, strtab_cur, strtab_sz);
   }
}
#endif

/* Compare DiSyms by their start address, and for equal addresses, use
   the primary name as a secondary sort key. */
static Int cmp_DiSym_by_start_then_name ( const void* v1, const void* v2 )
{
   const DiSym* s1 = (const DiSym*)v1;
   const DiSym* s2 = (const DiSym*)v2;
   if (s1->avmas.main < s2->avmas.main) return -1;
   if (s1->avmas.main > s2->avmas.main) return 1;
   return VG_(strcmp)(s1->pri_name, s2->pri_name);
}

/* 'cand' is a bunch of candidate symbols obtained by reading
   nlist-style symbol table entries.  Their ends may overlap, so sort
   them and truncate them accordingly.  The code in this routine is
   copied almost verbatim from read_symbol_table() in readxcoff.c. */
static void tidy_up_cand_syms ( /*MOD*/XArray* /* of DiSym */ syms,
                                Bool trace_symtab )
{
   Word nsyms, i, j, k, m;

   nsyms = VG_(sizeXA)(syms);

   VG_(setCmpFnXA)(syms, cmp_DiSym_by_start_then_name);
   VG_(sortXA)(syms);

   /* We only know for sure the start addresses (actual VMAs) of
      symbols, and an overestimation of their end addresses.  So sort
      by start address, then clip each symbol so that its end address
      does not overlap with the next one along.

      There is a small refinement: if a group of symbols have the same
      address, treat them as a group: find the next symbol along that
      has a higher start address, and clip all of the group
      accordingly.  This clips the group as a whole so as not to
      overlap following symbols.  This leaves prefersym() in
      storage.c, which is not nlist-specific, to later decide which of
      the symbols in the group to keep.

      Another refinement is that we need to get rid of symbols which,
      after clipping, have identical starts, ends, and names.  So the
      sorting uses the name as a secondary key.
   */

   for (i = 0; i < nsyms; i++) {
      for (k = i+1;
           k < nsyms
             && ((DiSym*)VG_(indexXA)(syms,i))->avmas.main
                 == ((DiSym*)VG_(indexXA)(syms,k))->avmas.main;
           k++)
         ;
      /* So now [i .. k-1] is a group all with the same start address.
         Clip their ending addresses so they don't overlap [k].  In
         the normal case (no overlaps), k == i+1. */
      if (k < nsyms) {
         DiSym* next = (DiSym*)VG_(indexXA)(syms,k);
         for (m = i; m < k; m++) {
            DiSym* here = (DiSym*)VG_(indexXA)(syms,m);
            vg_assert(here->avmas.main < next->avmas.main);
            if (here->avmas.main + here->size > next->avmas.main)
               here->size = next->avmas.main - here->avmas.main;
         }
      }
      i = k-1;
      vg_assert(i <= nsyms);
   }

   j = 0;
   if (nsyms > 0) {
      j = 1;
      for (i = 1; i < nsyms; i++) {
         DiSym *s_j1, *s_j, *s_i;
         vg_assert(j <= i);
         s_j1 = (DiSym*)VG_(indexXA)(syms, j-1);
         s_j  = (DiSym*)VG_(indexXA)(syms, j);
         s_i  = (DiSym*)VG_(indexXA)(syms, i);
         if (s_i->avmas.main != s_j1->avmas.main
             || s_i->size != s_j1->size
             || 0 != VG_(strcmp)(s_i->pri_name, s_j1->pri_name)) {
            *s_j = *s_i;
            j++;
         } else {
            if (trace_symtab)
               VG_(printf)("nlist cleanup: dump duplicate avma %010lx  %s\n",
                           s_i->avmas.main, s_i->pri_name );
         }
      }
   }
   vg_assert(j >= 0 && j <= nsyms);
   VG_(dropTailXA)(syms, nsyms - j);
}


/*------------------------------------------------------------*/
/*---                                                      ---*/
/*--- Mach-O top-level processing                          ---*/
/*---                                                      ---*/
/*------------------------------------------------------------*/

#if !defined(APPLE_DSYM_EXT_AND_SUBDIRECTORY)
#define APPLE_DSYM_EXT_AND_SUBDIRECTORY ".dSYM/Contents/Resources/DWARF/"
#endif


static Bool file_exists_p(const HChar *path)
{
   struct vg_stat sbuf;
   SysRes res = VG_(stat)(path, &sbuf);
   return sr_isError(res) ? False : True;
}


/* Search for an existing dSYM file as a possible separate debug file.
   Adapted from gdb. */
static HChar *
find_separate_debug_file (const HChar *executable_name)
{
   const HChar *basename_str;
   HChar *dot_ptr;
   HChar *slash_ptr;
   HChar *dsymfile;

   /* Make sure the object file name itself doesn't contain ".dSYM" in it or we
      will end up with an infinite loop where after we add a dSYM symbol file,
      it will then enter this function asking if there is a debug file for the
      dSYM file itself.  */
   if (VG_(strcasestr) (executable_name, ".dSYM") == NULL)
   {
      /* Check for the existence of a .dSYM file for a given executable.  */
      basename_str = VG_(basename) (executable_name);
      dsymfile = ML_(dinfo_zalloc)("di.readmacho.dsymfile",
                    VG_(strlen) (executable_name)
                    + VG_(strlen) (APPLE_DSYM_EXT_AND_SUBDIRECTORY)
                    + VG_(strlen) (basename_str)
                    + 1
                 );

      /* First try for the dSYM in the same directory as the original file.  */
      VG_(strcpy) (dsymfile, executable_name);
      VG_(strcat) (dsymfile, APPLE_DSYM_EXT_AND_SUBDIRECTORY);
      VG_(strcat) (dsymfile, basename_str);

      if (file_exists_p (dsymfile))
         return dsymfile;

      /* Now search for any parent directory that has a '.' in it so we can find
         Mac OS X applications, bundles, plugins, and any other kinds of files.
         Mac OS X application bundles wil have their program in
         "/some/path/MyApp.app/Contents/MacOS/MyApp" (or replace ".app" with
         ".bundle" or ".plugin" for other types of bundles).  So we look for any
         prior '.' character and try appending the apple dSYM extension and
         subdirectory and see if we find an existing dSYM file (in the above
         MyApp example the dSYM would be at either:
         "/some/path/MyApp.app.dSYM/Contents/Resources/DWARF/MyApp" or
         "/some/path/MyApp.dSYM/Contents/Resources/DWARF/MyApp".  */
      VG_(strcpy) (dsymfile, VG_(dirname) (executable_name));
      while ((dot_ptr = VG_(strrchr) (dsymfile, '.')))
      {
         /* Find the directory delimiter that follows the '.' character since
            we now look for a .dSYM that follows any bundle extension.  */
         slash_ptr = VG_(strchr) (dot_ptr, '/');
         if (slash_ptr)
         {
             /* NULL terminate the string at the '/' character and append
                the path down to the dSYM file.  */
            *slash_ptr = '\0';
            VG_(strcat) (slash_ptr, APPLE_DSYM_EXT_AND_SUBDIRECTORY);
            VG_(strcat) (slash_ptr, basename_str);
            if (file_exists_p (dsymfile))
               return dsymfile;
         }

         /* NULL terminate the string at the '.' character and append
            the path down to the dSYM file.  */
         *dot_ptr = '\0';
         VG_(strcat) (dot_ptr, APPLE_DSYM_EXT_AND_SUBDIRECTORY);
         VG_(strcat) (dot_ptr, basename_str);
         if (file_exists_p (dsymfile))
            return dsymfile;

         /* NULL terminate the string at the '.' locatated by the strrchr()
            function again.  */
         *dot_ptr = '\0';

         /* We found a previous extension '.' character and did not find a
            dSYM file so now find previous directory delimiter so we don't
            try multiple times on a file name that may have a version number
            in it such as "/some/path/MyApp.6.0.4.app".  */
         slash_ptr = VG_(strrchr) (dsymfile, '/');
         if (!slash_ptr)
            break;
         /* NULL terminate the string at the previous directory character
            and search again.  */
         *slash_ptr = '\0';
      }
   }

   return NULL;
}


/* Given a DiSlice covering the entire Mach-O thin image, find the
   DiSlice for the specified (segname, sectname) pairing, if
   possible.  Also return the section's .addr field in *svma if
   svma is non-NULL. */
static DiSlice getsectdata ( DiSlice img,
                             const HChar *segname, const HChar *sectname,
                             /*OUT*/Addr* svma )
{
   DiCursor cur = ML_(cur_from_sli)(img);

   struct MACH_HEADER mh;
   ML_(cur_step_get)(&mh, &cur, sizeof(mh));

   Int c;
   for (c = 0; c < mh.ncmds; c++) {
      struct load_command cmd;
      ML_(cur_read_get)(&cmd, cur, sizeof(cmd));
      if (cmd.cmd == LC_SEGMENT_CMD) {
         struct SEGMENT_COMMAND seg;
         ML_(cur_read_get)(&seg, cur, sizeof(seg));
         if (0 == VG_(strncmp)(&seg.segname[0],
                               segname, sizeof(seg.segname))) {
            DiCursor sects_cur = ML_(cur_plus)(cur, sizeof(seg));
            Int s;
            for (s = 0; s < seg.nsects; s++) {
               struct SECTION sect;
               ML_(cur_step_get)(&sect, &sects_cur, sizeof(sect));
               if (0 == VG_(strncmp)(sect.sectname, sectname,
                                     sizeof(sect.sectname))) {
                  DiSlice res = img;
                  res.ioff = sect.offset;
                  res.szB = sect.size;
                  if (svma) *svma = (Addr)sect.addr;
                  return res;
               }
            }

         }
      }
      cur = ML_(cur_plus)(cur, cmd.cmdsize);
   }

   return DiSlice_INVALID;
}


/* Brute force just simply search for uuid[0..15] in |sli| */
static Bool check_uuid_matches ( DiSlice sli, UChar* uuid )
{
   if (sli.szB < 16)
      return False;

   /* Work through the slice in 1 KB chunks. */
   UChar  first    = uuid[0];
   DiOffT min_off  = sli.ioff;
   DiOffT max1_off = sli.ioff + sli.szB;
   DiOffT curr_off = min_off;
   vg_assert(min_off < max1_off);
   while (1) {
      vg_assert(curr_off >= min_off && curr_off <= max1_off);
      if (curr_off == max1_off) break;
      DiOffT avail = max1_off - curr_off;
      vg_assert(avail > 0 && avail <= max1_off);
      if (avail > 1024) avail = 1024;
      UChar buf[1024];
      SizeT nGot = ML_(img_get_some)(buf, sli.img, curr_off, avail);
      vg_assert(nGot >= 1 && nGot <= avail);
      UInt i;
      /* Scan through the 1K chunk we got, looking for the start char. */
      for (i = 0; i < (UInt)nGot; i++) {
         if (LIKELY(buf[i] != first))
            continue;
         /* first char matches.  See if we can get 16 bytes at this
            offset, and compare. */
         if (curr_off + i < max1_off && max1_off - (curr_off + i) >= 16) {
            UChar buff16[16];
            ML_(img_get)(&buff16[0], sli.img, curr_off + i, 16);
            if (0 == VG_(memcmp)(&buff16[0], &uuid[0], 16))
               return True;
         }
      }
      curr_off += nGot;
   }
   return False;
}


/* Heuristic kludge: return True if this looks like an installed
   standard library; hence we shouldn't consider automagically running
   dsymutil on it. */
static Bool is_systemish_library_name ( const HChar* name )
{
   vg_assert(name);
   if (0 == VG_(strncasecmp)(name, "/usr/", 5)
       || 0 == VG_(strncasecmp)(name, "/bin/", 5)
       || 0 == VG_(strncasecmp)(name, "/sbin/", 6)
       || 0 == VG_(strncasecmp)(name, "/opt/", 5)
       || 0 == VG_(strncasecmp)(name, "/sw/", 4)
       || 0 == VG_(strncasecmp)(name, "/System/", 8)
       || 0 == VG_(strncasecmp)(name, "/Library/", 9)
       || 0 == VG_(strncasecmp)(name, "/Applications/", 14)) {
      return True;
   } else {
      return False;
   }
}


Bool ML_(read_macho_debug_info)( struct _DebugInfo* di )
{
   DiSlice  msli         = DiSlice_INVALID; // the main image
   DiSlice  dsli         = DiSlice_INVALID; // the debuginfo image
   DiCursor sym_cur      = DiCursor_INVALID;
   DiCursor dysym_cur    = DiCursor_INVALID;
   HChar*   dsymfilename = NULL;
   Bool     have_uuid    = False;
   Bool     from_memory  = False; // True if we're reading from DSC
   Bool     have_rw      = False;
   Addr     kernel_slide = 0; // Used when from_memory is True
   UChar    uuid[16];
   Word     i;
   struct SEGMENT_COMMAND     data_const = {.cmd = 0};
   struct SEGMENT_COMMAND     link_edit = {.cmd = 0};
   const DebugInfoMapping* rx_map = NULL;
   const DebugInfoMapping* rw_map = NULL;

   /* mmap the object file to look for di->soname and di->text_bias
      and uuid and nlist */

   /* This should be ensured by our caller (that we're in the accept
      state). */
   vg_assert(di->fsm.have_rx_map);
#if DARWIN_VERS >= DARWIN_11_00
   if (di->from_memory) {
     from_memory = True;
     kernel_slide = VG_(dyld_cache_get_slide)();
   }
#endif
   if (di->fsm.rw_map_count) {
      have_rw = True;
   }

   for (i = 0; i < VG_(sizeXA)(di->fsm.maps); i++) {
      const DebugInfoMapping* map = VG_(indexXA)(di->fsm.maps, i);
      if (map->rx && !rx_map)
         rx_map = map;
      if (map->rw && !rw_map)
         rw_map = map;
      if (rx_map && (rw_map || !have_rw))
         break;
   }
   vg_assert(rx_map);
   vg_assert(!have_rw || rw_map);

   if (VG_(clo_verbosity) > 1) {
      if (!have_rw) {
        VG_(message)(Vg_DebugMsg,
                    "%s (rx at %#lx)\n", di->fsm.filename,
                    rx_map->avma);
      } else {
      VG_(message)(Vg_DebugMsg,
                   "%s (rx at %#lx, rw at %#lx)\n", di->fsm.filename,
                   rx_map->avma, rw_map->avma );
      }
   }

   VG_(memset)(&uuid, 0, sizeof(uuid));

   msli = map_image_aboard( di, di->fsm.filename, from_memory ? rx_map : NULL );
   if (!ML_(sli_is_valid)(msli)) {
      ML_(symerr)(di, False, "Connect to main image failed.");
      goto fail;
   }

   vg_assert(msli.img != NULL && msli.szB > 0);

   /* Poke around in the Mach-O header, to find some important
      stuff. */
   // Find LC_SYMTAB and LC_DYSYMTAB, if present.
   // Read di->soname from LC_ID_DYLIB if present,
   //    or from LC_ID_DYLINKER if present,
   //    or use "NONE".
   // Get di->text_bias (aka slide) based on the corresponding LC_SEGMENT
   // Get uuid for later dsym search

   di->text_bias = 0;

   {
      DiCursor cmd_cur = ML_(cur_from_sli)(msli);

      struct MACH_HEADER mh;
      ML_(cur_step_get)(&mh, &cmd_cur, sizeof(mh));

      /* Now cur_cmd points just after the Mach header, right at the
         start of the load commands, which is where we need it to start
         the following loop. */

      Int c;
      for (c = 0; c < mh.ncmds; c++) {
         struct load_command cmd;
         ML_(cur_read_get)(&cmd, cmd_cur, sizeof(cmd));

         if (cmd.cmd == LC_SYMTAB) {
            sym_cur = cmd_cur;
         }
         else if (cmd.cmd == LC_DYSYMTAB) {
            dysym_cur = cmd_cur;
         }
         else if (cmd.cmd == LC_ID_DYLIB && mh.filetype == MH_DYLIB) {
            // GrP fixme bundle?
            struct dylib_command dcmd;
            ML_(cur_read_get)(&dcmd, cmd_cur, sizeof(dcmd));
            DiCursor dylibname_cur
               = ML_(cur_plus)(cmd_cur, dcmd.dylib.name.offset);
            HChar* dylibname
               = ML_(cur_read_strdup)(dylibname_cur, "di.rmdi.1");
            HChar* soname = VG_(strrchr)(dylibname, '/');
            if (!soname) soname = dylibname;
            else soname++;
            di->soname = ML_(dinfo_strdup)("di.readmacho.dylibname",
                                           soname);
            ML_(dinfo_free)(dylibname);
         }
         else if (cmd.cmd==LC_ID_DYLINKER  &&  mh.filetype==MH_DYLINKER) {
            struct dylinker_command dcmd;
            ML_(cur_read_get)(&dcmd, cmd_cur, sizeof(dcmd));
            DiCursor dylinkername_cur
               = ML_(cur_plus)(cmd_cur, dcmd.name.offset);
            HChar* dylinkername
               = ML_(cur_read_strdup)(dylinkername_cur, "di.rmdi.2");
            HChar* soname = VG_(strrchr)(dylinkername, '/');
            if (!soname) soname = dylinkername;
            else soname++;
            di->soname = ML_(dinfo_strdup)("di.readmacho.dylinkername",
                                           soname);
            ML_(dinfo_free)(dylinkername);
         }

         // A comment from Julian about why varinfo[35] fail:
         //
         // My impression is, from comparing the output of otool -l for these
         // executables with the logic in ML_(read_macho_debug_info),
         // specifically the part that begins "else if (cmd->cmd ==
         // LC_SEGMENT_CMD) {", that it's a complete hack which just happens
         // to work ok for text symbols.  In particular, it appears to assume
         // that in a "struct load_command" of type LC_SEGMENT_CMD, the first
         // "struct SEGMENT_COMMAND" inside it is going to contain the info we
         // need.  However, otool -l shows, and also the Apple docs state,
         // that a struct load_command may contain an arbitrary number of
         // struct SEGMENT_COMMANDs, so I'm not sure why it's OK to merely
         // snarf the first.  But I'm not sure about this.
         //
         // The "Try for __DATA" block below simply adds acquisition of data
         // svma/bias values using the same assumption.  It also needs
         // (probably) to deal with bss sections, but I don't understand how
         // this all ties together really, so it requires further study.
         //
         // If you can get your head around the relationship between MachO
         // segments, sections and load commands, this might be relatively
         // easy to fix properly.
         //
         // Basically we need to come up with plausible numbers for di->
         // {text,data,bss}_{avma,svma}, from which the _bias numbers are
         // then trivially derived.  Then I think the debuginfo reader should
         // work pretty well.
         else if (cmd.cmd == LC_SEGMENT_CMD) {
            struct SEGMENT_COMMAND seg;
            ML_(cur_read_get)(&seg, cmd_cur, sizeof(seg));
            /* Try for __TEXT */
            if (!di->text_present
                && 0 == VG_(strcmp)(&seg.segname[0], "__TEXT")
                /* DDD: is the  next line a kludge? -- JRS */
                && (from_memory || seg.fileoff == 0) && seg.filesize != 0) {
               di->text_present = True;
               di->text_svma = (Addr)seg.vmaddr;
               di->text_avma = rx_map->avma;
               di->text_size = seg.vmsize;
               di->text_bias = di->text_avma - di->text_svma;
               /* Make the _debug_ values be the same as the
                  svma/bias for the primary object, since there is
                  no secondary (debuginfo) object, but nevertheless
                  downstream biasing of Dwarf3 relies on the
                  _debug_ values. */
               di->text_debug_svma = di->text_svma;
               di->text_debug_bias = di->text_bias;
            }
            if (0 == VG_(strcmp)(seg.segname, "__DATA_CONST")) {
               data_const = seg;
            }
            /* Try for __DATA */
            if (have_rw && !di->data_present
                && 0 == VG_(strcmp)(&seg.segname[0], "__DATA")
                /* && DDD:seg->fileoff == 0 */ && seg.filesize != 0) {
               di->data_present = True;
               di->data_svma = (Addr)seg.vmaddr;
               di->data_avma = rw_map->avma;
#if defined(VGO_darwin) && (DARWIN_VERS >= DARWIN_10_15)
               // FIXME: the same mmap contains both __DATA_CONST, __DATA and __DATA_DIRTY
               // this means that symbols in __DATA/__DATA_DIRTY are offset by the size of __DATA_CONST
               // not sure when this started to be an issue so I am going to gate this under arm64 for now
               if (data_const.cmd != 0) {
                  di->data_avma += data_const.vmsize;
               }
#endif
               di->data_size = seg.vmsize;
               di->data_bias = di->data_avma - di->data_svma;
               di->data_debug_svma = di->data_svma;
               di->data_debug_bias = di->data_bias;
            }
            /* We store __DATA_DIRTY inside .sdata (because they correspond somewhat).
               Some binaries have very important information there,
               notably dyld and its dyld_all_image_infos. */
            if (have_rw && !di->sdata_present
                && 0 == VG_(strcmp)(&seg.segname[0], "__DATA_DIRTY")
                /* && DDD:seg->fileoff == 0 */ && seg.filesize != 0) {
               di->sdata_present = True;
               di->sdata_svma = (Addr)seg.vmaddr;
               // FIXME: assumes __DATA was found first (which in practice should be fine)
               di->sdata_avma = rw_map->avma + di->data_size;
#if defined(VGA_arm64)
               // FIXME: the same mmap contains both __DATA_CONST, __DATA and __DATA_DIRTY
               // this means that symbols in __DATA/__DATA_DIRTY are offset by the size of __DATA_CONST
               // not sure when this started to be an issue so I am going to gate this under arm64 for now
               if (data_const.cmd != 0) {
                  di->sdata_avma += data_const.vmsize;
               }
#endif
               di->sdata_size = seg.vmsize;
               di->sdata_bias = di->sdata_avma - di->sdata_svma;
               di->sdata_debug_svma = di->sdata_svma;
               di->sdata_debug_bias = di->sdata_bias;
            }
            /* Try for __LINKEDIT */
            if (0 == VG_(strcmp)(&seg.segname[0], "__LINKEDIT")) {
              link_edit = seg;
            }
         }
         else if (cmd.cmd == LC_UUID) {
             ML_(cur_read_get)(&uuid, cmd_cur, sizeof(uuid));
             have_uuid = True;
         }
         // Move the cursor along
         cmd_cur = ML_(cur_plus)(cmd_cur, cmd.cmdsize);
      }
   }

  if (from_memory && link_edit.cmd == 0) {
    ML_(symerr)(di, False, "Invalid Mach-O file (missing __LINKEDIT).");
    goto fail;
  }

   if (!di->soname) {
      di->soname = ML_(dinfo_strdup)("di.readmacho.noname", "NONE");
   }

   if (di->trace_symtab) {
      VG_(printf)("\n");
      VG_(printf)("SONAME = %s\n", di->soname);
      VG_(printf)("\n");
   }

   /* Now we have the base object to hand.  Read symbols from it. */

   // We already asserted that ..
   vg_assert(msli.img != NULL && msli.szB > 0);

   if (ML_(cur_is_valid)(sym_cur)) {
      // Some binaries (e.g. Valgrind's tools themselves, like memcheck)
      // don't have dynamic symbols because they are static binaries.
      // Let's try to load symbols using a single table.
      Bool has_dynamic = ML_(cur_is_valid)(dysym_cur);

      struct symtab_command   symcmd;
      struct dysymtab_command dysymcmd;

      ML_(cur_read_get)(&symcmd,   sym_cur,   sizeof(symcmd));
      if (has_dynamic) {
        ML_(cur_read_get)(&dysymcmd, dysym_cur, sizeof(dysymcmd));
      }

      /* Read nlist symbol table */
      DiCursor syms = DiCursor_INVALID;
      DiCursor strs = DiCursor_INVALID;
      XArray* /* DiSym */ candSyms = NULL;
      Word nCandSyms;

      // FIXME: there is no real nice way to check this when using DSC
      // the chunk that msli points to is only the mach_header + load_commands
      // but not the actual sections (__TEXT, __DATA, __LINKEDIT, etc)
      // so those checks will always fail because they point to data much further in memory
      // (as DSC groups all this kind of data together for X amount of images)
      if (!from_memory
          && (msli.szB < symcmd.stroff + symcmd.strsize
          || msli.szB < symcmd.symoff + symcmd.nsyms * sizeof(struct NLIST))) {
         ML_(symerr)(di, False, "Invalid Mach-O file (5 too small).");
         goto fail;
      }
      if (has_dynamic
          && (dysymcmd.ilocalsym + dysymcmd.nlocalsym > symcmd.nsyms
          || dysymcmd.iextdefsym + dysymcmd.nextdefsym > symcmd.nsyms)) {
         ML_(symerr)(di, False, "Invalid Mach-O file (bad symbol table).");
         goto fail;
      }

      if (from_memory) {
        // First, we calculate the real position of __LINKEDIT in the DSC by adding the slide
        // Then we get the offset of syms/strings within __LINKEDIT by removing the fileoffset
        // Then we add the __LINKEDIT address
        // Finally we calculate the proper offset of those addresses compared to the mach_header slice
        Addr link_edit_addr = link_edit.vmaddr + kernel_slide;
        syms = ML_(cur_from_sli)(msli);
        syms.ioff = (link_edit_addr + (symcmd.symoff - link_edit.fileoff)) - rx_map->avma;
        strs = ML_(cur_from_sli)(msli);
        strs.ioff = (link_edit_addr + (symcmd.stroff - link_edit.fileoff)) - rx_map->avma;
      } else {
        syms = ML_(cur_plus)(ML_(cur_from_sli)(msli), symcmd.symoff);
        strs = ML_(cur_plus)(ML_(cur_from_sli)(msli), symcmd.stroff);
      }

      if (VG_(clo_verbosity) > 1) {
        if (has_dynamic) {
         VG_(message)(Vg_DebugMsg,
            "   reading syms   from primary file (%u %u)\n",
            dysymcmd.nextdefsym, dysymcmd.nlocalsym );
        } else {
          VG_(message)(Vg_DebugMsg,
              "   reading syms   from primary file (%u)\n",
              symcmd.nsyms );
        }
      }

      /* Read candidate symbols into 'candSyms', so we can truncate
         overlapping ends and generally tidy up, before presenting
         them to ML_(addSym). */
      candSyms = VG_(newXA)(
                    ML_(dinfo_zalloc), "di.readmacho.candsyms.1",
                    ML_(dinfo_free), sizeof(DiSym)
                 );

      if (has_dynamic) {
        // extern symbols
        read_symtab(candSyms,
                    di,
                    ML_(cur_plus)(syms,
                                  dysymcmd.iextdefsym * sizeof(struct NLIST)),
                    dysymcmd.nextdefsym, strs, symcmd.strsize);
        // static and private_extern symbols
        read_symtab(candSyms,
                    di,
                    ML_(cur_plus)(syms,
                                  dysymcmd.ilocalsym * sizeof(struct NLIST)),
                    dysymcmd.nlocalsym, strs, symcmd.strsize);

// Due to the usage of dyld_cache, I am unsure how to properly capture the stubs from there
// moreover we don't have a rw_map for those so loads of logic need to change above.
// This is only really useful when reading librairies from disk which is limited now.
// Finally, there is also a weird overflow of some kind on arm64 macOS 15.
// So I am disabling this for now.
#if 0
        {
          DiCursor cmd_cur = ML_(cur_from_sli)(msli);
          DiCursor indirs = DiCursor_INVALID;

          if (from_memory) {
            Addr link_edit_addr = link_edit.vmaddr + kernel_slide;
            indirs = ML_(cur_from_sli)(msli);
            indirs.ioff = (link_edit_addr + (dysymcmd.indirectsymoff - link_edit.fileoff)) - rx_map->avma;
          } else {
            indirs = ML_(cur_plus)(ML_(cur_from_sli)(msli), dysymcmd.indirectsymoff);
          }

          struct MACH_HEADER mh;
          ML_(cur_step_get)(&mh, &cmd_cur, sizeof(mh));
          for (Int c = 0; c < mh.ncmds; c++) {
            struct load_command cmd;
            ML_(cur_read_get)(&cmd, cmd_cur, sizeof(cmd));

            if (cmd.cmd == LC_SEGMENT_CMD) {
              struct SEGMENT_COMMAND seg;
              ML_(cur_read_get)(&seg, cmd_cur, sizeof(seg));

              for (i = 0; i < seg.nsects; i += 1) {
                DiCursor sect_cur = ML_(cur_plus)(cmd_cur, sizeof(seg));
                struct SECTION sect;
                ML_(cur_read_get)(&sect, sect_cur, sizeof(sect));
                Int indexOfIndirects = sect.reserved1;

                if ((sect.flags & S_SYMBOL_STUBS) == S_SYMBOL_STUBS) {
                  Int sizeOfStub = sect.reserved2;
                  Int amountOfStubs = sect.size / sizeOfStub;
                  if (indexOfIndirects + amountOfStubs > dysymcmd.nindirectsyms) {
                    ML_(symerr)(di, False, "Invalid Mach-O file (invalid stub section).");
                    goto fail;
                  }
                  // add symbols where we have stub assembly
                  add_indirect_symbols(candSyms, di,
                    &sect, sizeOfStub,
                    ML_(cur_plus)(indirs, indexOfIndirects * sizeof(UInt)), amountOfStubs,
                    syms, symcmd.nsyms, strs, symcmd.strsize);
                }
                if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) {
                  Int sizeOfPointer = VG_WORDSIZE;
                  Int amountOfPointers = sect.size / sizeOfPointer;
                  if (indexOfIndirects + amountOfPointers > dysymcmd.nindirectsyms) {
                    ML_(symerr)(di, False, "Invalid Mach-O file (invalid lazy symbol section).");
                    goto fail;
                  }
                  // add symbols where we have lazy symbol pointers
                  add_indirect_symbols(candSyms, di,
                    &sect, sizeOfPointer,
                    ML_(cur_plus)(indirs, indexOfIndirects * sizeof(UInt)), amountOfPointers,
                    syms, symcmd.nsyms, strs, symcmd.strsize);
                }

                sect_cur = ML_(cur_plus)(sect_cur, sizeof(sect));
              }
            }

            cmd_cur = ML_(cur_plus)(cmd_cur, cmd.cmdsize);
          }
        }
#endif
      } else {
        read_symtab(candSyms,
                    di,
                    syms,
                    symcmd.nsyms, strs, symcmd.strsize);
      }

      /* tidy up the cand syms -- trim overlapping ends.  May resize
         candSyms. */
      tidy_up_cand_syms( candSyms, di->trace_symtab );

      /* and finally present them to ML_(addSym) */
      nCandSyms = VG_(sizeXA)( candSyms );
      for (i = 0; i < nCandSyms; i++) {
         DiSym* cand = (DiSym*) VG_(indexXA)( candSyms, i );
         vg_assert(cand->pri_name != NULL);
         vg_assert(cand->sec_names == NULL);
         if (di->trace_symtab)
            VG_(printf)("nlist final: acquire  avma %010lx-%010lx  %s\n",
                        cand->avmas.main, cand->avmas.main + cand->size - 1,
                        cand->pri_name );
         ML_(addSym)( di, cand );
      }
      VG_(deleteXA)( candSyms );
   }

   /* If there's no UUID in the primary, don't even bother to try and
      read any DWARF, since we won't be able to verify it matches.
      Our policy is not to load debug info unless we can verify that
      it matches the primary.  Just declare success at this point.
      And don't complain to the user, since that would cause us to
      complain on objects compiled without -g.  (Some versions of
      XCode are observed to omit a UUID entry for object linked(?)
      without -g.  Others don't appear to omit it.) */
   if (!have_uuid)
      goto success;

   /* mmap the dSYM file to look for DWARF debug info.  If successful,
      use the .macho_img and .macho_img_szB in dsli. */

   dsymfilename = find_separate_debug_file( di->fsm.filename );

   /* Try to load it. */
   if (dsymfilename) {
      Bool valid;

      if (VG_(clo_verbosity) > 1)
         VG_(message)(Vg_DebugMsg, "   dSYM= %s\n", dsymfilename);

      dsli = map_image_aboard( di, dsymfilename, NULL );
      if (!ML_(sli_is_valid)(dsli)) {
         ML_(symerr)(di, False, "Connect to debuginfo image failed "
                                "(first attempt).");
         goto fail;
      }

      /* check it has the right uuid. */
      vg_assert(have_uuid);
      valid = dsli.img && dsli.szB > 0 && check_uuid_matches( dsli, uuid );
      if (valid)
         goto read_the_dwarf;

      if (VG_(clo_verbosity) > 1)
         VG_(message)(Vg_DebugMsg, "   dSYM does not have "
                                   "correct UUID (out of date?)\n");
   }

   /* There was no dsym file, or it doesn't match.  We'll have to try
      regenerating it, unless --dsymutil=no, in which case just complain
      instead. */

   /* If this looks like a lib that we shouldn't run dsymutil on, just
      give up.  (possible reasons: is system lib, or in /usr etc, or
      the dsym dir would not be writable by the user, or we're running
      as root) */
   vg_assert(di->fsm.filename);
   if (is_systemish_library_name(di->fsm.filename))
      goto success;

   if (!VG_(clo_dsymutil)) {
      if (VG_(clo_verbosity) == 1) {
         VG_(message)(Vg_DebugMsg, "%s:\n", di->fsm.filename);
      }
      if (VG_(clo_verbosity) > 0)
         VG_(message)(Vg_DebugMsg, "%sdSYM directory %s; consider using "
                      "--dsymutil=yes\n",
                      VG_(clo_verbosity) > 1 ? "   " : "",
                      dsymfilename ? "has wrong UUID" : "is missing");
      goto success;
   }

   /* Run dsymutil */

   { Int r;
     const HChar* dsymutil = "/usr/bin/dsymutil ";
     HChar* cmd = ML_(dinfo_zalloc)( "di.readmacho.tmp1",
                                     VG_(strlen)(dsymutil)
                                     + VG_(strlen)(di->fsm.filename)
                                     + 32 /* misc */ );
     VG_(strcpy)(cmd, dsymutil);
     if (0) VG_(strcat)(cmd, "--verbose ");
     VG_(strcat)(cmd, "\"");
     VG_(strcat)(cmd, di->fsm.filename);
     VG_(strcat)(cmd, "\"");
     if (VG_(clo_verbosity) > 0) {
        VG_(message)(Vg_DebugMsg, "run: %s\n", cmd);
     }
     r = VG_(system)( cmd );
     if (r)
        VG_(message)(Vg_DebugMsg, "run: %s FAILED\n", dsymutil);
     ML_(dinfo_free)(cmd);
     dsymfilename = find_separate_debug_file(di->fsm.filename);
   }

   /* Try again to load it. */
   if (dsymfilename) {
      Bool valid;

      if (VG_(clo_verbosity) > 1)
         VG_(message)(Vg_DebugMsg, "   dsyms= %s\n", dsymfilename);

      dsli = map_image_aboard( di, dsymfilename, NULL );
      if (!ML_(sli_is_valid)(dsli)) {
         ML_(symerr)(di, False, "Connect to debuginfo image failed "
                                "(second attempt).");
         goto fail;
      }

      /* check it has the right uuid. */
      vg_assert(have_uuid);
      vg_assert(have_uuid);
      valid = dsli.img && dsli.szB > 0 && check_uuid_matches( dsli, uuid );
      if (!valid) {
         if (VG_(clo_verbosity) > 0) {
            VG_(message)(Vg_DebugMsg,
               "WARNING: did not find expected UUID %02X%02X%02X%02X"
               "-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X"
               " in dSYM dir\n",
               (UInt)uuid[0], (UInt)uuid[1], (UInt)uuid[2], (UInt)uuid[3],
               (UInt)uuid[4], (UInt)uuid[5], (UInt)uuid[6], (UInt)uuid[7],
               (UInt)uuid[8], (UInt)uuid[9], (UInt)uuid[10],
               (UInt)uuid[11], (UInt)uuid[12], (UInt)uuid[13],
               (UInt)uuid[14], (UInt)uuid[15] );
            VG_(message)(Vg_DebugMsg,
                         "WARNING: for %s\n", di->fsm.filename);
         }
         unmap_image( &dsli );
         /* unmap_image zeroes out dsli, so it's safe for "fail:" to
            re-try unmap_image. */
         goto fail;
      }
   }

   /* Right.  Finally we have our best try at the dwarf image, so go
      on to reading stuff out of it. */

  read_the_dwarf:
   if (ML_(sli_is_valid)(dsli) && dsli.szB > 0) {
      // "_mscn" is "mach-o section"
      DiSlice debug_info_mscn
         = getsectdata(dsli, "__DWARF", "__debug_info", NULL);
      DiSlice debug_abbv_mscn
         = getsectdata(dsli, "__DWARF", "__debug_abbrev", NULL);
      DiSlice debug_line_mscn
         = getsectdata(dsli, "__DWARF", "__debug_line", NULL);
      DiSlice debug_str_mscn
         = getsectdata(dsli, "__DWARF", "__debug_str", NULL);
      DiSlice debug_line_str_mscn
         = getsectdata(dsli, "__DWARF", "__debug_line_str", NULL);
      DiSlice debug_ranges_mscn
         = getsectdata(dsli, "__DWARF", "__debug_ranges", NULL);
      DiSlice debug_rnglists_mscn
         = getsectdata(dsli, "__DWARF", "__debug_rnglists", NULL);
      DiSlice debug_loclists_mscn
         = getsectdata(dsli, "__DWARF", "__debug_loclists", NULL);
      DiSlice debug_loc_mscn
         = getsectdata(dsli, "__DWARF", "__debug_loc", NULL);
      DiSlice debug_addr_mscn
         = getsectdata(dsli, "__DWARF", "__debug_addr", NULL);
      DiSlice debug_str_offsets_mscn
         = getsectdata(dsli, "__DWARF", "__debug_str_offsets", NULL);

      /* It appears (jrs, 2014-oct-19) that section "__eh_frame" in
         segment "__TEXT" appears in both the main and dsym files, but
         only the main one gives the right results.  Since it's in the
         __TEXT segment, we calculate the __eh_frame avma using its
         svma and the text bias, and that sounds reasonable. */
      Addr eh_frame_svma = 0;
      DiSlice eh_frame_mscn
         = getsectdata(msli, "__TEXT", "__eh_frame", &eh_frame_svma);

      if (ML_(sli_is_valid)(eh_frame_mscn)) {
         vg_assert(di->text_bias == di->text_debug_bias);
         ML_(read_callframe_info_dwarf3)(di, eh_frame_mscn,
                                         eh_frame_svma + di->text_bias,
                                         True/*is_ehframe*/);
      }

      if (ML_(sli_is_valid)(debug_info_mscn)) {
         if (VG_(clo_verbosity) > 1) {
            if (0)
            VG_(message)(Vg_DebugMsg,
                         "Reading dwarf3 for %s (%#lx) from %s"
                         " (%llu %llu %llu %llu %llu %llu)\n",
                         di->fsm.filename, di->text_avma, dsymfilename,
                         debug_info_mscn.szB, debug_abbv_mscn.szB,
                         debug_line_mscn.szB, debug_str_mscn.szB,
                         debug_ranges_mscn.szB, debug_loc_mscn.szB
                         );
            VG_(message)(Vg_DebugMsg,
               "   reading dwarf3 from dsyms file\n");
         }
         /* The old reader: line numbers and unwind info only */
         ML_(read_debuginfo_dwarf3) ( di,
                                      debug_info_mscn,
				      DiSlice_INVALID, /* .debug_types */
                                      debug_abbv_mscn,
                                      debug_line_mscn,
                                      debug_str_mscn,
                                      DiSlice_INVALID, /* ALT .debug_str */
                                      debug_line_str_mscn );

         /* The new reader: read the DIEs in .debug_info to acquire
            information on variable types and locations or inline info.
            But only if the tool asks for it, or the user requests it on
            the command line. */
         if (VG_(clo_read_var_info) /* the user or tool asked for it */
             || VG_(clo_read_inline_info)) {
            ML_(new_dwarf3_reader)(
               di, debug_info_mscn,
                   DiSlice_INVALID, /* .debug_types */
                   debug_abbv_mscn,
                   debug_line_mscn,
                   debug_str_mscn,
                   debug_ranges_mscn,
                   debug_rnglists_mscn,
                   debug_loclists_mscn,
                   debug_loc_mscn,
                   DiSlice_INVALID, /* ALT .debug_info */
                   DiSlice_INVALID, /* ALT .debug_abbv */
                   DiSlice_INVALID, /* ALT .debug_line */
                   DiSlice_INVALID, /* ALT .debug_str */
                   debug_line_str_mscn,  /* .debug_line_str */
                   debug_addr_mscn,
                   debug_str_offsets_mscn
            );
         }
      }
   }

   if (dsymfilename) ML_(dinfo_free)(dsymfilename);

  success:
   unmap_image(&msli);
   unmap_image(&dsli);
   return True;

   /* NOTREACHED */

  fail:
   ML_(symerr)(di, True, "Error reading Mach-O object.");
   unmap_image(&msli);
   unmap_image(&dsli);
   return False;
}

#endif // defined(VGO_darwin)

/*--------------------------------------------------------------------*/
/*--- end                                                          ---*/
/*--------------------------------------------------------------------*/
