| /* |
| * 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 |
| #undef DPRINTF |
| #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 "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> |
| |
| #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 |
| |
| #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); |
| |
| |
| #ifdef _WIN64 |
| typedef struct segment_command_64 macho_segment_command; |
| typedef struct nlist_64 macho_nlist; |
| |
| #define TARGET_CPU_TYPE CPU_TYPE_X86_64 |
| #define TARGET_MH_MAGIC MH_MAGIC_64 |
| #define TARGET_SEGMENT_COMMAND LC_SEGMENT_64 |
| #else |
| typedef struct segment_command macho_segment_command; |
| typedef struct nlist macho_nlist; |
| |
| #define TARGET_CPU_TYPE CPU_TYPE_X86 |
| #define TARGET_MH_MAGIC MH_MAGIC |
| #define TARGET_SEGMENT_COMMAND LC_SEGMENT |
| #endif |
| |
| |
| /* 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, sizeof(fmap->mach_header), 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, |
| 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)) == 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(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_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 macho_file_map* fmap, const struct load_command* lc, void* user) |
| { |
| const macho_segment_command* sc = (const macho_segment_command*)lc; |
| |
| TRACE("(%p/%d, %p, %p) segment %s\n", fmap, fmap->fd, lc, user, debugstr_an(sc->segname, sizeof(sc->segname))); |
| |
| fmap->num_sections += sc->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 macho_file_map* fmap, const struct load_command* lc, void* user) |
| { |
| const macho_segment_command* sc = (const macho_segment_command*)lc; |
| struct section_info* info = user; |
| BOOL ignore; |
| const macho_section* section; |
| int i; |
| unsigned long tmp, page_mask = sysconf( _SC_PAGESIZE ) - 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)sc->vmaddr, |
| (unsigned long)(sc->vmaddr + sc->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(sc->segname, SEG_TEXT)); |
| |
| if (!strncmp(sc->segname, "WINE_", 5)) |
| TRACE("Ignoring special Wine segment %s\n", debugstr_an(sc->segname, sizeof(sc->segname))); |
| else if (!strncmp(sc->segname, "__PAGEZERO", 10)) |
| TRACE("Ignoring __PAGEZERO segment\n"); |
| else if (ignore) |
| TRACE("Ignoring %s segment because image has split segments\n", sc->segname); |
| else |
| { |
| /* 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%08lx - 0x%08lx\n", (unsigned long)fmap->segs_start, (unsigned long)fmap->segs_size); |
| } |
| |
| section = (const macho_section*)(sc + 1); |
| for (i = 0; i < sc->nsects; i++) |
| { |
| fmap->sect[info->section_index].section = §ion[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 macho_file_map* fmap, const struct load_command* lc, void* user) |
| { |
| fmap->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(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; |
| |
| TRACE("(%s, %p)\n", debugstr_w(filenameW), fmap); |
| |
| reset_file_map(ifm); |
| |
| ifm->modtype = DMT_MACHO; |
| #ifdef _WIN64 |
| ifm->addr_size = 64; |
| #else |
| ifm->addr_size = 32; |
| #endif |
| |
| 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_TYPE) |
| { |
| 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_TYPE); |
| } |
| 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_MH_MAGIC || |
| fmap->mach_header.cputype != TARGET_CPU_TYPE) 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(fmap, TARGET_SEGMENT_COMMAND, 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(fmap, TARGET_SEGMENT_COMMAND, 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(fmap, 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 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 macho_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(macho_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(macho_nlist), |
| stabstr, sc->strsize, macho_stabs_def_cb, mdi)) |
| ret = -1; |
| |
| macho_unmap_ranges(fmap, sc->symoff, sc->nsyms * sizeof(macho_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 = 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->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(const WCHAR* path, struct macho_file_map* fmap) |
| { |
| struct image_file_map dsym_ifm; |
| |
| if (macho_map_file(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 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(path, fmap)) |
| goto found; |
| |
| strcpyW(path + strlenW(module->module.LoadedImageName), dot_dwarf); |
| |
| if (try_dsym(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(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) |
| { |
| macho_mach_header header; |
| if (ReadProcessMemory(process, (void*)load_addr, &header, sizeof(header), NULL) && |
| header.magic == TARGET_MH_MAGIC && header.cputype == TARGET_CPU_TYPE && |
| 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 module* module) |
| { |
| BOOL ret = FALSE; |
| struct macho_debug_info mdi; |
| int result; |
| 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; |
| } |
| |
| fmap = &module->format_info[DFI_MACHO]->u.macho_info->file_map.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(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(fmap, 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(mdi.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_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; |
| BOOL split_segs; |
| |
| TRACE("(%s, %p, %p, %p)\n", debugstr_w(name), base, size, checksum); |
| |
| split_segs = image_uses_split_segs(process, load_addr); |
| if (!macho_map_file(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; |
| |
| /* 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 (ReadProcessMemory(pcs->handle, &pbi.PebBaseAddress->Reserved[0], |
| &dyld_image_info_address, sizeof(dyld_image_info_address), NULL)) |
| { |
| TRACE("got dyld_image_info_address 0x%08lx from PEB %p MacDyldImageInfo %p\n", |
| (unsigned long)dyld_image_info_address, pbi.PebBaseAddress, &pbi.PebBaseAddress->Reserved); |
| } |
| } |
| |
| #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(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(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) |
| { |
| 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) |
| { |
| BOOL ret = FALSE; |
| ULONG_PTR dyld_image_info_address; |
| struct dyld_all_image_infos image_infos; |
| struct dyld_image_info image_info; |
| uint32_t len; |
| char path[PATH_MAX]; |
| BOOL got_path = FALSE; |
| |
| 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, sizeof(image_infos), NULL) && |
| image_infos.infoArray && image_infos.infoArrayCount && |
| ReadProcessMemory(pcs->handle, image_infos.infoArray, &image_info, sizeof(image_info), NULL) && |
| image_info.imageFilePath) |
| { |
| for (len = sizeof(path); len > 0; len /= 2) |
| { |
| if (ReadProcessMemory(pcs->handle, image_info.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, get_wine_loader_name(), 0, macho_info); |
| return ret; |
| } |
| |
| /****************************************************************** |
| * macho_read_wine_loader_dbg_info |
| * |
| * Try to find a decent wine executable which could have loaded the debuggee |
| */ |
| BOOL macho_read_wine_loader_dbg_info(struct process* pcs) |
| { |
| struct macho_info macho_info; |
| |
| TRACE("(%p/%p)\n", pcs, pcs->handle); |
| macho_info.flags = MACHO_INFO_DEBUG_HEADER | MACHO_INFO_MODULE; |
| if (!macho_search_loader(pcs, &macho_info)) return FALSE; |
| macho_info.module->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 module* module) |
| { |
| return FALSE; |
| } |
| #endif /* HAVE_MACH_O_LOADER_H */ |