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