/*
 * 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"

#ifdef HAVE_MACH_O_LOADER_H
#include <CoreFoundation/CFString.h>
#define LoadResource mac_LoadResource
#define GetCurrentThread mac_GetCurrentThread
#include <CoreServices/CoreServices.h>
#undef LoadResource
#undef GetCurrentThread
#endif

#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
#include <errno.h>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "dbghelp_private.h"
#include "winternl.h"
#include "wine/library.h"
#include "wine/debug.h"
#include "wine/heap.h"
#include "image_private.h"

#ifdef HAVE_MACH_O_LOADER_H

#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/dyld.h>

struct dyld_image_info32 {
    uint32_t /* const struct mach_header* */    imageLoadAddress;
    uint32_t /* const char* */                  imageFilePath;
    uint32_t /* uintptr_t */                    imageFileModDate;
};

struct dyld_all_image_infos32 {
    uint32_t                                        version;
    uint32_t                                        infoArrayCount;
    uint32_t /* const struct dyld_image_info* */    infoArray;
};

struct dyld_image_info64 {
    uint64_t /* const struct mach_header* */    imageLoadAddress;
    uint64_t /* const char* */                  imageFilePath;
    uint64_t /* uintptr_t */                    imageFileModDate;
};

struct dyld_all_image_infos64 {
    uint32_t                                        version;
    uint32_t                                        infoArrayCount;
    uint64_t /* const struct dyld_image_info* */    infoArray;
};

union wine_image_info {
    struct dyld_image_info32 info32;
    struct dyld_image_info64 info64;
};

union wine_all_image_infos {
    struct dyld_all_image_infos32 infos32;
    struct dyld_all_image_infos64 infos64;
};

#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);


/* Bitmask for Mach-O image header flags indicating that the image is in dyld's
   shared cached.  That implies that its segments are mapped non-contiguously.
   This value isn't defined anywhere in headers.  It's used in dyld and in
   debuggers which support OS X as a magic number.

   The flag also isn't set in the on-disk image file.  It's only set in
   memory by dyld. */
#define MACHO_DYLD_IN_SHARED_CACHE 0x80000000


#define UUID_STRING_LEN 37 /* 16 bytes at 2 hex digits apiece, 4 dashes, and the null terminator */


struct macho_module_info
{
    struct image_file_map       file_map;
    unsigned long               load_addr;
    unsigned short              in_use : 1,
                                is_loader : 1;
};

struct section_info
{
    BOOL            split_segs;
    unsigned int    section_index;
};

#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) */
};

static void macho_unmap_file(struct image_file_map* fmap);

static char* format_uuid(const uint8_t uuid[16], char out[UUID_STRING_LEN])
{
    sprintf(out, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
            uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
            uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);
    return out;
}

/******************************************************************
 *              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 long offset,
                             unsigned long len, unsigned long* out_aligned_offset,
                             unsigned long* out_aligned_end, unsigned long* out_aligned_len,
                             unsigned long* out_misalign)
{
    unsigned long pagemask = sysconf( _SC_PAGESIZE ) - 1;
    unsigned long 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 long offset, unsigned long len,
                                   const char** base)
{
    unsigned long   misalign, aligned_offset, aligned_map_end, map_size;
    const void*     aligned_ptr;

    TRACE("(%p/%d, 0x%08lx, 0x%08lx)\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%08lx - 0x%08lx) to %p\n", aligned_offset, aligned_map_end, aligned_ptr);

    if (aligned_ptr == MAP_FAILED) return IMAGE_NO_MAP;
    if (base)
        *base = aligned_ptr;
    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 char** base, const void** mapped, const struct macho_file_map* fmap,
                              unsigned long offset, unsigned long len)
{
    TRACE("(%p, %p, %p/%d, 0x%08lx, 0x%08lx)\n", base, mapped, fmap, fmap->fd, offset, len);

    if ((mapped && *mapped != IMAGE_NO_MAP) || (base && *base != IMAGE_NO_MAP))
    {
        unsigned long   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);

        if (mapped)
            aligned_ptr = (char*)*mapped - misalign;
        else
            aligned_ptr = (void*)*base;
        if (munmap(aligned_ptr, map_size) < 0)
            WARN("Couldn't unmap the range\n");
        TRACE("Unmapped (0x%08lx - 0x%08lx) from %p - %p\n", aligned_offset, aligned_map_end, aligned_ptr, (char*)aligned_ptr + map_size);
        if (mapped)
            *mapped = IMAGE_NO_MAP;
        if (base)
            *base = IMAGE_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 long offset1, unsigned long len1,
                             unsigned long offset2, unsigned long len2,
                             const void** mapped1, const void** mapped2)
{
    unsigned long aligned_offset1, aligned_map_end1;
    unsigned long aligned_offset2, aligned_map_end2;

    TRACE("(%p/%d, 0x%08lx, 0x%08lx, 0x%08lx, 0x%08lx, %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, NULL);
        if (*mapped1 != IMAGE_NO_MAP)
        {
            *mapped2 = macho_map_range(fmap, offset2, len2, NULL);
            if (*mapped2 == IMAGE_NO_MAP)
                macho_unmap_range(NULL, mapped1, fmap, offset1, len1);
        }
    }
    else
    {
        if (offset1 < offset2)
        {
            *mapped1 = macho_map_range(fmap, offset1, offset2 + len2 - offset1, NULL);
            if (*mapped1 != IMAGE_NO_MAP)
                *mapped2 = (const char*)*mapped1 + offset2 - offset1;
        }
        else
        {
            *mapped2 = macho_map_range(fmap, offset2, offset1 + len1 - offset2, NULL);
            if (*mapped2 != IMAGE_NO_MAP)
                *mapped1 = (const char*)*mapped2 + offset1 - offset2;
        }
    }

    TRACE(" => %p, %p\n", *mapped1, *mapped2);

    return (*mapped1 != IMAGE_NO_MAP) && (*mapped2 != IMAGE_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 long offset1, unsigned long len1,
                               unsigned long offset2, unsigned long len2,
                               const void** mapped1, const void** mapped2)
{
    unsigned long   aligned_offset1, aligned_map_end1;
    unsigned long   aligned_offset2, aligned_map_end2;

    TRACE("(%p/%d, 0x%08lx, 0x%08lx, 0x%08lx, 0x%08lx, %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(NULL, mapped1, fmap, offset1, len1);
        macho_unmap_range(NULL, mapped2, fmap, offset2, len2);
    }
    else
    {
        if (offset1 < offset2)
        {
            macho_unmap_range(NULL, mapped1, fmap, offset1, offset2 + len2 - offset1);
            *mapped2 = IMAGE_NO_MAP;
        }
        else
        {
            macho_unmap_range(NULL, mapped2, fmap, offset2, offset1 + len1 - offset2);
            *mapped1 = IMAGE_NO_MAP;
        }
    }
}

/******************************************************************
 *              macho_find_section
 */
