From d7466e0c4e8202d1713d7f50c8029ff4f6d74fee Mon Sep 17 00:00:00 2001
From: Ken Thomases <ken@codeweavers.com>
Date: Fri, 13 Mar 2009 11:48:00 -0500
Subject: [PATCH] dbghelp: Add support for debugging Mach-O modules.

---
 dlls/dbghelp/Makefile.in       |    1 +
 dlls/dbghelp/dbghelp.c         |    4 +-
 dlls/dbghelp/dbghelp_private.h |   16 +-
 dlls/dbghelp/macho_module.c    | 1438 ++++++++++++++++++++++++++++++++
 dlls/dbghelp/module.c          |   51 +-
 dlls/dbghelp/path.c            |   15 +
 dlls/dbghelp/symbol.c          |    5 +-
 7 files changed, 1515 insertions(+), 15 deletions(-)
 create mode 100644 dlls/dbghelp/macho_module.c

diff --git a/dlls/dbghelp/Makefile.in b/dlls/dbghelp/Makefile.in
index 1b1547dc865..b75014d1c64 100644
--- a/dlls/dbghelp/Makefile.in
+++ b/dlls/dbghelp/Makefile.in
@@ -15,6 +15,7 @@ C_SRCS = \
 	dwarf.c \
 	elf_module.c \
 	image.c \
+	macho_module.c \
 	memory.c \
 	minidump.c \
 	module.c \
diff --git a/dlls/dbghelp/dbghelp.c b/dlls/dbghelp/dbghelp.c
index 1bfc338e596..1f9f0f8abd7 100644
--- a/dlls/dbghelp/dbghelp.c
+++ b/dlls/dbghelp/dbghelp.c
@@ -232,7 +232,8 @@ static BOOL check_live_target(struct process* pcs)
 {
     if (!GetProcessId(pcs->handle)) return FALSE;
     if (GetEnvironmentVariableA("DBGHELP_NOLIVE", NULL, 0)) return FALSE;
-    elf_read_wine_loader_dbg_info(pcs);
+    if (!elf_read_wine_loader_dbg_info(pcs))
+        macho_read_wine_loader_dbg_info(pcs);
     return TRUE;
 }
 
@@ -327,6 +328,7 @@ BOOL WINAPI SymInitializeW(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeP
         if (fInvadeProcess)
             EnumerateLoadedModules(hProcess, process_invade_cb, hProcess);
         elf_synchronize_module_list(pcs);
+        macho_synchronize_module_list(pcs);
     }
     else if (fInvadeProcess)
     {
diff --git a/dlls/dbghelp/dbghelp_private.h b/dlls/dbghelp/dbghelp_private.h
index bb14855b1a3..e1941bef05f 100644
--- a/dlls/dbghelp/dbghelp_private.h
+++ b/dlls/dbghelp/dbghelp_private.h
@@ -303,6 +303,7 @@ enum module_type
     DMT_UNKNOWN,        /* for lookup, not actually used for a module */
     DMT_ELF,            /* a real ELF shared module */
     DMT_PE,             /* a native or builtin PE module */
+    DMT_MACHO,          /* a real Mach-O shared module */
     DMT_PDB,            /* .PDB file */
     DMT_DBG,            /* .DBG file */
 };
@@ -322,6 +323,8 @@ struct module
     struct elf_module_info*	elf_info;
     struct dwarf2_module_info_s*dwarf2_info;
 
+    struct macho_module_info*	macho_info;
+
     /* memory allocation pool */
     struct pool                 pool;
 
@@ -419,7 +422,7 @@ extern DWORD calc_crc32(int fd);
 typedef BOOL (*enum_modules_cb)(const WCHAR*, unsigned long addr, void* user);
 
 /* elf_module.c */
-#define ELF_NO_MAP      ((const void*)0xffffffff)
+#define ELF_NO_MAP      ((const void*)-1)
 extern BOOL         elf_enum_modules(HANDLE hProc, enum_modules_cb, void*);
 extern BOOL         elf_fetch_file_info(const WCHAR* name, DWORD* base, DWORD* size, DWORD* checksum);
 struct elf_file_map;
@@ -432,6 +435,17 @@ struct elf_thunk_area;
 extern int          elf_is_in_thunk_area(unsigned long addr, const struct elf_thunk_area* thunks);
 extern DWORD WINAPI addr_to_linear(HANDLE hProcess, HANDLE hThread, ADDRESS* addr);
 
+/* macho_module.c */
+#define MACHO_NO_MAP    ((const void*)-1)
+extern BOOL         macho_enum_modules(HANDLE hProc, enum_modules_cb, void*);
+extern BOOL         macho_fetch_file_info(const WCHAR* name, DWORD* base, DWORD* size, DWORD* checksum);
+struct macho_file_map;
+extern BOOL         macho_load_debug_info(struct module* module, struct macho_file_map* fmap);
+extern struct module*
+                    macho_load_module(struct process* pcs, const WCHAR* name, unsigned long);
+extern BOOL         macho_read_wine_loader_dbg_info(struct process* pcs);
+extern BOOL         macho_synchronize_module_list(struct process* pcs);
+
 /* module.c */
 extern const WCHAR      S_ElfW[];
 extern const WCHAR      S_WineLoaderW[];
