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

#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_", 5))
    {
        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->format_info[DFI_MACHO]->u.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->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->addr, 0);
        }
    }
}

/******************************************************************
 *              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->format_info[DFI_MACHO]->u.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_PTR* 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;
        struct module_format*   modfmt =
            HeapAlloc(GetProcessHeap(), 0, sizeof(struct module_format) + sizeof(struct macho_module_info));
        if (!modfmt) 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, modfmt);
            goto leave;
        }
        macho_module_info = (void*)(modfmt + 1);
        macho_info->module->format_info[DFI_MACHO] = modfmt;

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

        macho_module_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->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(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->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 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.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->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)
{
    return macho_search_and_load_file(pcs, get_wine_loader_name(), 0, macho_info);
}

/******************************************************************
 *              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_synchronize_module_list(struct process* pcs)
{
    return FALSE;
}

BOOL macho_fetch_file_info(const WCHAR* name, 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 module* module, struct macho_file_map* fmap)
{
    return FALSE;
}
#endif  /* HAVE_MACH_O_LOADER_H */