BOOL macho_find_section(struct image_file_map* ifm, const char* segname, const char* sectname, struct image_section_map* ism)
{
    struct macho_file_map* fmap;
    unsigned i;
    char tmp[sizeof(fmap->sect[0].section.sectname)];

    /* Other parts of dbghelp use section names like ".eh_frame".  Mach-O uses
       names like "__eh_frame".  Convert those. */
    if (sectname[0] == '.')
    {
        lstrcpynA(tmp, "__", sizeof(tmp));
        lstrcpynA(tmp + 2, sectname + 1, sizeof(tmp) - 2);
        sectname = tmp;
    }

    while (ifm)
    {
        fmap = &ifm->u.macho;
        for (i = 0; i < fmap->num_sections; i++)
        {
            if (!fmap->sect[i].ignored &&
                strcmp(fmap->sect[i].section.sectname, sectname) == 0 &&
                (!segname || strcmp(fmap->sect[i].section.segname, segname) == 0))
            {
                ism->fmap = ifm;
                ism->sidx = i;
                return TRUE;
            }
        }
        ifm = fmap->dsym;
    }

    ism->fmap = NULL;
    ism->sidx = -1;
    return FALSE;
}

/******************************************************************
 *              macho_map_section
 */
const char* macho_map_section(struct image_section_map* ism)
{
    struct macho_file_map* fmap = &ism->fmap->u.macho;

    assert(ism->fmap->modtype == DMT_MACHO);
    if (ism->sidx < 0 || ism->sidx >= ism->fmap->u.macho.num_sections || fmap->sect[ism->sidx].ignored)
        return IMAGE_NO_MAP;

    return macho_map_range(fmap, fmap->sect[ism->sidx].section.offset, fmap->sect[ism->sidx].section.size,
                           &fmap->sect[ism->sidx].mapped);
}

/******************************************************************
 *              macho_unmap_section
 */
void macho_unmap_section(struct image_section_map* ism)
{
    struct macho_file_map* fmap = &ism->fmap->u.macho;

    if (ism->sidx >= 0 && ism->sidx < fmap->num_sections && fmap->sect[ism->sidx].mapped != IMAGE_NO_MAP)
    {
        macho_unmap_range(&fmap->sect[ism->sidx].mapped, NULL, fmap, fmap->sect[ism->sidx].section.offset,
                          fmap->sect[ism->sidx].section.size);
    }
}

/******************************************************************
 *              macho_get_map_rva
 */
DWORD_PTR macho_get_map_rva(const struct image_section_map* ism)
{
    if (ism->sidx < 0 || ism->sidx >= ism->fmap->u.macho.num_sections ||
        ism->fmap->u.macho.sect[ism->sidx].ignored)
        return 0;
    return ism->fmap->u.macho.sect[ism->sidx].section.addr - ism->fmap->u.macho.segs_start;
}

/******************************************************************
 *              macho_get_map_size
 */
unsigned macho_get_map_size(const struct image_section_map* ism)
{
    if (ism->sidx < 0 || ism->sidx >= ism->fmap->u.macho.num_sections ||
        ism->fmap->u.macho.sect[ism->sidx].ignored)
        return 0;
    return ism->fmap->u.macho.sect[ism->sidx].section.size;
}

/******************************************************************
 *              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 == IMAGE_NO_MAP)
    {
        fmap->load_commands = (const struct load_command*) macho_map_range(
                fmap, fmap->header_size, fmap->mach_header.sizeofcmds, NULL);
        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 != IMAGE_NO_MAP)
    {
        TRACE("Unmapping load commands: %p\n", fmap->load_commands);
        macho_unmap_range(NULL, (const void**)&fmap->load_commands, fmap,
                    fmap->header_size, 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 image_file_map *ifm, unsigned cmd,
                                    int (*cb)(struct image_file_map*, const struct load_command*, void*),
                                    void* user)
{
    struct macho_file_map* fmap = &ifm->u.macho;
    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)) == IMAGE_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(ifm, 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_count_sections
 *
 * Callback for macho_enum_load_commands.  Counts the number of
 * significant sections in a Mach-O file.  All commands are
 * expected to be of LC_SEGMENT[_64] type.
 */