diff --git a/dlls/dbghelp/macho_module.c b/dlls/dbghelp/macho_module.c
new file mode 100644
index 00000000000..13f24d94108
--- /dev/null
+++ b/dlls/dbghelp/macho_module.c
@@ -0,0 +1,1438 @@
+/*
+ * File macho_module.c - processing of Mach-O files
+ *      Originally based on elf_module.c
+ *
+ * Copyright (C) 1996, Eric Youngdale.
+ *               1999-2007 Eric Pouech
+ *               2009 Ken Thomases, CodeWeavers Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "config.h"
+#include "wine/port.h"
+
+#include "dbghelp_private.h"
+
+#ifdef __MACH__
+
+#include <assert.h>
+#include <stdarg.h>
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_MMAN_H
+# include <sys/mman.h>
+#endif
+
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#include <mach-o/nlist.h>
+
+#ifdef HAVE_MACH_O_DYLD_IMAGES_H
+#include <mach-o/dyld_images.h>
+#else
+struct dyld_image_info {
+    const struct mach_header *imageLoadAddress;
+    const char               *imageFilePath;
+    uintptr_t                 imageFileModDate;
+};
+
+struct dyld_all_image_infos {
+    uint32_t                      version;
+    uint32_t                      infoArrayCount;
+    const struct dyld_image_info *infoArray;
+    void*                         notification;
+    int                           processDetachedFromSharedRegion;
+};
+#endif
+
+#include "winternl.h"
+#include "wine/library.h"
+#include "wine/debug.h"
+
+#ifdef WORDS_BIGENDIAN
+#define swap_ulong_be_to_host(n) (n)
+#else
+#define swap_ulong_be_to_host(n) (RtlUlongByteSwap(n))
+#endif
+
+WINE_DEFAULT_DEBUG_CHANNEL(dbghelp_macho);
+
+
+struct macho_module_info
+{
+    unsigned long               load_addr;
+    unsigned short              in_use : 1,
+                                is_loader : 1;
+};
+
+#define MACHO_INFO_DEBUG_HEADER   0x0001
+#define MACHO_INFO_MODULE         0x0002
+#define MACHO_INFO_NAME           0x0004
+
+struct macho_info
+{
+    unsigned                    flags;          /* IN  one (or several) of the MACHO_INFO constants */
+    unsigned long               dbg_hdr_addr;   /* OUT address of debug header (if MACHO_INFO_DEBUG_HEADER is set) */
+    struct module*              module;         /* OUT loaded module (if MACHO_INFO_MODULE is set) */
+    const WCHAR*                module_name;    /* OUT found module name (if MACHO_INFO_NAME is set) */
+};
+
+/* structure holding information while handling a Mach-O image */
+#define BITS_PER_ULONG (sizeof(ULONG) * 8)
+#define ULONGS_FOR_BITS(nbits) (((nbits) + BITS_PER_ULONG - 1) / BITS_PER_ULONG)
+struct macho_file_map
+{
+    /* A copy of the Mach-O header for an individual architecture. */
+    struct mach_header          mach_header;
+
+    /* The mapped load commands. */
+    const struct load_command*  load_commands;
+
+    /* The portion of the file which is this architecture.  mach_header was
+     * read from arch_offset. */
+    unsigned                    arch_offset;
+    unsigned                    arch_size;
+
+    /* The range of address space covered by all segments. */
+    size_t                      segs_start;
+    size_t                      segs_size;
+
+    /* Map of which sections contain code.  Sections are accessed using 1-based
+     * index.  Bit 0 of this bitset indicates if the bitset has been initialized. */
+    RTL_BITMAP                  sect_is_code;
+    ULONG                       sect_is_code_buff[ULONGS_FOR_BITS(MAX_SECT + 1)];
+
+    /* The file. */
+    int                         fd;
+};
+
+static void macho_unmap_file(struct macho_file_map* fmap);
+
+/******************************************************************
+ *              macho_calc_range
+ *
+ * For a range (offset & length) of a single architecture within
+ * a Mach-O file, calculate the page-aligned range of the whole file
+ * that encompasses it.  For a fat binary, the architecture will
+ * itself be offset within the file, so take that into account.
+ */
+static void macho_calc_range(const struct macho_file_map* fmap, unsigned offset,
+                             unsigned len, unsigned* out_aligned_offset,
+                             unsigned* out_aligned_end, unsigned* out_aligned_len,
+                             unsigned* out_misalign)
+{
+    unsigned pagemask = getpagesize() - 1;
+    unsigned file_offset, misalign;
+
+    file_offset = fmap->arch_offset + offset;
+    misalign = file_offset & pagemask;
+    *out_aligned_offset = file_offset - misalign;
+    *out_aligned_end = (file_offset + len + pagemask) & ~pagemask;
+    if (out_aligned_len)
+        *out_aligned_len = *out_aligned_end - *out_aligned_offset;
+    if (out_misalign)
+        *out_misalign = misalign;
+}
+
+/******************************************************************
+ *              macho_map_range
+ *
+ * Maps a range (offset, length in bytes) from a Mach-O file into memory
+ */
+static const char* macho_map_range(const struct macho_file_map* fmap, unsigned offset, unsigned len)
+{
+    unsigned    misalign, aligned_offset, aligned_map_end, map_size;
+    const void* aligned_ptr;
+
+    TRACE("(%p/%d, 0x%08x, 0x%08x)\n", fmap, fmap->fd, offset, len);
+
+    macho_calc_range(fmap, offset, len, &aligned_offset, &aligned_map_end,
+                     &map_size, &misalign);
+
+    aligned_ptr = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fmap->fd, aligned_offset);
+
+    TRACE("Mapped (0x%08x - 0x%08x) to %p\n", aligned_offset, aligned_map_end, aligned_ptr);
+
+    if (aligned_ptr == MAP_FAILED) return MACHO_NO_MAP;
+    return (const char*)aligned_ptr + misalign;
+}
+
+/******************************************************************
+ *              macho_unmap_range
+ *
+ * Unmaps a range (offset, length in bytes) of a Mach-O file from memory
+ */
+static void macho_unmap_range(const void** mapped, const struct macho_file_map* fmap,
+                              unsigned offset, unsigned len)
+{
+    TRACE("(%p, %p/%d, 0x%08x, 0x%08x)\n", mapped, fmap, fmap->fd, offset, len);
+
+    if (mapped && *mapped != MACHO_NO_MAP)
+    {
+        unsigned    misalign, aligned_offset, aligned_map_end, map_size;
+        void*       aligned_ptr;
+
+        macho_calc_range(fmap, offset, len, &aligned_offset, &aligned_map_end,
+                         &map_size, &misalign);
+
+        aligned_ptr = (char*)*mapped - misalign;
+        if (munmap(aligned_ptr, map_size) < 0)
+            WARN("Couldn't unmap the range\n");
+        TRACE("Unmapped (0x%08x - 0x%08x) from %p - %p\n", aligned_offset, aligned_map_end, aligned_ptr, (char*)aligned_ptr + map_size);
+        *mapped = MACHO_NO_MAP;
+    }
+}
+
+/******************************************************************
+ *              macho_map_ranges
+ *
+ * Maps two ranges (offset, length in bytes) from a Mach-O file
+ * into memory.  If the two ranges overlap, use one mmap so that
+ * the munmap doesn't fragment the mapping.
+ */
+static BOOL macho_map_ranges(const struct macho_file_map* fmap,
+                             unsigned offset1, unsigned len1,
+                             unsigned offset2, unsigned len2,
+                             const void** mapped1, const void** mapped2)
+{
+    unsigned aligned_offset1, aligned_map_end1;
+    unsigned aligned_offset2, aligned_map_end2;
+
+    TRACE("(%p/%d, 0x%08x, 0x%08x, 0x%08x, 0x%08x, %p, %p)\n", fmap, fmap->fd,
+            offset1, len1, offset2, len2, mapped1, mapped2);
+
+    macho_calc_range(fmap, offset1, len1, &aligned_offset1, &aligned_map_end1, NULL, NULL);
+    macho_calc_range(fmap, offset2, len2, &aligned_offset2, &aligned_map_end2, NULL, NULL);
+
+    if (aligned_map_end1 < aligned_offset2 || aligned_map_end2 < aligned_offset1)
+    {
+        *mapped1 = macho_map_range(fmap, offset1, len1);
+        if (*mapped1 != MACHO_NO_MAP)
+        {
+            *mapped2 = macho_map_range(fmap, offset2, len2);
+            if (*mapped2 == MACHO_NO_MAP)
+                macho_unmap_range(mapped1, fmap, offset1, len1);
+        }
+    }
+    else
+    {
+        if (offset1 < offset2)
+        {
+            *mapped1 = macho_map_range(fmap, offset1, offset2 + len2 - offset1);
+            if (*mapped1 != MACHO_NO_MAP)
+                *mapped2 = (const char*)*mapped1 + offset2 - offset1;
+        }
+        else
+        {
+            *mapped2 = macho_map_range(fmap, offset2, offset1 + len1 - offset2);
+            if (*mapped2 != MACHO_NO_MAP)
+                *mapped1 = (const char*)*mapped2 + offset1 - offset2;
+        }
+    }
+
+    TRACE(" => %p, %p\n", *mapped1, *mapped2);
+
+    return (*mapped1 != MACHO_NO_MAP) && (*mapped2 != MACHO_NO_MAP);
+}
+
+/******************************************************************
+ *              macho_unmap_ranges
+ *
+ * Unmaps two ranges (offset, length in bytes) of a Mach-O file
+ * from memory.  Use for ranges which were mapped by
+ * macho_map_ranges.
+ */
+static void macho_unmap_ranges(const struct macho_file_map* fmap,
+                               unsigned offset1, unsigned len1,
+                               unsigned offset2, unsigned len2,
+                               const void** mapped1, const void** mapped2)
+{
+    unsigned    aligned_offset1, aligned_map_end1;
+    unsigned    aligned_offset2, aligned_map_end2;
+
+    TRACE("(%p/%d, 0x%08x, 0x%08x, 0x%08x, 0x%08x, %p/%p, %p/%p)\n", fmap, fmap->fd,
+            offset1, len1, offset2, len2, mapped1, *mapped1, mapped2, *mapped2);
+
+    macho_calc_range(fmap, offset1, len1, &aligned_offset1, &aligned_map_end1, NULL, NULL);
+    macho_calc_range(fmap, offset2, len2, &aligned_offset2, &aligned_map_end2, NULL, NULL);
+
+    if (aligned_map_end1 < aligned_offset2 || aligned_map_end2 < aligned_offset1)
+    {
+        macho_unmap_range(mapped1, fmap, offset1, len1);
+        macho_unmap_range(mapped2, fmap, offset2, len2);
+    }
+    else
+    {
+        if (offset1 < offset2)
+        {
+            macho_unmap_range(mapped1, fmap, offset1, offset2 + len2 - offset1);
+            *mapped2 = MACHO_NO_MAP;
+        }
+        else
+        {
+            macho_unmap_range(mapped2, fmap, offset2, offset1 + len1 - offset2);
+            *mapped1 = MACHO_NO_MAP;
+        }
+    }
+}
+
+/******************************************************************
+ *              macho_map_load_commands
+ *
+ * Maps the load commands from a Mach-O file into memory
+ */
+static const struct load_command* macho_map_load_commands(struct macho_file_map* fmap)
+{
+    if (fmap->load_commands == MACHO_NO_MAP)
+    {
+        fmap->load_commands = (const struct load_command*) macho_map_range(
+                fmap, sizeof(fmap->mach_header), fmap->mach_header.sizeofcmds);
+        TRACE("Mapped load commands: %p\n", fmap->load_commands);
+    }
+
+    return fmap->load_commands;
+}
+
+/******************************************************************
+ *              macho_unmap_load_commands
+ *
+ * Unmaps the load commands of a Mach-O file from memory
+ */
+static void macho_unmap_load_commands(struct macho_file_map* fmap)
+{
+    if (fmap->load_commands != MACHO_NO_MAP)
+    {
+        TRACE("Unmapping load commands: %p\n", fmap->load_commands);
+        macho_unmap_range((const void**)&fmap->load_commands, fmap,
+                    sizeof(fmap->mach_header), fmap->mach_header.sizeofcmds);
+    }
+}
+
+/******************************************************************
+ *              macho_next_load_command
+ *
+ * Advance to the next load command
+ */
+static const struct load_command* macho_next_load_command(const struct load_command* lc)
+{
+    return (const struct load_command*)((const char*)lc + lc->cmdsize);
+}
+
+/******************************************************************
+ *              macho_enum_load_commands
+ *
+ * Enumerates the load commands for a Mach-O file, selecting by
+ * the command type, calling a callback for each.  If the callback
+ * returns <0, that indicates an error.  If it returns >0, that means
+ * it's not interested in getting any more load commands.
+ * If this function returns <0, that's an error produced by the
+ * callback.  If >=0, that's the count of load commands successfully
+ * processed.
+ */
+static int macho_enum_load_commands(struct macho_file_map* fmap, unsigned cmd,
+                                    int (*cb)(struct macho_file_map*, const struct load_command*, void*),
+                                    void* user)
+{
+    const struct load_command* lc;
+    int i;
+    int count = 0;
+
+    TRACE("(%p/%d, %u, %p, %p)\n", fmap, fmap->fd, cmd, cb, user);
+
+    if ((lc = macho_map_load_commands(fmap)) == MACHO_NO_MAP) return -1;
+
+    TRACE("%d total commands\n", fmap->mach_header.ncmds);
+
+    for (i = 0; i < fmap->mach_header.ncmds; i++, lc = macho_next_load_command(lc))
+    {
+        int result;
+
+        if (cmd && cmd != lc->cmd) continue;
+        count++;
+
+        result = cb(fmap, lc, user);
+        TRACE("load_command[%d] (%p), cmd %u; callback => %d\n", i, lc, lc->cmd, result);
+        if (result) return (result < 0) ? result : count;
+    }
+
+    return count;
+}
+
+/******************************************************************
+ *              macho_accum_segs_range
+ *
+ * Callback for macho_enum_load_commands.  Accumulates the address
+ * range covered by the segments of a Mach-O file.  All commands
+ * are expected to be of LC_SEGMENT type.
+ */
+static int macho_accum_segs_range(struct macho_file_map* fmap,
+                                  const struct load_command* lc, void* user)
+{
+    const struct segment_command*   sc = (const struct segment_command*)lc;
+    unsigned                        tmp, page_mask = getpagesize() - 1;
+
+    TRACE("(%p/%d, %p, %p) before: 0x%08x - 0x%08x\n", fmap, fmap->fd, lc, user,
+            (unsigned)fmap->segs_start, (unsigned)fmap->segs_size);
+    TRACE("Segment command vm: 0x%08x - 0x%08x\n", (unsigned)sc->vmaddr,
+            (unsigned)sc->vmaddr + sc->vmsize);
+
+    if (!strncmp(sc->segname, "WINE_DOS", sizeof(sc->segname)) ||
+        !strncmp(sc->segname, "WINE_OLE32", sizeof(sc->segname)) ||
+        !strncmp(sc->segname, "WINE_SHARED_HEAP", sizeof(sc->segname)))
+    {
+        TRACE("Ignoring special Wine segment %s\n", debugstr_an(sc->segname, sizeof(sc->segname)));
+        return 0;
+    }
+
+    /* If this segment starts before previously-known earliest, record
+     * new earliest. */
+    if (sc->vmaddr < fmap->segs_start)
+        fmap->segs_start = sc->vmaddr;
+
+    /* If this segment extends beyond previously-known furthest, record
+     * new furthest. */
+    tmp = (sc->vmaddr + sc->vmsize + page_mask) & ~page_mask;
+    if (fmap->segs_size < tmp) fmap->segs_size = tmp;
+
+    TRACE("after: 0x%08x - 0x%08x\n", (unsigned)fmap->segs_start, (unsigned)fmap->segs_size);
+
+    return 0;
+}
+
+/******************************************************************
+ *              macho_map_file
+ *
+ * Maps a Mach-O file into memory (and checks it's a real Mach-O file)
+ */
+static BOOL macho_map_file(const WCHAR* filenameW, struct macho_file_map* fmap)
+{
+    struct fat_header   fat_header;
+    struct stat         statbuf;
+    int                 i;
+    char*               filename;
+    unsigned            len;
+    BOOL                ret = FALSE;
+
+    TRACE("(%s, %p)\n", debugstr_w(filenameW), fmap);
+
+    fmap->fd = -1;
+    fmap->load_commands = MACHO_NO_MAP;
+    RtlInitializeBitMap(&fmap->sect_is_code, fmap->sect_is_code_buff, MAX_SECT + 1);
+
+    len = WideCharToMultiByte(CP_UNIXCP, 0, filenameW, -1, NULL, 0, NULL, NULL);
+    if (!(filename = HeapAlloc(GetProcessHeap(), 0, len))) return FALSE;
+    WideCharToMultiByte(CP_UNIXCP, 0, filenameW, -1, filename, len, NULL, NULL);
+
+    /* check that the file exists */
+    if (stat(filename, &statbuf) == -1 || S_ISDIR(statbuf.st_mode)) goto done;
+
+    /* Now open the file, so that we can mmap() it. */
+    if ((fmap->fd = open(filename, O_RDONLY)) == -1) goto done;
+
+    if (read(fmap->fd, &fat_header, sizeof(fat_header)) != sizeof(fat_header))
+        goto done;
+    TRACE("... got possible fat header\n");
+
+    /* Fat header is always in big-endian order. */
+    if (swap_ulong_be_to_host(fat_header.magic) == FAT_MAGIC)
+    {
+        int narch = swap_ulong_be_to_host(fat_header.nfat_arch);
+        for (i = 0; i < narch; i++)
+        {
+            struct fat_arch fat_arch;
+            if (read(fmap->fd, &fat_arch, sizeof(fat_arch)) != sizeof(fat_arch))
+                goto done;
+            if (swap_ulong_be_to_host(fat_arch.cputype) == CPU_TYPE_X86)
+            {
+                fmap->arch_offset = swap_ulong_be_to_host(fat_arch.offset);
+                fmap->arch_size = swap_ulong_be_to_host(fat_arch.size);
+                break;
+            }
+        }
+        if (i >= narch) goto done;
+        TRACE("... found x86 arch\n");
+    }
+    else
+    {
+        fmap->arch_offset = 0;
+        fmap->arch_size = statbuf.st_size;
+        TRACE("... not a fat header\n");
+    }
+
+    /* Individual architecture (standalone or within a fat file) is in its native byte order. */
+    lseek(fmap->fd, fmap->arch_offset, SEEK_SET);
+    if (read(fmap->fd, &fmap->mach_header, sizeof(fmap->mach_header)) != sizeof(fmap->mach_header))
+        goto done;
+    TRACE("... got possible Mach header\n");
+    /* and check for a Mach-O header */
+    if (fmap->mach_header.magic != MH_MAGIC ||
+        fmap->mach_header.cputype != CPU_TYPE_X86) goto done;
+    /* Make sure the file type is one of the ones we expect. */
+    switch (fmap->mach_header.filetype)
+    {
+        case MH_EXECUTE:
+        case MH_DYLIB:
+        case MH_DYLINKER:
+        case MH_BUNDLE:
+            break;
+        default:
+            goto done;
+    }
+    TRACE("... verified Mach x86 header\n");
+
+    fmap->segs_size = 0;
+    fmap->segs_start = ~0L;
+
+    if (macho_enum_load_commands(fmap, LC_SEGMENT, macho_accum_segs_range, NULL) < 0)
+        goto done;
+
+    fmap->segs_size -= fmap->segs_start;
+    TRACE("segs_start: 0x%08x, segs_size: 0x%08x\n", (unsigned)fmap->segs_start,
+            (unsigned)fmap->segs_size);
+
+    ret = TRUE;
+done:
+    if (!ret)
+        macho_unmap_file(fmap);
+    HeapFree(GetProcessHeap(), 0, filename);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_unmap_file
+ *
+ * Unmaps a Mach-O file from memory (previously mapped with macho_map_file)
+ */
+static void macho_unmap_file(struct macho_file_map* fmap)
+{
+    TRACE("(%p/%d)\n", fmap, fmap->fd);
+    if (fmap->fd != -1)
+    {
+        macho_unmap_load_commands(fmap);
+        close(fmap->fd);
+        fmap->fd = -1;
+    }
+}
+
+/******************************************************************
+ *              macho_fill_sect_is_code
+ *
+ * Callback for macho_enum_load_commands.  Determines which segments
+ * of a Mach-O file contain code.  All commands are expected to be
+ * of LC_SEGMENT type.
+ */
+static int macho_fill_sect_is_code(struct macho_file_map* fmap,
+                                   const struct load_command* lc, void* user)
+{
+    const struct segment_command*   sc = (const struct segment_command*)lc;
+    const struct section*           sections;
+    int*                            cursect = user;
+    int                             i;
+
+    TRACE("(%p/%d, %p, %p/%d) scanning %u sections\n", fmap, fmap->fd, lc,
+            cursect, *cursect, sc->nsects);
+
+    sections = (const struct section*)(sc + 1);
+    for (i = 0; i < sc->nsects; i++)
+    {
+        if (*cursect > MAX_SECT) return -1;
+        (*cursect)++;
+
+        if (!(sections[i].flags & SECTION_TYPE) &&
+            (sections[i].flags & (S_ATTR_PURE_INSTRUCTIONS|S_ATTR_SOME_INSTRUCTIONS)))
+            RtlSetBits(&fmap->sect_is_code, *cursect, 1);
+        else
+            RtlClearBits(&fmap->sect_is_code, *cursect, 1);
+        TRACE("Section %d (%d of this segment) is%s code\n", *cursect, i,
+                (RtlAreBitsSet(&fmap->sect_is_code, *cursect, 1) ? "" : " not"));
+    }
+
+    return 0;
+}
+
+/******************************************************************
+ *              macho_sect_is_code
+ *
+ * Checks if a section, identified by sectidx which is a 1-based
+ * index into the sections of all segments, in order of load
+ * commands, contains code.
+ */
+static BOOL macho_sect_is_code(struct macho_file_map* fmap, unsigned char sectidx)
+{
+    TRACE("(%p/%d, %u)\n", fmap, fmap->fd, sectidx);
+
+    if (!RtlAreBitsSet(&fmap->sect_is_code, 0, 1))
+    {
+        int cursect = 0;
+        if (macho_enum_load_commands(fmap, LC_SEGMENT, macho_fill_sect_is_code, &cursect) < 0)
+            WARN("Couldn't load sect_is_code map\n");
+        RtlSetBits(&fmap->sect_is_code, 0, 1);
+    }
+
+    return RtlAreBitsSet(&fmap->sect_is_code, sectidx, 1);
+}
+
+struct symtab_elt
+{
+    struct hash_table_elt       ht_elt;
+    struct symt_compiland*      compiland;
+    unsigned long               addr;
+    unsigned char               is_code:1,
+                                is_public:1,
+                                is_global:1,
+                                used:1;
+};
+
+struct macho_debug_info
+{
+    struct macho_file_map*      fmap;
+    struct module*              module;
+    struct pool                 pool;
+    struct hash_table           ht_symtab;
+};
+
+/******************************************************************
+ *              macho_stabs_def_cb
+ *
+ * Callback for stabs_parse.  Collect symbol definitions.
+ */
+static void macho_stabs_def_cb(struct module* module, unsigned long load_offset,
+                               const char* name, unsigned long offset,
+                               BOOL is_public, BOOL is_global, unsigned char sectidx,
+                               struct symt_compiland* compiland, void* user)
+{
+    struct macho_debug_info*    mdi = user;
+    struct symtab_elt*          ste;
+
+    TRACE("(%p, 0x%08lx, %s, 0x%08lx, %d, %d, %u, %p, %p/%p/%d)\n", module, load_offset,
+            debugstr_a(name), offset, is_public, is_global, sectidx,
+            compiland, mdi, mdi->fmap, mdi->fmap->fd);
+
+    /* Defer the creation of new non-debugging symbols until after we've
+     * finished parsing the stabs. */
+    ste                 = pool_alloc(&mdi->pool, sizeof(*ste));
+    ste->ht_elt.name    = pool_strdup(&mdi->pool, name);
+    ste->compiland      = compiland;
+    ste->addr           = load_offset + offset;
+    ste->is_code        = !!macho_sect_is_code(mdi->fmap, sectidx);
+    ste->is_public      = !!is_public;
+    ste->is_global      = !!is_global;
+    ste->used           = 0;
+    hash_table_add(&mdi->ht_symtab, &ste->ht_elt);
+}
+
+/******************************************************************
+ *              macho_parse_symtab
+ *
+ * Callback for macho_enum_load_commands.  Processes the LC_SYMTAB
+ * load commands from the Mach-O file.
+ */
+static int macho_parse_symtab(struct macho_file_map* fmap,
+                              const struct load_command* lc, void* user)
+{
+    const struct symtab_command*    sc = (const struct symtab_command*)lc;
+    struct macho_debug_info*        mdi = user;
+    const struct nlist*             stab;
+    const char*                     stabstr;
+    int                             ret = 0;
+
+    TRACE("(%p/%d, %p, %p) %u syms at 0x%08x, strings 0x%08x - 0x%08x\n", fmap, fmap->fd, lc,
+            user, sc->nsyms, sc->symoff, sc->stroff, sc->stroff + sc->strsize);
+
+    if (!macho_map_ranges(fmap, sc->symoff, sc->nsyms * sizeof(struct nlist),
+            sc->stroff, sc->strsize, (const void**)&stab, (const void**)&stabstr))
+        return 0;
+
+    if (!stabs_parse(mdi->module, mdi->module->macho_info->load_addr - fmap->segs_start,
+                       stab, sc->nsyms * sizeof(struct nlist),
+                       stabstr, sc->strsize, macho_stabs_def_cb, mdi))
+        ret = -1;
+
+    macho_unmap_ranges(fmap, sc->symoff, sc->nsyms * sizeof(struct nlist),
+            sc->stroff, sc->strsize, (const void**)&stab, (const void**)&stabstr);
+
+    return ret;
+}
+
+/******************************************************************
+ *              macho_finish_stabs
+ *
+ * Integrate the non-debugging symbols we've gathered into the
+ * symbols that were generated during stabs parsing.
+ */
+static void macho_finish_stabs(struct module* module, struct hash_table* ht_symtab)
+{
+    struct hash_table_iter      hti_ours;
+    struct symtab_elt*          ste;
+    BOOL                        adjusted = FALSE;
+
+    TRACE("(%p, %p)\n", module, ht_symtab);
+
+    /* For each of our non-debugging symbols, see if it can provide some
+     * missing details to one of the module's known symbols. */
+    hash_table_iter_init(ht_symtab, &hti_ours, NULL);
+    while ((ste = hash_table_iter_up(&hti_ours)))
+    {
+        struct hash_table_iter  hti_modules;
+        void*                   ptr;
+        struct symt_ht*         sym;
+        struct symt_function*   func;
+        struct symt_data*       data;
+
+        hash_table_iter_init(&module->ht_symbols, &hti_modules, ste->ht_elt.name);
+        while ((ptr = hash_table_iter_up(&hti_modules)))
+        {
+            sym = GET_ENTRY(ptr, struct symt_ht, hash_elt);
+
+            if (strcmp(sym->hash_elt.name, ste->ht_elt.name))
+                continue;
+
+            switch (sym->symt.tag)
+            {
+            case SymTagFunction:
+                func = (struct symt_function*)sym;
+                if (func->address == module->macho_info->load_addr)
+                {
+                    TRACE("Adjusting function %p/%s!%s from 0x%08lx to 0x%08lx\n", func,
+                          debugstr_w(module->module.ModuleName), sym->hash_elt.name,
+                          func->address, ste->addr);
+                    func->address = ste->addr;
+                    adjusted = TRUE;
+                }
+                if (func->address == ste->addr)
+                    ste->used = 1;
+                break;
+            case SymTagData:
+                data = (struct symt_data*)sym;
+                switch (data->kind)
+                {
+                case DataIsGlobal:
+                case DataIsFileStatic:
+                    if (data->u.var.offset == module->macho_info->load_addr)
+                    {
+                        TRACE("Adjusting data symbol %p/%s!%s from 0x%08lx to 0x%08lx\n",
+                              data, debugstr_w(module->module.ModuleName), sym->hash_elt.name,
+                              data->u.var.offset, ste->addr);
+                        data->u.var.offset = ste->addr;
+                        adjusted = TRUE;
+                    }
+                    if (data->u.var.offset == ste->addr)
+                    {
+                        enum DataKind new_kind;
+
+                        new_kind = ste->is_global ? DataIsGlobal : DataIsFileStatic;
+                        if (data->kind != new_kind)
+                        {
+                            WARN("Changing kind for %p/%s!%s from %d to %d\n", sym,
+                                 debugstr_w(module->module.ModuleName), sym->hash_elt.name,
+                                 (int)data->kind, (int)new_kind);
+                            data->kind = new_kind;
+                            adjusted = TRUE;
+                        }
+                        ste->used = 1;
+                    }
+                    break;
+                default:;
+                }
+                break;
+            default:
+                TRACE("Ignoring tag %u\n", sym->symt.tag);
+                break;
+            }
+        }
+    }
+
+    if (adjusted)
+    {
+        /* since we may have changed some addresses, mark the module to be resorted */
+        module->sortlist_valid = FALSE;
+    }
+
+    /* Mark any of our non-debugging symbols which fall on an already-used
+     * address as "used".  This allows us to skip them in the next loop,
+     * below.  We do this in separate loops because symt_new_* marks the
+     * list as needing sorting and symt_find_nearest sorts if needed,
+     * causing thrashing. */
+    if (!(dbghelp_options & SYMOPT_PUBLICS_ONLY))
+    {
+        hash_table_iter_init(ht_symtab, &hti_ours, NULL);
+        while ((ste = hash_table_iter_up(&hti_ours)))
+        {
+            struct symt_ht* sym;
+            ULONG64         addr;
+
+            if (ste->used) continue;
+
+            sym = symt_find_nearest(module, ste->addr);
+            if (sym)
+                symt_get_info(&sym->symt, TI_GET_ADDRESS, &addr);
+            if (sym && ste->addr == addr)
+            {
+                ULONG64 size = 0;
+                DWORD   kind = -1;
+
+                ste->used = 1;
+
+                /* If neither symbol has a correct size (ours never does), we
+                 * consider them both to be markers.  No warning is needed in
+                 * that case.
+                 * Also, we check that we don't have two symbols, one local, the other
+                 * global, which is legal.
+                 */
+                symt_get_info(&sym->symt, TI_GET_LENGTH,   &size);
+                symt_get_info(&sym->symt, TI_GET_DATAKIND, &kind);
+                if (size && kind == (ste->is_global ? DataIsGlobal : DataIsFileStatic))
+                    FIXME("Duplicate in %s: %s<%08lx> %s<%s-%s>\n",
+                          debugstr_w(module->module.ModuleName),
+                          ste->ht_elt.name, ste->addr,
+                          sym->hash_elt.name,
+                          wine_dbgstr_longlong(addr), wine_dbgstr_longlong(size));
+            }
+        }
+    }
+
+    /* For any of our remaining non-debugging symbols which have no match
+     * among the module's known symbols, add them as new symbols. */
+    hash_table_iter_init(ht_symtab, &hti_ours, NULL);
+    while ((ste = hash_table_iter_up(&hti_ours)))
+    {
+        if (!(dbghelp_options & SYMOPT_PUBLICS_ONLY) && !ste->used)
+        {
+            if (ste->is_code)
+            {
+                symt_new_function(module, ste->compiland, ste->ht_elt.name,
+                    ste->addr, 0, NULL);
+            }
+            else
+            {
+                symt_new_global_variable(module, ste->compiland, ste->ht_elt.name,
+                    !ste->is_global, ste->addr, 0, NULL);
+            }
+
+            ste->used = 1;
+        }
+
+        if (ste->is_public && !(dbghelp_options & SYMOPT_NO_PUBLICS))
+        {
+            symt_new_public(module, ste->compiland, ste->ht_elt.name,
+                            ste->addr, 0, ste->is_code, ste->is_code);
+        }
+    }
+}
+
+/******************************************************************
+ *              macho_load_debug_info_from_map
+ *
+ * Loads the symbolic information from a Mach-O module.
+ * Returns
+ *      FALSE if the file doesn't contain symbolic info (or this info
+ *              cannot be read or parsed)
+ *      TRUE on success
+ */
+static BOOL macho_load_debug_info_from_map(struct module* module,
+                                           struct macho_file_map* fmap)
+{
+    BOOL                    ret = FALSE;
+    struct macho_debug_info mdi;
+    int                     result;
+
+    TRACE("(%p, %p/%d)\n", module, fmap, fmap->fd);
+
+    module->module.SymType = SymExport;
+
+    mdi.fmap = fmap;
+    mdi.module = module;
+    pool_init(&mdi.pool, 65536);
+    hash_table_init(&mdi.pool, &mdi.ht_symtab, 256);
+    result = macho_enum_load_commands(fmap, LC_SYMTAB, macho_parse_symtab, &mdi);
+    if (result > 0)
+        ret = TRUE;
+    else if (result < 0)
+        WARN("Couldn't correctly read stabs\n");
+
+    macho_finish_stabs(module, &mdi.ht_symtab);
+
+    pool_destroy(&mdi.pool);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_load_debug_info
+ *
+ * Loads Mach-O debugging information from the module image file.
+ */
+BOOL macho_load_debug_info(struct module* module, struct macho_file_map* fmap)
+{
+    BOOL                    ret = TRUE;
+    struct macho_file_map   my_fmap;
+
+    TRACE("(%p, %p/%d)\n", module, fmap, fmap ? fmap->fd : -1);
+
+    if (module->type != DMT_MACHO || !module->macho_info)
+    {
+        ERR("Bad Mach-O module '%s'\n", debugstr_w(module->module.LoadedImageName));
+        return FALSE;
+    }
+
+    if (!fmap)
+    {
+        fmap = &my_fmap;
+        ret = macho_map_file(module->module.LoadedImageName, fmap);
+    }
+    if (ret)
+        ret = macho_load_debug_info_from_map(module, fmap);
+
+    if (fmap == &my_fmap) macho_unmap_file(fmap);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_fetch_file_info
+ *
+ * Gathers some more information for a Mach-O module from a given file
+ */
+BOOL macho_fetch_file_info(const WCHAR* name, DWORD* base,
+                           DWORD* size, DWORD* checksum)
+{
+    struct macho_file_map fmap;
+
+    TRACE("(%s, %p, %p, %p)\n", debugstr_w(name), base, size, checksum);
+
+    if (!macho_map_file(name, &fmap)) return FALSE;
+    if (base) *base = fmap.segs_start;
+    *size = fmap.segs_size;
+    *checksum = calc_crc32(fmap.fd);
+    macho_unmap_file(&fmap);
+    return TRUE;
+}
+
+/******************************************************************
+ *              macho_load_file
+ *
+ * Loads the information for Mach-O module stored in 'filename'.
+ * The module has been loaded at 'load_addr' address.
+ * returns
+ *      FALSE if the file cannot be found/opened or if the file doesn't
+ *              contain symbolic info (or this info cannot be read or parsed)
+ *      TRUE on success
+ */
+static BOOL macho_load_file(struct process* pcs, const WCHAR* filename,
+                            unsigned long load_addr, struct macho_info* macho_info)
+{
+    BOOL                    ret = TRUE;
+    struct macho_file_map   fmap;
+
+    TRACE("(%p/%p, %s, 0x%08lx, %p/0x%08x)\n", pcs, pcs->handle, debugstr_w(filename),
+            load_addr, macho_info, macho_info->flags);
+
+    if (!macho_map_file(filename, &fmap)) return FALSE;
+
+    /* Find the dynamic loader's table of images loaded into the process.
+     */
+    if (macho_info->flags & MACHO_INFO_DEBUG_HEADER)
+    {
+        static void* dyld_all_image_infos_addr;
+
+        /* This symbol should be in the same place in all processes. */
+        if (!dyld_all_image_infos_addr)
+        {
+            struct nlist nl[2];
+            memset(nl, 0, sizeof(nl));
+            nl[0].n_un.n_name = (char*)"_dyld_all_image_infos";
+            if (!nlist("/usr/lib/dyld", nl))
+                dyld_all_image_infos_addr = (void*)nl[0].n_value;
+        }
+
+        if (dyld_all_image_infos_addr)
+            macho_info->dbg_hdr_addr = (unsigned long)dyld_all_image_infos_addr;
+        else
+            ret = FALSE;
+        TRACE("dbg_hdr_addr = 0x%08lx\n", macho_info->dbg_hdr_addr);
+    }
+
+    if (macho_info->flags & MACHO_INFO_MODULE)
+    {
+        struct macho_module_info *macho_module_info =
+            HeapAlloc(GetProcessHeap(), 0, sizeof(struct macho_module_info));
+        if (!macho_module_info) goto leave;
+        macho_info->module = module_new(pcs, filename, DMT_MACHO, FALSE, load_addr,
+                                        fmap.segs_size, 0, calc_crc32(fmap.fd));
+        if (!macho_info->module)
+        {
+            HeapFree(GetProcessHeap(), 0, macho_module_info);
+            goto leave;
+        }
+        macho_info->module->macho_info = macho_module_info;
+        macho_info->module->macho_info->load_addr = load_addr;
+
+        if (dbghelp_options & SYMOPT_DEFERRED_LOADS)
+            macho_info->module->module.SymType = SymDeferred;
+        else if (!macho_load_debug_info(macho_info->module, &fmap))
+            ret = FALSE;
+
+        macho_info->module->macho_info->in_use = 1;
+        macho_info->module->macho_info->is_loader = 0;
+        TRACE("module = %p\n", macho_info->module);
+    }
+
+    if (macho_info->flags & MACHO_INFO_NAME)
+    {
+        WCHAR*  ptr;
+        ptr = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(filename) + 1) * sizeof(WCHAR));
+        if (ptr)
+        {
+            strcpyW(ptr, filename);
+            macho_info->module_name = ptr;
+        }
+        else ret = FALSE;
+        TRACE("module_name = %p %s\n", macho_info->module_name, debugstr_w(macho_info->module_name));
+    }
+leave:
+    macho_unmap_file(&fmap);
+
+    TRACE(" => %d\n", ret);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_load_file_from_path
+ * Tries to load a Mach-O file from a set of paths (separated by ':')
+ */
+static BOOL macho_load_file_from_path(HANDLE hProcess,
+                                      const WCHAR* filename,
+                                      unsigned long load_addr,
+                                      const char* path,
+                                      struct macho_info* macho_info)
+{
+    BOOL                ret = FALSE;
+    WCHAR               *s, *t, *fn;
+    WCHAR*              pathW = NULL;
+    unsigned            len;
+
+    TRACE("(%p, %s, 0x%08lx, %s, %p)\n", hProcess, debugstr_w(filename), load_addr,
+            debugstr_a(path), macho_info);
+
+    if (!path) return FALSE;
+
+    len = MultiByteToWideChar(CP_UNIXCP, 0, path, -1, NULL, 0);
+    pathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+    if (!pathW) return FALSE;
+    MultiByteToWideChar(CP_UNIXCP, 0, path, -1, pathW, len);
+
+    for (s = pathW; s && *s; s = (t) ? (t+1) : NULL)
+    {
+        t = strchrW(s, ':');
+        if (t) *t = '\0';
+        fn = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(filename) + 1 + lstrlenW(s) + 1) * sizeof(WCHAR));
+        if (!fn) break;
+        strcpyW(fn, s);
+        strcatW(fn, S_SlashW);
+        strcatW(fn, filename);
+        ret = macho_load_file(hProcess, fn, load_addr, macho_info);
+        HeapFree(GetProcessHeap(), 0, fn);
+        if (ret) break;
+        s = (t) ? (t+1) : NULL;
+    }
+
+    TRACE(" => %d\n", ret);
+    HeapFree(GetProcessHeap(), 0, pathW);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_load_file_from_dll_path
+ *
+ * Tries to load a Mach-O file from the dll path
+ */
+static BOOL macho_load_file_from_dll_path(HANDLE hProcess,
+                                          const WCHAR* filename,
+                                          unsigned long load_addr,
+                                          struct macho_info* macho_info)
+{
+    BOOL ret = FALSE;
+    unsigned int index = 0;
+    const char *path;
+
+    TRACE("(%p, %s, 0x%08lx, %p)\n", hProcess, debugstr_w(filename), load_addr,
+            macho_info);
+
+    while (!ret && (path = wine_dll_enum_load_path( index++ )))
+    {
+        WCHAR *name;
+        unsigned len;
+
+        len = MultiByteToWideChar(CP_UNIXCP, 0, path, -1, NULL, 0);
+
+        name = HeapAlloc( GetProcessHeap(), 0,
+                          (len + lstrlenW(filename) + 2) * sizeof(WCHAR) );
+
+        if (!name) break;
+        MultiByteToWideChar(CP_UNIXCP, 0, path, -1, name, len);
+        strcatW( name, S_SlashW );
+        strcatW( name, filename );
+        ret = macho_load_file(hProcess, name, load_addr, macho_info);
+        HeapFree( GetProcessHeap(), 0, name );
+    }
+    TRACE(" => %d\n", ret);
+    return ret;
+}
+
+/******************************************************************
+ *              macho_search_and_load_file
+ *
+ * Lookup a file in standard Mach-O locations, and if found, load it
+ */
+static BOOL macho_search_and_load_file(struct process* pcs, const WCHAR* filename,
+                                       unsigned long load_addr,
+                                       struct macho_info* macho_info)
+{
+    BOOL                ret = FALSE;
+    struct module*      module;
+    static WCHAR        S_libstdcPPW[] = {'l','i','b','s','t','d','c','+','+','\0'};
+    const WCHAR*        p;
+
+    TRACE("(%p/%p, %s, 0x%08lx, %p)\n", pcs, pcs->handle, debugstr_w(filename), load_addr,
+            macho_info);
+
+    if (filename == NULL || *filename == '\0') return FALSE;
+    if ((module = module_is_already_loaded(pcs, filename)))
+    {
+        macho_info->module = module;
+        module->macho_info->in_use = 1;
+        return module->module.SymType;
+    }
+
+    if (strstrW(filename, S_libstdcPPW)) return FALSE; /* We know we can't do it */
+
+    /* If has no directories, try LD_LIBRARY_PATH first. */
+    if (!strchrW(filename, '/'))
+    {
+        ret = macho_load_file_from_path(pcs, filename, load_addr,
+                                      getenv("PATH"), macho_info);
+    }
+    /* Try DYLD_LIBRARY_PATH, with just the filename (no directories). */
+    if (!ret)
+    {
+        if ((p = strrchrW(filename, '/'))) p++;
+        else p = filename;
+        ret = macho_load_file_from_path(pcs, p, load_addr,
+                                      getenv("DYLD_LIBRARY_PATH"), macho_info);
+    }
+    /* Try the path as given. */
+    if (!ret)
+        ret = macho_load_file(pcs, filename, load_addr, macho_info);
+    /* Try DYLD_FALLBACK_LIBRARY_PATH, with just the filename (no directories). */
+    if (!ret)
+    {
+        ret = macho_load_file_from_path(pcs, p, load_addr,
+                                      getenv("DYLD_FALLBACK_LIBRARY_PATH"), macho_info);
+    }
+    if (!ret && !strchrW(filename, '/'))
+        ret = macho_load_file_from_dll_path(pcs, filename, load_addr, macho_info);
+
+    return ret;
+}
+
+/******************************************************************
+ *              macho_enum_modules_internal
+ *
+ * Enumerate Mach-O modules from a running process
+ */
+static BOOL macho_enum_modules_internal(const struct process* pcs,
+                                        const WCHAR* main_name,
+                                        enum_modules_cb cb, void* user)
+{
+    struct dyld_all_image_infos image_infos;
+    struct dyld_image_info*     info_array = NULL;
+    unsigned long               len;
+    int                         i;
+    char                        bufstr[256];
+    WCHAR                       bufstrW[MAX_PATH];
+    BOOL                        ret = FALSE;
+
+    TRACE("(%p/%p, %s, %p, %p)\n", pcs, pcs->handle, debugstr_w(main_name), cb,
+            user);
+
+    if (!pcs->dbg_hdr_addr ||
+        !ReadProcessMemory(pcs->handle, (void*)pcs->dbg_hdr_addr,
+                           &image_infos, sizeof(image_infos), NULL) ||
+        (image_infos.version != 1 && image_infos.version != 2) ||
+        !image_infos.infoArray)
+        goto done;
+    TRACE("Process has %u image infos at %p\n", image_infos.infoArrayCount, image_infos.infoArray);
+
+    len = image_infos.infoArrayCount * sizeof(info_array[0]);
+    info_array = HeapAlloc(GetProcessHeap(), 0, len);
+    if (!info_array ||
+        !ReadProcessMemory(pcs->handle, image_infos.infoArray,
+                           info_array, len, NULL))
+        goto done;
+    TRACE("... read image infos\n");
+
+    for (i = 0; i < image_infos.infoArrayCount; i++)
+    {
+        if (info_array[i].imageFilePath != NULL &&
+            ReadProcessMemory(pcs->handle, info_array[i].imageFilePath, bufstr, sizeof(bufstr), NULL))
+        {
+            bufstr[sizeof(bufstr) - 1] = '\0';
+            TRACE("[%d] image file %s\n", i, debugstr_a(bufstr));
+            MultiByteToWideChar(CP_UNIXCP, 0, bufstr, -1, bufstrW, sizeof(bufstrW) / sizeof(WCHAR));
+            if (main_name && !bufstrW[0]) strcpyW(bufstrW, main_name);
+            if (!cb(bufstrW, (unsigned long)info_array[i].imageLoadAddress, user)) break;
+        }
+    }
+
+    ret = TRUE;
+done:
+    HeapFree(GetProcessHeap(), 0, info_array);
+    return ret;
+}
+
+struct macho_sync
+{
+    struct process*     pcs;
+    struct macho_info   macho_info;
+};
+
+static BOOL macho_enum_sync_cb(const WCHAR* name, unsigned long addr, void* user)
+{
+    struct macho_sync*  ms = user;
+
+    TRACE("(%s, 0x%08lx, %p)\n", debugstr_w(name), addr, user);
+    macho_search_and_load_file(ms->pcs, name, addr, &ms->macho_info);
+    return TRUE;
+}
+
+/******************************************************************
+ *              macho_synchronize_module_list
+ *
+ * Rescans the debuggee's modules list and synchronizes it with
+ * the one from 'pcs', ie:
+ * - if a module is in debuggee and not in pcs, it's loaded into pcs
+ * - if a module is in pcs and not in debuggee, it's unloaded from pcs
+ */
+BOOL    macho_synchronize_module_list(struct process* pcs)
+{
+    struct module*      module;
+    struct macho_sync     ms;
+
+    TRACE("(%p/%p)\n", pcs, pcs->handle);
+
+    for (module = pcs->lmodules; module; module = module->next)
+    {
+        if (module->type == DMT_MACHO && !module->is_virtual)
+            module->macho_info->in_use = 0;
+    }
+
+    ms.pcs = pcs;
+    ms.macho_info.flags = MACHO_INFO_MODULE;
+    if (!macho_enum_modules_internal(pcs, NULL, macho_enum_sync_cb, &ms))
+        return FALSE;
+
+    module = pcs->lmodules;
+    while (module)
+    {
+        if (module->type == DMT_MACHO && !module->is_virtual &&
+            !module->macho_info->in_use && !module->macho_info->is_loader)
+        {
+            module_remove(pcs, module);
+            /* restart all over */
+            module = pcs->lmodules;
+        }
+        else module = module->next;
+    }
+    return TRUE;
+}
+
+/******************************************************************
+ *              macho_search_loader
+ *
+ * Lookup in a running Mach-O process the loader, and sets its Mach-O link
+ * address (for accessing the list of loaded images) in pcs.
+ * If flags is MACHO_INFO_MODULE, the module for the loader is also
+ * added as a module into pcs.
+ */
+static BOOL macho_search_loader(struct process* pcs, struct macho_info* macho_info)
+{
+    BOOL                ret;
+    const char*         ptr;
+
+    TRACE("(%p/%p, %p)\n", pcs, pcs->handle, macho_info);
+
+    /* All binaries are loaded with WINELOADER (if run from tree) or by the
+     * main executable
+     */
+    if ((ptr = getenv("WINELOADER")))
+    {
+        WCHAR   tmp[MAX_PATH];
+        MultiByteToWideChar(CP_UNIXCP, 0, ptr, -1, tmp, sizeof(tmp) / sizeof(WCHAR));
+        ret = macho_search_and_load_file(pcs, tmp, 0, macho_info);
+    }
+    else
+    {
+        ret = macho_search_and_load_file(pcs, S_WineW, 0, macho_info);
+    }
+    return ret;
+}
+
+/******************************************************************
+ *              macho_read_wine_loader_dbg_info
+ *
+ * Try to find a decent wine executable which could have loaded the debuggee
+ */
+BOOL macho_read_wine_loader_dbg_info(struct process* pcs)
+{
+    struct macho_info     macho_info;
+
+    TRACE("(%p/%p)\n", pcs, pcs->handle);
+    macho_info.flags = MACHO_INFO_DEBUG_HEADER | MACHO_INFO_MODULE;
+    if (!macho_search_loader(pcs, &macho_info)) return FALSE;
+    macho_info.module->macho_info->is_loader = 1;
+    module_set_module(macho_info.module, S_WineLoaderW);
+    return (pcs->dbg_hdr_addr = macho_info.dbg_hdr_addr) != 0;
+}
+
+/******************************************************************
+ *              macho_enum_modules
+ *
+ * Enumerates the Mach-O loaded modules from a running target (hProc)
+ * This function doesn't require that someone has called SymInitialize
+ * on this very process.
+ */
+BOOL macho_enum_modules(HANDLE hProc, enum_modules_cb cb, void* user)
+{
+    struct process      pcs;
+    struct macho_info   macho_info;
+    BOOL                ret;
+
+    TRACE("(%p, %p, %p)\n", hProc, cb, user);
+    memset(&pcs, 0, sizeof(pcs));
+    pcs.handle = hProc;
+    macho_info.flags = MACHO_INFO_DEBUG_HEADER | MACHO_INFO_NAME;
+    if (!macho_search_loader(&pcs, &macho_info)) return FALSE;
+    pcs.dbg_hdr_addr = macho_info.dbg_hdr_addr;
+    ret = macho_enum_modules_internal(&pcs, macho_info.module_name, cb, user);
+    HeapFree(GetProcessHeap(), 0, (char*)macho_info.module_name);
+    return ret;
+}
+
+struct macho_load
+{
+    struct process*     pcs;
+    struct macho_info   macho_info;
+    const WCHAR*        name;
+    BOOL                ret;
+};
+
+/******************************************************************
+ *              macho_load_cb
+ *
+ * Callback for macho_load_module, used to walk the list of loaded
+ * modules.
+ */
+static BOOL macho_load_cb(const WCHAR* name, unsigned long addr, void* user)
+{
+    struct macho_load*  ml = user;
+    const WCHAR*        p;
+
+    TRACE("(%s, 0x%08lx, %p)\n", debugstr_w(name), addr, user);
+
+    /* memcmp is needed for matches when bufstr contains also version information
+     * ml->name: libc.so, name: libc.so.6.0
+     */
+    p = strrchrW(name, '/');
+    if (!p++) p = name;
+    if (!memcmp(p, ml->name, lstrlenW(ml->name) * sizeof(WCHAR)))
+    {
+        ml->ret = macho_search_and_load_file(ml->pcs, name, addr, &ml->macho_info);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+/******************************************************************
+ *              macho_load_module
+ *
+ * Loads a Mach-O module and stores it in process' module list.
+ * Also, find module real name and load address from
+ * the real loaded modules list in pcs address space.
+ */
+struct module*  macho_load_module(struct process* pcs, const WCHAR* name, unsigned long addr)
+{
+    struct macho_load   ml;
+
+    TRACE("(%p/%p, %s, 0x%08lx)\n", pcs, pcs->handle, debugstr_w(name), addr);
+
+    ml.macho_info.flags = MACHO_INFO_MODULE;
+    ml.ret = FALSE;
+
+    if (pcs->dbg_hdr_addr) /* we're debugging a live target */
+    {
+        ml.pcs = pcs;
+        /* do only the lookup from the filename, not the path (as we lookup module
+         * name in the process' loaded module list)
+         */
+        ml.name = strrchrW(name, '/');
+        if (!ml.name++) ml.name = name;
+        ml.ret = FALSE;
+
+        if (!macho_enum_modules_internal(pcs, NULL, macho_load_cb, &ml))
+            return NULL;
+    }
+    else if (addr)
+    {
+        ml.name = name;
+        ml.ret = macho_search_and_load_file(pcs, ml.name, addr, &ml.macho_info);
+    }
+    if (!ml.ret) return NULL;
+    assert(ml.macho_info.module);
+    return ml.macho_info.module;
+}
+
+#else   /* !__MACH__ */
+
+BOOL    macho_synchronize_module_list(struct process* pcs)
+{
+    return FALSE;
+}
+
+BOOL macho_fetch_file_info(const WCHAR* name, DWORD* base,
+                           DWORD* size, DWORD* checksum)
+{
+    return FALSE;
+}
+
+BOOL macho_read_wine_loader_dbg_info(struct process* pcs)
+{
+    return FALSE;
+}
+
+BOOL macho_enum_modules(HANDLE hProc, enum_modules_cb cb, void* user)
+{
+    return FALSE;
+}
+
+struct module*  macho_load_module(struct process* pcs, const WCHAR* name, unsigned long addr)
+{
+    return NULL;
+}
+
+BOOL macho_load_debug_info(struct module* module, struct macho_file_map* fmap)
+{
+    return FALSE;
+}
+#endif  /* __MACH__ */
diff --git a/dlls/dbghelp/module.c b/dlls/dbghelp/module.c
index abccf2c93f9..3e32a82ca69 100644
--- a/dlls/dbghelp/module.c
+++ b/dlls/dbghelp/module.c
@@ -111,6 +111,7 @@ static const char*      get_module_type(enum module_type type, BOOL virtual)
     {
     case DMT_ELF: return virtual ? "Virtual ELF" : "ELF";
     case DMT_PE: return virtual ? "Virtual PE" : "PE";
+    case DMT_MACHO: return virtual ? "Virtual Mach-O" : "Mach-O";
     default: return "---";
     }
 }
@@ -125,7 +126,7 @@ struct module* module_new(struct process* pcs, const WCHAR* name,
 {
     struct module*      module;
 
-    assert(type == DMT_ELF || type == DMT_PE);
+    assert(type == DMT_ELF || type == DMT_PE || type == DMT_MACHO);
     if (!(module = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*module))))
 	return NULL;
 
@@ -316,6 +317,9 @@ BOOL module_get_debug(struct module_pair* pair)
                          ret ? CBA_DEFERRED_SYMBOL_LOAD_COMPLETE : CBA_DEFERRED_SYMBOL_LOAD_FAILURE,
                          &idslW64);
             break;
+        case DMT_MACHO:
+            ret = macho_load_debug_info(pair->effective, NULL);
+            break;
         default:
             ret = FALSE;
             break;
@@ -341,7 +345,8 @@ struct module* module_find_by_addr(const struct process* pcs, unsigned long addr
     if (type == DMT_UNKNOWN)
     {
         if ((module = module_find_by_addr(pcs, addr, DMT_PE)) ||
-            (module = module_find_by_addr(pcs, addr, DMT_ELF)))
+            (module = module_find_by_addr(pcs, addr, DMT_ELF)) ||
+            (module = module_find_by_addr(pcs, addr, DMT_MACHO)))
             return module;
     }
     else
@@ -376,7 +381,7 @@ static BOOL module_is_container_loaded(const struct process* pcs,
 
     for (module = pcs->lmodules; module; module = module->next)
     {
-        if (module->type == DMT_ELF &&
+        if ((module->type == DMT_ELF || module->type == DMT_MACHO) &&
             base >= module->module.BaseOfImage &&
             base < module->module.BaseOfImage + module->module.ImageSize)
         {
@@ -416,8 +421,14 @@ enum module_type module_get_type_by_name(const WCHAR* name)
     } while (len);
 
     /* check for terminating .so or .so.[digit] */
+    /* FIXME: Can't rely solely on extension; have to check magic or
+     *        stop using .so on Mac OS X.  For now, base on platform. */
     if (len > 3 && !memcmp(name + len - 3, S_DotSoW, 3))
+#ifdef __APPLE__
+        return DMT_MACHO;
+#else
         return DMT_ELF;
+#endif
 
     if (len > 4 && !strncmpiW(name + len - 4, S_DotPdbW, 4))
         return DMT_PDB;
@@ -425,10 +436,15 @@ enum module_type module_get_type_by_name(const WCHAR* name)
     if (len > 4 && !strncmpiW(name + len - 4, S_DotDbgW, 4))
         return DMT_DBG;
 
-    /* wine is also an ELF module */
+    /* wine is also a native module (Mach-O on Mac OS X, ELF elsewhere) */
     if (((len > 4 && name[len - 5] == '/') || len == 4) && !strcmpiW(name + len - 4, S_WineW))
+    {
+#ifdef __APPLE__
+        return DMT_MACHO;
+#else
         return DMT_ELF;
-
+#endif
+    }
     return DMT_PE;
 }
 
@@ -513,8 +529,9 @@ DWORD64 WINAPI  SymLoadModuleExW(HANDLE hProcess, HANDLE hFile, PCWSTR wImageNam
     if (Flags & ~(SLMFLAG_VIRTUAL))
         FIXME("Unsupported Flags %08x for %s\n", Flags, debugstr_w(wImageName));
 
-    /* force transparent ELF loading / unloading */
+    /* force transparent ELF and Mach-O loading / unloading */
     elf_synchronize_module_list(pcs);
+    macho_synchronize_module_list(pcs);
 
     /* this is a Wine extension to the API just to redo the synchronisation */
     if (!wImageName && !hFile) return 0;
@@ -534,11 +551,22 @@ DWORD64 WINAPI  SymLoadModuleExW(HANDLE hProcess, HANDLE hFile, PCWSTR wImageNam
     if (!module)
     {
         /* otherwise, try a regular PE module */
-        if (!(module = pe_load_native_module(pcs, wImageName, hFile, BaseOfDll, SizeOfDll)))
+        if (!(module = pe_load_native_module(pcs, wImageName, hFile, BaseOfDll, SizeOfDll)) &&
+            wImageName)
         {
-            /* and finally and ELF module */
-            if (wImageName && (module_get_type_by_name(wImageName) == DMT_ELF))
-                module = elf_load_module(pcs, wImageName, BaseOfDll);
+            /* and finally an ELF or Mach-O module */
+            switch (module_get_type_by_name(wImageName))
+            {
+                case DMT_ELF:
+                    module = elf_load_module(pcs, wImageName, BaseOfDll);
+                    break;
+                case DMT_MACHO:
+                    module = macho_load_module(pcs, wImageName, BaseOfDll);
+                    break;
+                default:
+                    /* Ignored */
+                    break;
+            }
         }
     }
     if (!module)
@@ -709,7 +737,8 @@ BOOL  WINAPI SymEnumerateModulesW64(HANDLE hProcess,
     
     for (module = pcs->lmodules; module; module = module->next)
     {
-        if (!(dbghelp_options & SYMOPT_WINE_WITH_NATIVE_MODULES) && module->type == DMT_ELF)
+        if (!(dbghelp_options & SYMOPT_WINE_WITH_NATIVE_MODULES) &&
+            (module->type == DMT_ELF || module->type == DMT_MACHO))
             continue;
         if (!EnumModulesCallback(module->module.ModuleName,
                                  module->module.BaseOfImage, UserContext))
diff --git a/dlls/dbghelp/path.c b/dlls/dbghelp/path.c
index 6af51d098fc..12c595b4d1e 100644
--- a/dlls/dbghelp/path.c
+++ b/dlls/dbghelp/path.c
@@ -522,6 +522,21 @@ static BOOL CALLBACK module_find_cb(PCWSTR buffer, PVOID user)
             return FALSE;
         }
         break;
+    case DMT_MACHO:
+        if (macho_fetch_file_info(buffer, 0, &size, &checksum))
+        {
+            matched++;
+            if (checksum == mf->dw1) matched++;
+            else
+                WARN("Found %s, but wrong checksums: %08x %08x\n",
+                     debugstr_w(buffer), checksum, mf->dw1);
+        }
+        else
+        {
+            WARN("Couldn't read %s\n", debugstr_w(buffer));
+            return FALSE;
+        }
+        break;
     case DMT_PDB:
         {
             struct pdb_lookup   pdb_lookup;
diff --git a/dlls/dbghelp/symbol.c b/dlls/dbghelp/symbol.c
index 16866075ca9..ce3b8420afe 100644
--- a/dlls/dbghelp/symbol.c
+++ b/dlls/dbghelp/symbol.c
@@ -1013,7 +1013,7 @@ static BOOL sym_enum(HANDLE hProcess, ULONG64 BaseOfDll, PCSTR Mask,
         {
             for (pair.requested = pair.pcs->lmodules; pair.requested; pair.requested = pair.requested->next)
             {
-                if (pair.requested->type == DMT_ELF &&
+                if ((pair.requested->type == DMT_ELF || pair.requested->type == DMT_MACHO) &&
                     !module_get_containee(pair.pcs, pair.requested) &&
                     module_get_debug(&pair))
                 {
@@ -1340,7 +1340,8 @@ BOOL WINAPI SymFromName(HANDLE hProcess, PCSTR Name, PSYMBOL_INFO Symbol)
     {
         for (module = pcs->lmodules; module; module = module->next)
         {
-            if (module->type == DMT_ELF && !module_get_containee(pcs, module) &&
+            if ((module->type == DMT_ELF || module->type == DMT_MACHO) &&
+                !module_get_containee(pcs, module) &&
                 find_name(pcs, module, Name, Symbol))
                 return TRUE;
         }
-- 
GitLab