static int macho_count_sections(struct image_file_map* ifm, const struct load_command* lc, void* user)
{
    char segname[16];
    uint32_t nsects;

    if (ifm->addr_size == 32)
    {
        const struct segment_command *sc = (const struct segment_command *)lc;
        memcpy(segname, sc->segname, sizeof(segname));
        nsects = sc->nsects;
    }
    else
    {
        const struct segment_command_64 *sc = (const struct segment_command_64 *)lc;
        memcpy(segname, sc->segname, sizeof(segname));
        nsects = sc->nsects;
    }

    TRACE("(%p/%d, %p, %p) segment %s\n", ifm, ifm->u.macho.fd, lc, user,
        debugstr_an(segname, sizeof(segname)));

    ifm->u.macho.num_sections += nsects;
    return 0;
}

/******************************************************************
 *              macho_load_section_info
 *
 * Callback for macho_enum_load_commands.  Accumulates the address
 * range covered by the segments of a Mach-O file and builds the
 * section map.  All commands are expected to be of LC_SEGMENT[_64] type.
 */
static int macho_load_section_info(struct image_file_map* ifm, const struct load_command* lc, void* user)
{
    struct macho_file_map*          fmap = &ifm->u.macho;
    struct section_info*            info = user;
    BOOL                            ignore;
    int                             i;
    unsigned long                   tmp, page_mask = sysconf( _SC_PAGESIZE ) - 1;
    uint64_t vmaddr, vmsize;
    char segname[16];
    uint32_t nsects;
    const void *sections;

    if (ifm->addr_size == 32)
    {
        const struct segment_command *sc = (const struct segment_command *)lc;
        vmaddr = sc->vmaddr;
        vmsize = sc->vmsize;
        memcpy(segname, sc->segname, sizeof(segname));
        nsects = sc->nsects;
        sections = (const void *)(sc + 1);
    }
    else
    {
        const struct segment_command_64 *sc = (const struct segment_command_64 *)lc;
        vmaddr = sc->vmaddr;
        vmsize = sc->vmsize;
        memcpy(segname, sc->segname, sizeof(segname));
        nsects = sc->nsects;
        sections = (const void *)(sc + 1);
    }

    TRACE("(%p/%d, %p, %p) before: 0x%08lx - 0x%08lx\n", fmap, fmap->fd, lc, user,
            (unsigned long)fmap->segs_start, (unsigned long)fmap->segs_size);
    TRACE("Segment command vm: 0x%08lx - 0x%08lx\n", (unsigned long)vmaddr,
            (unsigned long)(vmaddr + vmsize));

    /* Images in the dyld shared cache have their segments mapped non-contiguously.
       We don't know how to properly locate any of the segments other than __TEXT,
       so ignore them. */
    ignore = (info->split_segs && strcmp(segname, SEG_TEXT));

    if (!strncmp(segname, "WINE_", 5))
        TRACE("Ignoring special Wine segment %s\n", debugstr_an(segname, sizeof(segname)));
    else if (!strncmp(segname, "__PAGEZERO", 10))
        TRACE("Ignoring __PAGEZERO segment\n");
    else if (ignore)
        TRACE("Ignoring %s segment because image has split segments\n", segname);
    else
    {
        /* If this segment starts before previously-known earliest, record new earliest. */
        if (vmaddr < fmap->segs_start)
            fmap->segs_start = vmaddr;

        /* If this segment extends beyond previously-known furthest, record new furthest. */
        tmp = (vmaddr + vmsize + page_mask) & ~page_mask;
        if (fmap->segs_size < tmp) fmap->segs_size = tmp;

        TRACE("after: 0x%08lx - 0x%08lx\n", (unsigned long)fmap->segs_start, (unsigned long)fmap->segs_size);
    }

    for (i = 0; i < nsects; i++)
    {
        if (ifm->addr_size == 32)
        {
            const struct section *section = &((const struct section *)sections)[i];
            memcpy(fmap->sect[info->section_index].section.sectname, section->sectname, sizeof(section->sectname));
            memcpy(fmap->sect[info->section_index].section.segname,  section->segname,  sizeof(section->segname));
            fmap->sect[info->section_index].section.addr      = section->addr;
            fmap->sect[info->section_index].section.size      = section->size;
            fmap->sect[info->section_index].section.offset    = section->offset;
            fmap->sect[info->section_index].section.align     = section->align;
            fmap->sect[info->section_index].section.reloff    = section->reloff;
            fmap->sect[info->section_index].section.nreloc    = section->nreloc;
            fmap->sect[info->section_index].section.flags     = section->flags;
        }
        else
            fmap->sect[info->section_index].section = ((const struct section_64 *)sections)[i];

        fmap->sect[info->section_index].mapped = IMAGE_NO_MAP;
        fmap->sect[info->section_index].ignored = ignore;
        info->section_index++;
    }

    return 0;
}

/******************************************************************
 *              find_uuid
 *
 * Callback for macho_enum_load_commands.  Records the UUID load
 * command of a Mach-O file.
 */
static int find_uuid(struct image_file_map* ifm, const struct load_command* lc, void* user)
{
    ifm->u.macho.uuid = (const struct uuid_command*)lc;
    return 1;
}

/******************************************************************
 *              reset_file_map
 */
static inline void reset_file_map(struct image_file_map* ifm)
{
    struct macho_file_map* fmap = &ifm->u.macho;

    fmap->fd = -1;
    fmap->dsym = NULL;
    fmap->load_commands = IMAGE_NO_MAP;
    fmap->uuid = NULL;
    fmap->num_sections = 0;
    fmap->sect = NULL;
}

/******************************************************************
 *              macho_map_file
 *
 * Maps a Mach-O file into memory (and checks it's a real Mach-O file)
 */
static BOOL macho_map_file(struct process *pcs, const WCHAR *filenameW,
    BOOL split_segs, struct image_file_map* ifm)
{
    struct macho_file_map* fmap = &ifm->u.macho;
    struct fat_header   fat_header;
    struct stat         statbuf;
    int                 i;
    char*               filename;
    unsigned            len;
    struct section_info info;
    BOOL                ret = FALSE;
    cpu_type_t target_cpu = (pcs->is_64bit) ? CPU_TYPE_X86_64 : CPU_TYPE_X86;
    uint32_t target_magic = (pcs->is_64bit) ? MH_MAGIC_64 : MH_MAGIC;
    uint32_t target_cmd   = (pcs->is_64bit) ? LC_SEGMENT_64 : LC_SEGMENT;

    TRACE("(%s, %p)\n", debugstr_w(filenameW), fmap);

    reset_file_map(ifm);

    ifm->modtype = DMT_MACHO;
    ifm->addr_size = (pcs->is_64bit) ? 64 : 32;
    fmap->header_size = (pcs->is_64bit) ? sizeof(struct mach_header_64) : sizeof(struct mach_header);

    len = WideCharToMultiByte(CP_UNIXCP, 0, filenameW, -1, NULL, 0, NULL, NULL);
    if (!(filename = HeapAlloc(GetProcessHeap(), 0, len)))
    {
        WARN("failed to allocate filename buffer\n");
        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))
    {
        TRACE("stat() failed or %s is directory: %s\n", debugstr_a(filename), strerror(errno));
        goto done;
    }

    /* Now open the file, so that we can mmap() it. */
    if ((fmap->fd = open(filename, O_RDONLY)) == -1)
    {
        TRACE("failed to open file %s: %d\n", debugstr_a(filename), errno);
        goto done;
    }

    if (read(fmap->fd, &fat_header, sizeof(fat_header)) != sizeof(fat_header))
    {
        TRACE("failed to read fat header: %d\n", errno);
        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) == target_cpu)
            {
                fmap->arch_offset = swap_ulong_be_to_host(fat_arch.offset);
                break;
            }
        }
        if (i >= narch) goto done;
        TRACE("... found target arch (%d)\n", target_cpu);
    }
    else
    {
        fmap->arch_offset = 0;
        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 != target_magic ||
        fmap->mach_header.cputype != target_cpu) 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:
        case MH_DSYM:
            break;
        default:
            goto done;
    }
    TRACE("... verified Mach header\n");

    fmap->num_sections = 0;
    if (macho_enum_load_commands(ifm, target_cmd, macho_count_sections, NULL) < 0)
        goto done;
    TRACE("%d sections\n", fmap->num_sections);

    fmap->sect = HeapAlloc(GetProcessHeap(), 0, fmap->num_sections * sizeof(fmap->sect[0]));
    if (!fmap->sect)
        goto done;

    fmap->segs_size = 0;
    fmap->segs_start = ~0L;

    info.split_segs = split_segs;
    info.section_index = 0;
    if (macho_enum_load_commands(ifm, target_cmd, macho_load_section_info, &info) < 0)
    {
        fmap->num_sections = 0;
        goto done;
    }

    fmap->segs_size -= fmap->segs_start;
    TRACE("segs_start: 0x%08lx, segs_size: 0x%08lx\n", (unsigned long)fmap->segs_start,
            (unsigned long)fmap->segs_size);

    if (macho_enum_load_commands(ifm, LC_UUID, find_uuid, NULL) < 0)
        goto done;
    if (fmap->uuid)
    {
        char uuid_string[UUID_STRING_LEN];
        TRACE("UUID %s\n", format_uuid(fmap->uuid->uuid, uuid_string));
    }
    else
        TRACE("no UUID found\n");

    ret = TRUE;
done:
    if (!ret)
        macho_unmap_file(ifm);
    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 image_file_map* ifm)
{
    struct image_file_map* cursor;

    TRACE("(%p/%d)\n", ifm, ifm->u.macho.fd);

    cursor = ifm;
    while (cursor)
    {
        struct image_file_map* next;

        if (ifm->u.macho.fd != -1)
        {
            struct image_section_map ism;

            ism.fmap = ifm;
            for (ism.sidx = 0; ism.sidx < ifm->u.macho.num_sections; ism.sidx++)
                macho_unmap_section(&ism);

            HeapFree(GetProcessHeap(), 0, ifm->u.macho.sect);
            macho_unmap_load_commands(&ifm->u.macho);
            close(ifm->u.macho.fd);
            ifm->u.macho.fd = -1;
        }

        next = cursor->u.macho.dsym;
        if (cursor != ifm)
            HeapFree(GetProcessHeap(), 0, cursor);
        cursor = next;
    }
}

/******************************************************************
 *              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)
{
    BOOL ret;

    TRACE("(%p/%d, %u)\n", fmap, fmap->fd, sectidx);

    if (!sectidx) return FALSE;

    sectidx--; /* convert from 1-based to 0-based */
    if (sectidx >= fmap->num_sections || fmap->sect[sectidx].ignored) return FALSE;

    ret = (!(fmap->sect[sectidx].section.flags & SECTION_TYPE) &&
           (fmap->sect[sectidx].section.flags & (S_ATTR_PURE_INSTRUCTIONS|S_ATTR_SOME_INSTRUCTIONS)));
    TRACE("-> %d\n", ret);
    return ret;
}

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 image_file_map* ifm,
                              const struct load_command* lc, void* user)
{
    struct macho_file_map* fmap = &ifm->u.macho;
    const struct symtab_command*    sc = (const struct symtab_command*)lc;
    struct macho_debug_info*        mdi = user;
    const char*                     stabstr;
    int                             ret = 0;
    size_t stabsize = (ifm->addr_size == 32) ? sizeof(struct nlist) : sizeof(struct nlist_64);
    const char *stab;

    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 * stabsize,
            sc->stroff, sc->strsize, (const void**)&stab, (const void**)&stabstr))
        return 0;

    if (!stabs_parse(mdi->module,
                     mdi->module->format_info[DFI_MACHO]->u.macho_info->load_addr - fmap->segs_start,
                     stab, sc->nsyms * stabsize,
                     stabstr, sc->strsize, macho_stabs_def_cb, mdi))
        ret = -1;

    macho_unmap_ranges(fmap, sc->symoff, sc->nsyms * stabsize,
            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 = CONTAINING_RECORD(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->format_info[DFI_MACHO]->u.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->format_info[DFI_MACHO]->u.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_address(&sym->symt, &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(module, &sym->symt, TI_GET_LENGTH,   &size);
                symt_get_info(module, &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
            {
                struct location loc;

                loc.kind = loc_absolute;
                loc.reg = 0;
                loc.offset = ste->addr;
                symt_new_global_variable(module, ste->compiland, ste->ht_elt.name,
                                         !ste->is_global, loc, 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->is_code, ste->addr, 0);
        }
    }
}

/******************************************************************
 *              try_dsym
 *
 * Try to load a debug symbol file from the given path and check
 * if its UUID matches the UUID of an already-mapped file.  If so,
 * stash the file map in the "dsym" field of the file and return
 * TRUE.  If it can't be mapped or its UUID doesn't match, return
 * FALSE.
 */
static BOOL try_dsym(struct process *pcs, const WCHAR* path, struct macho_file_map* fmap)
{
    struct image_file_map dsym_ifm;

    if (macho_map_file(pcs, path, FALSE, &dsym_ifm))
    {
        char uuid_string[UUID_STRING_LEN];

        if (dsym_ifm.u.macho.uuid && !memcmp(dsym_ifm.u.macho.uuid->uuid, fmap->uuid->uuid, sizeof(fmap->uuid->uuid)))
        {
            TRACE("found matching debug symbol file at %s\n", debugstr_w(path));
            fmap->dsym = HeapAlloc(GetProcessHeap(), 0, sizeof(dsym_ifm));
            *fmap->dsym = dsym_ifm;
            return TRUE;
        }

        TRACE("candidate debug symbol file at %s has wrong UUID %s; ignoring\n", debugstr_w(path),
              format_uuid(dsym_ifm.u.macho.uuid->uuid, uuid_string));

        macho_unmap_file(&dsym_ifm);
    }
    else
        TRACE("couldn't map file at %s\n", debugstr_w(path));

    return FALSE;
}

/******************************************************************
 *              find_and_map_dsym
 *
 * Search for a debugging symbols file associated with a module and
 * map it.  First look for a .dSYM bundle next to the module file
 * (e.g. <path>.dSYM/Contents/Resources/DWARF/<basename of path>)
 * as produced by dsymutil.  Next, look for a .dwarf file next to
 * the module file (e.g. <path>.dwarf) as produced by
 * "dsymutil --flat".  Finally, use Spotlight to search for a
 * .dSYM bundle with the same UUID as the module file.
 */
static void find_and_map_dsym(struct process *pcs, struct module* module)
{
    static const WCHAR dot_dsym[] = {'.','d','S','Y','M',0};
    static const WCHAR dsym_subpath[] = {'/','C','o','n','t','e','n','t','s','/','R','e','s','o','u','r','c','e','s','/','D','W','A','R','F','/',0};
    static const WCHAR dot_dwarf[] = {'.','d','w','a','r','f',0};
    struct macho_file_map* fmap = &module->format_info[DFI_MACHO]->u.macho_info->file_map.u.macho;
    const WCHAR* p;
    size_t len;
    WCHAR* path = NULL;
    char uuid_string[UUID_STRING_LEN];
    CFStringRef uuid_cfstring;
    CFStringRef query_string;
    MDQueryRef query = NULL;

    /* Without a UUID, we can't verify that any debug info file we find corresponds
       to this file.  Better to have no debug info than incorrect debug info. */
    if (!fmap->uuid)
        return;

    if ((p = strrchrW(module->module.LoadedImageName, '/')))
        p++;
    else
        p = module->module.LoadedImageName;
    len = strlenW(module->module.LoadedImageName) + strlenW(dot_dsym) + strlenW(dsym_subpath) + strlenW(p) + 1;
    path = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
    if (!path)
        return;
    strcpyW(path, module->module.LoadedImageName);
    strcatW(path, dot_dsym);
    strcatW(path, dsym_subpath);
    strcatW(path, p);

    if (try_dsym(pcs, path, fmap))
        goto found;

    strcpyW(path + strlenW(module->module.LoadedImageName), dot_dwarf);

    if (try_dsym(pcs, path, fmap))
        goto found;

    format_uuid(fmap->uuid->uuid, uuid_string);
    uuid_cfstring = CFStringCreateWithCString(NULL, uuid_string, kCFStringEncodingASCII);
    query_string = CFStringCreateWithFormat(NULL, NULL, CFSTR("com_apple_xcode_dsym_uuids == \"%@\""), uuid_cfstring);
    CFRelease(uuid_cfstring);
    query = MDQueryCreate(NULL, query_string, NULL, NULL);
    CFRelease(query_string);
    MDQuerySetMaxCount(query, 1);
    if (MDQueryExecute(query, kMDQuerySynchronous) && MDQueryGetResultCount(query) >= 1)
    {
        MDItemRef item = (MDItemRef)MDQueryGetResultAtIndex(query, 0);
        CFStringRef item_path = MDItemCopyAttribute(item, kMDItemPath);
        if (item_path)
        {
            CFIndex item_path_len = CFStringGetLength(item_path);
            if (item_path_len + strlenW(dsym_subpath) + strlenW(p) >= len)
            {
                HeapFree(GetProcessHeap(), 0, path);
                len = item_path_len + strlenW(dsym_subpath) + strlenW(p) + 1;
                path = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
            }
            CFStringGetCharacters(item_path, CFRangeMake(0, item_path_len), (UniChar*)path);
            strcpyW(path + item_path_len, dsym_subpath);
            strcatW(path, p);
            CFRelease(item_path);

            if (try_dsym(pcs, path, fmap))
                goto found;
        }
    }

found:
    HeapFree(GetProcessHeap(), 0, path);
    if (query) CFRelease(query);
}

/******************************************************************
 *              image_uses_split_segs
 *
 * Determine if the Mach-O image loaded at a particular address in
 * the given process is in the dyld shared cache and therefore has
 * its segments mapped non-contiguously.
 *
 * The image header has to be loaded from the process's memory
 * because the relevant flag is only set in memory, not in the file.
 */
static BOOL image_uses_split_segs(HANDLE process, unsigned long load_addr)
{
    BOOL split_segs = FALSE;

    if (process && load_addr)
    {
        struct process *pcs = process_find_by_handle(process);
        cpu_type_t target_cpu = (pcs->is_64bit) ? CPU_TYPE_X86_64 : CPU_TYPE_X86;
        uint32_t target_magic = (pcs->is_64bit) ? MH_MAGIC_64 : MH_MAGIC;
        struct mach_header header;

        if (ReadProcessMemory(process, (void*)load_addr, &header, sizeof(header), NULL) &&
            header.magic == target_magic && header.cputype == target_cpu &&
            header.flags & MACHO_DYLD_IN_SHARED_CACHE)
        {
            split_segs = TRUE;
        }
    }

    return split_segs;
}

/******************************************************************
 *              macho_load_debug_info
 *
 * Loads Mach-O debugging information from the module image file.
 */
BOOL macho_load_debug_info(struct process *pcs, struct module* module)
{
    BOOL                    ret = FALSE;
    struct macho_debug_info mdi;
    int                     result;
    struct image_file_map  *ifm;
    struct macho_file_map  *fmap;

    if (module->type != DMT_MACHO || !module->format_info[DFI_MACHO]->u.macho_info)
    {
        ERR("Bad Mach-O module '%s'\n", debugstr_w(module->module.LoadedImageName));
        return FALSE;
    }

    ifm = &module->format_info[DFI_MACHO]->u.macho_info->file_map;
    fmap = &ifm->u.macho;

    TRACE("(%p, %p/%d)\n", module, fmap, fmap->fd);

    module->module.SymType = SymExport;

    if (!(dbghelp_options & SYMOPT_PUBLICS_ONLY))
    {
        find_and_map_dsym(pcs, module);

        if (dwarf2_parse(module, module->reloc_delta, NULL /* FIXME: some thunks to deal with ? */,
                         &module->format_info[DFI_MACHO]->u.macho_info->file_map))
            ret = TRUE;
    }

    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(ifm, LC_SYMTAB, macho_parse_symtab, &mdi);
    if (result > 0)
        ret = TRUE;
    else if (result < 0)
        WARN("Couldn't correctly read stabs\n");

    if (!(dbghelp_options & SYMOPT_PUBLICS_ONLY) && fmap->dsym)
    {
        mdi.fmap = &fmap->dsym->u.macho;
        result = macho_enum_load_commands(fmap->dsym, 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_fetch_file_info
 *
 * Gathers some more information for a Mach-O module from a given file
 */
BOOL macho_fetch_file_info(HANDLE process, const WCHAR* name, unsigned long load_addr, DWORD_PTR* base,
                           DWORD* size, DWORD* checksum)
{
    struct image_file_map fmap;
    struct process *pcs;
    BOOL split_segs;

    TRACE("(%s, %p, %p, %p)\n", debugstr_w(name), base, size, checksum);

    pcs = process_find_by_handle(process);
    if (!pcs) return FALSE;

    split_segs = image_uses_split_segs(process, load_addr);
    if (!macho_map_file(pcs, name, split_segs, &fmap)) return FALSE;
    if (base) *base = fmap.u.macho.segs_start;
    *size = fmap.u.macho.segs_size;
    *checksum = calc_crc32(fmap.u.macho.fd);
    macho_unmap_file(&fmap);
    return TRUE;
}

/******************************************************************
 *              macho_module_remove
 */
static void macho_module_remove(struct process* pcs, struct module_format* modfmt)
{
    macho_unmap_file(&modfmt->u.macho_info->file_map);
    HeapFree(GetProcessHeap(), 0, modfmt);
}


/******************************************************************
 *              get_dyld_image_info_address
 */
static ULONG_PTR get_dyld_image_info_address(struct process* pcs)
{
    NTSTATUS status;
    PROCESS_BASIC_INFORMATION pbi;
    ULONG_PTR dyld_image_info_address = 0;
    BOOL ret;

    /* Get address of PEB */
    status = NtQueryInformationProcess(pcs->handle, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
    if (status == STATUS_SUCCESS)
    {
        /* Read dyld image info address from PEB */
        if (pcs->is_64bit)
            ret = ReadProcessMemory(pcs->handle, &pbi.PebBaseAddress->Reserved[0],
                &dyld_image_info_address, sizeof(dyld_image_info_address), NULL);
        else
        {
            PEB32 *peb32 = (PEB32 *)pbi.PebBaseAddress;
            ULONG addr32;
            ret = ReadProcessMemory(pcs->handle, &peb32->Reserved[0], &addr32,
                sizeof(addr32), NULL);
            dyld_image_info_address = addr32;
        }

        if (ret)
            TRACE("got dyld_image_info_address %#lx from PEB %p\n",
                dyld_image_info_address, pbi.PebBaseAddress);
    }

#ifndef __LP64__ /* No reading the symtab with nlist(3) in LP64 */
    if (!dyld_image_info_address)
    {
        static void* dyld_all_image_infos_addr;

        /* Our next best guess is that dyld was loaded at its base address
           and we can find the dyld image infos address by looking up its symbol. */
        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)
        {
            TRACE("got dyld_image_info_address %p from /usr/lib/dyld symbol table\n",
                  dyld_all_image_infos_addr);
            dyld_image_info_address = (ULONG_PTR)dyld_all_image_infos_addr;
        }
    }
#endif

    return dyld_image_info_address;
}

/******************************************************************
 *              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;
    BOOL                    split_segs;
    struct image_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);

    split_segs = image_uses_split_segs(pcs->handle, load_addr);
    if (!macho_map_file(pcs, filename, split_segs, &fmap)) return FALSE;

    /* Find the dynamic loader's table of images loaded into the process.
     */
    if (macho_info->flags & MACHO_INFO_DEBUG_HEADER)
    {
        macho_info->dbg_hdr_addr = (unsigned long)get_dyld_image_info_address(pcs);
        ret = TRUE;
    }

    if (macho_info->flags & MACHO_INFO_MODULE)
    {
        struct macho_module_info *macho_module_info;
        struct module_format*   modfmt =
            HeapAlloc(GetProcessHeap(), 0, sizeof(struct module_format) + sizeof(struct macho_module_info));
        if (!modfmt) goto leave;
        if (!load_addr)
            load_addr = fmap.u.macho.segs_start;
        macho_info->module = module_new(pcs, filename, DMT_MACHO, FALSE, load_addr,
                                        fmap.u.macho.segs_size, 0, calc_crc32(fmap.u.macho.fd));
        if (!macho_info->module)
        {
            HeapFree(GetProcessHeap(), 0, modfmt);
            goto leave;
        }
        macho_info->module->reloc_delta = macho_info->module->module.BaseOfImage - fmap.u.macho.segs_start;
        macho_module_info = (void*)(modfmt + 1);
        macho_info->module->format_info[DFI_MACHO] = modfmt;

        modfmt->module       = macho_info->module;
        modfmt->remove       = macho_module_remove;
        modfmt->loc_compute  = NULL;
        modfmt->u.macho_info = macho_module_info;

        macho_module_info->load_addr = load_addr;

        macho_module_info->file_map = fmap;
        reset_file_map(&fmap);
        if (dbghelp_options & SYMOPT_DEFERRED_LOADS)
            macho_info->module->module.SymType = SymDeferred;
        else if (!macho_load_debug_info(pcs, macho_info->module))
            ret = FALSE;

        macho_info->module->format_info[DFI_MACHO]->u.macho_info->in_use = 1;
        macho_info->module->format_info[DFI_MACHO]->u.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(struct process* pcs,
                                      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/%p, %s, 0x%08lx, %s, %p)\n", pcs, pcs->handle, 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(pcs, 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(struct process* pcs,
                                          const WCHAR* filename,
                                          unsigned long load_addr,
                                          struct macho_info* macho_info)
{
    BOOL ret = FALSE;
    unsigned int index = 0;
    const char *path;

    TRACE("(%p/%p, %s, 0x%08lx, %p)\n", pcs, pcs->handle, 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(pcs, 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 const 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->format_info[DFI_MACHO]->u.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 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)
    {
        const char* fallback = getenv("DYLD_FALLBACK_LIBRARY_PATH");
        if (!fallback)
            fallback = "/usr/local/lib:/lib:/usr/lib";
        ret = macho_load_file_from_path(pcs, p, load_addr, fallback, 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)
{
    union wine_all_image_infos  image_infos;
    union wine_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->is_64bit)
        len = sizeof(image_infos.infos64);
    else
        len = sizeof(image_infos.infos32);
    if (!pcs->dbg_hdr_addr ||
        !ReadProcessMemory(pcs->handle, (void*)pcs->dbg_hdr_addr,
                           &image_infos, len, NULL))
        goto done;
    if (!pcs->is_64bit)
    {
        struct dyld_all_image_infos32 temp = image_infos.infos32;
        image_infos.infos64.infoArrayCount = temp.infoArrayCount;
        image_infos.infos64.infoArray = temp.infoArray;
    }
    if (!image_infos.infos64.infoArray)
        goto done;
    TRACE("Process has %u image infos at %p\n", image_infos.infos64.infoArrayCount, (void*)image_infos.infos64.infoArray);

    if (pcs->is_64bit)
        len = sizeof(info_array->info64);
    else
        len = sizeof(info_array->info32);
    len *= image_infos.infos64.infoArrayCount;
    info_array = HeapAlloc(GetProcessHeap(), 0, len);
    if (!info_array ||
        !ReadProcessMemory(pcs->handle, (void*)image_infos.infos64.infoArray,
                           info_array, len, NULL))
        goto done;
    TRACE("... read image infos\n");

    for (i = 0; i < image_infos.infos64.infoArrayCount; i++)
    {
        struct dyld_image_info64 info;
        if (pcs->is_64bit)
            info = info_array[i].info64;
        else
        {
            struct dyld_image_info32 *info32 = &info_array->info32 + i;
            info.imageLoadAddress = info32->imageLoadAddress;
            info.imageFilePath = info32->imageFilePath;
        }
        if (info.imageFilePath &&
            ReadProcessMemory(pcs->handle, (void*)info.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, ARRAY_SIZE(bufstrW));
            if (main_name && !bufstrW[0]) strcpyW(bufstrW, main_name);
            if (!cb(bufstrW, info.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->format_info[DFI_MACHO]->u.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->format_info[DFI_MACHO]->u.macho_info->in_use &&
            !module->format_info[DFI_MACHO]->u.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)
{
    WCHAR *loader = get_wine_loader_name(pcs);
    BOOL ret = FALSE;
    ULONG_PTR dyld_image_info_address;
    union wine_all_image_infos image_infos;
    union wine_image_info image_info;
    uint32_t len;
    char path[PATH_MAX];
    BOOL got_path = FALSE;

    if (pcs->is_64bit)
        len = sizeof(image_infos.infos64);
    else
        len = sizeof(image_infos.infos32);
    dyld_image_info_address = get_dyld_image_info_address(pcs);
    if (dyld_image_info_address &&
        ReadProcessMemory(pcs->handle, (void*)dyld_image_info_address, &image_infos, len, NULL))
    {
        if (pcs->is_64bit)
            len = sizeof(image_info.info64);
        else
        {
            struct dyld_all_image_infos32 temp = image_infos.infos32;
            image_infos.infos64.infoArrayCount = temp.infoArrayCount;
            image_infos.infos64.infoArray = temp.infoArray;
            len = sizeof(image_info.info32);
        }
        if (image_infos.infos64.infoArray && image_infos.infos64.infoArrayCount &&
            ReadProcessMemory(pcs->handle, (void*)image_infos.infos64.infoArray, &image_info, len, NULL))
        {
            if (!pcs->is_64bit)
            {
                struct dyld_image_info32 temp = image_info.info32;
                image_info.info64.imageLoadAddress = temp.imageLoadAddress;
                image_info.info64.imageFilePath = temp.imageFilePath;
            }
            for (len = sizeof(path); image_info.info64.imageFilePath && len > 0; len /= 2)
            {
                if (ReadProcessMemory(pcs->handle, (void*)image_info.info64.imageFilePath, path, len, NULL))
                {
                    path[len - 1] = 0;
                    got_path = TRUE;
                    TRACE("got executable path from target's dyld image info: %s\n", debugstr_a(path));
                    break;
                }
            }
        }
    }

    /* If we couldn't get the executable path from the target process, try our
       own.  It will almost always be the same. */
    if (!got_path)
    {
        len = sizeof(path);
        if (!_NSGetExecutablePath(path, &len))
        {
            got_path = TRUE;
            TRACE("using own executable path: %s\n", debugstr_a(path));
        }
    }

    if (got_path)
    {
        WCHAR* pathW;

        len = MultiByteToWideChar(CP_UNIXCP, 0, path, -1, NULL, 0);
        pathW = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
        if (pathW)
        {
            MultiByteToWideChar(CP_UNIXCP, 0, path, -1, pathW, len);
            ret = macho_load_file(pcs, pathW, 0, macho_info);
            HeapFree(GetProcessHeap(), 0, pathW);
        }
    }

    if (!ret)
        ret = macho_search_and_load_file(pcs, loader, 0, macho_info);
    heap_free(loader);
    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->format_info[DFI_MACHO]->u.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  /* HAVE_MACH_O_LOADER_H */

BOOL macho_find_section(struct image_file_map* ifm, const char* segname, const char* sectname, struct image_section_map* ism)
{
    return FALSE;
}

const char* macho_map_section(struct image_section_map* ism)
{
    return NULL;
}

void macho_unmap_section(struct image_section_map* ism)
{
}

DWORD_PTR macho_get_map_rva(const struct image_section_map* ism)
{
    return 0;
}

unsigned macho_get_map_size(const struct image_section_map* ism)
{
    return 0;
}

BOOL    macho_synchronize_module_list(struct process* pcs)
{
    return FALSE;
}

BOOL macho_fetch_file_info(HANDLE process, const WCHAR* name, unsigned long load_addr, DWORD_PTR* 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 process *pcs, struct module* module)
{
    return FALSE;
}
#endif  /* HAVE_MACH_O_LOADER_H */
