diff --git a/src/libbacktrace/configure b/src/libbacktrace/configure index 47df55c22d67f..ed47ba3c2fa08 100755 --- a/src/libbacktrace/configure +++ b/src/libbacktrace/configure @@ -11844,6 +11844,9 @@ elf*) FORMAT_FILE="elf.lo" ;; pecoff) FORMAT_FILE="pecoff.lo" backtrace_supports_data=no ;; +macho*) FORMAT_FILE="macho.lo" + backtrace_supports_data=no + ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: could not determine output file type" >&5 $as_echo "$as_me: WARNING: could not determine output file type" >&2;} FORMAT_FILE="unknown.lo" diff --git a/src/libbacktrace/configure.ac b/src/libbacktrace/configure.ac index 71e85187ef55a..7ae21b8d1a68c 100644 --- a/src/libbacktrace/configure.ac +++ b/src/libbacktrace/configure.ac @@ -231,6 +231,9 @@ elf*) FORMAT_FILE="elf.lo" ;; pecoff) FORMAT_FILE="pecoff.lo" backtrace_supports_data=no ;; +macho*) FORMAT_FILE="macho.lo" + backtrace_supports_data=no + ;; *) AC_MSG_WARN([could not determine output file type]) FORMAT_FILE="unknown.lo" backtrace_supported=no diff --git a/src/libbacktrace/filetype.awk b/src/libbacktrace/filetype.awk index 57bab797a9a8a..a5f6c8cc1800f 100644 --- a/src/libbacktrace/filetype.awk +++ b/src/libbacktrace/filetype.awk @@ -3,3 +3,9 @@ /\177ELF\002/ { if (NR == 1) { print "elf64"; exit } } /\114\001/ { if (NR == 1) { print "pecoff"; exit } } /\144\206/ { if (NR == 1) { print "pecoff"; exit } } +/\xFE\xED\xFA\xCE/ { if (NR == 1) { print "macho32"; exit } } +/\xCE\xFA\xED\xFE/ { if (NR == 1) { print "macho32"; exit } } +/\xFE\xED\xFA\xCF/ { if (NR == 1) { print "macho64"; exit } } +/\xCF\xFA\xED\xFE/ { if (NR == 1) { print "macho64"; exit } } +/\xCA\xFE\xBA\xBE/ { if (NR == 1) { print "macho-fat"; exit } } +/\xBE\xBA\xFE\xCA/ { if (NR == 1) { print "macho-fat"; exit } } diff --git a/src/libbacktrace/macho.c b/src/libbacktrace/macho.c new file mode 100644 index 0000000000000..9af14e724b40d --- /dev/null +++ b/src/libbacktrace/macho.c @@ -0,0 +1,1416 @@ +/* macho.c -- Get debug data from an Mach-O file for backtraces. + Copyright (C) 2012-2016 Free Software Foundation, Inc. + Written by John Colanduoni. + + Pending upstream pull request: + https://github.com/ianlancetaylor/libbacktrace/pull/2 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + (1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + (2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + (3) The name of the author may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. */ + +#include "config.h" + +/* We can't use autotools to detect the pointer width of our program because + we may be building a fat Mach-O file containing both 32-bit and 64-bit + variants. However Mach-O runs a limited set of platforms so detection + via preprocessor is not difficult. */ + +#if defined(__MACH__) +#if defined(__LP64__) +#define BACKTRACE_BITS 64 +#else +#define BACKTRACE_BITS 32 +#endif +#else +#error Attempting to build Mach-O support on incorrect platform +#endif + +#if defined(__x86_64__) +#define NATIVE_CPU_TYPE CPU_TYPE_X86_64 +#elif defined(__i386__) +#define NATIVE_CPU_TYPE CPU_TYPE_X86 +#elif defined(__aarch64__) +#define NATIVE_CPU_TYPE CPU_TYPE_ARM64 +#elif defined(__arm__) +#define NATIVE_CPU_TYPE CPU_TYPE_ARM +#else +#error Could not detect native Mach-O cpu_type_t +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "backtrace.h" +#include "internal.h" + +struct macho_commands_view +{ + struct backtrace_view view; + uint32_t commands_count; + uint32_t commands_total_size; + int bytes_swapped; + size_t base_offset; +}; + +enum debug_section +{ + DEBUG_INFO, + DEBUG_LINE, + DEBUG_ABBREV, + DEBUG_RANGES, + DEBUG_STR, + DEBUG_MAX +}; + +static const char *const debug_section_names[DEBUG_MAX] = + { + "__debug_info", + "__debug_line", + "__debug_abbrev", + "__debug_ranges", + "__debug_str" + }; + +struct found_dwarf_section +{ + uint32_t file_offset; + uintptr_t file_size; + const unsigned char *data; +}; + +/* Mach-O symbols don't have a length. As a result we have to infer it + by sorting the symbol addresses for each image and recording the + memory range attributed to each image. */ +struct macho_symbol +{ + uintptr_t addr; + size_t size; + const char *name; +}; + +struct macho_syminfo_data +{ + struct macho_syminfo_data *next; + struct macho_symbol *symbols; + size_t symbol_count; + uintptr_t min_addr; + uintptr_t max_addr; +}; + +uint16_t +macho_file_to_host_u16 (int file_bytes_swapped, uint16_t input) +{ + if (file_bytes_swapped) + return (input >> 8) | (input << 8); + else + return input; +} + +uint32_t +macho_file_to_host_u32 (int file_bytes_swapped, uint32_t input) +{ + if (file_bytes_swapped) + { + return ((input >> 24) & 0x000000FF) + | ((input >> 8) & 0x0000FF00) + | ((input << 8) & 0x00FF0000) + | ((input << 24) & 0xFF000000); + } + else + { + return input; + } +} + +uint64_t +macho_file_to_host_u64 (int file_bytes_swapped, uint64_t input) +{ + if (file_bytes_swapped) + { + return macho_file_to_host_u32 (file_bytes_swapped, + (uint32_t) (input >> 32)) + | (((uint64_t) macho_file_to_host_u32 (file_bytes_swapped, + (uint32_t) input)) << 32); + } + else + { + return input; + } +} + +#if BACKTRACE_BITS == 64 +#define macho_file_to_host_usize macho_file_to_host_u64 +typedef struct mach_header_64 mach_header_native_t; +#define LC_SEGMENT_NATIVE LC_SEGMENT_64 +typedef struct segment_command_64 segment_command_native_t; +typedef struct nlist_64 nlist_native_t; +typedef struct section_64 section_native_t; +#else /* BACKTRACE_BITS == 32 */ +#define macho_file_to_host_usize macho_file_to_host_u32 +typedef struct mach_header mach_header_native_t; +#define LC_SEGMENT_NATIVE LC_SEGMENT +typedef struct segment_command segment_command_native_t; +typedef struct nlist nlist_native_t; +typedef struct section section_native_t; +#endif + +// Gets a view into a Mach-O image, taking any slice offset into account +int +macho_get_view (struct backtrace_state *state, int descriptor, + off_t offset, size_t size, + backtrace_error_callback error_callback, + void *data, struct macho_commands_view *commands_view, + struct backtrace_view *view) +{ + return backtrace_get_view (state, descriptor, + commands_view->base_offset + offset, size, + error_callback, data, view); +} + +int +macho_get_commands (struct backtrace_state *state, int descriptor, + backtrace_error_callback error_callback, + void *data, struct macho_commands_view *commands_view, + int *incompatible) +{ + int ret = 0; + int is_fat = 0; + struct backtrace_view file_header_view; + int file_header_view_valid = 0; + struct backtrace_view fat_archs_view; + int fat_archs_view_valid = 0; + const mach_header_native_t *file_header; + uint64_t commands_offset; + + *incompatible = 0; + + if (!backtrace_get_view (state, descriptor, 0, sizeof (mach_header_native_t), + error_callback, data, &file_header_view)) + goto end; + file_header_view_valid = 1; + + switch (*(uint32_t *) file_header_view.data) + { + case MH_MAGIC: + if (BACKTRACE_BITS == 32) + commands_view->bytes_swapped = 0; + else + { + *incompatible = 1; + goto end; + } + break; + case MH_CIGAM: + if (BACKTRACE_BITS == 32) + commands_view->bytes_swapped = 1; + else + { + *incompatible = 1; + goto end; + } + break; + case MH_MAGIC_64: + if (BACKTRACE_BITS == 64) + commands_view->bytes_swapped = 0; + else + { + *incompatible = 1; + goto end; + } + break; + case MH_CIGAM_64: + if (BACKTRACE_BITS == 64) + commands_view->bytes_swapped = 1; + else + { + *incompatible = 1; + goto end; + } + break; + case FAT_MAGIC: + is_fat = 1; + commands_view->bytes_swapped = 0; + break; + case FAT_CIGAM: + is_fat = 1; + commands_view->bytes_swapped = 1; + break; + default: + goto end; + } + + if (is_fat) + { + uint32_t native_slice_offset; + size_t archs_total_size; + uint32_t arch_count; + const struct fat_header *fat_header; + const struct fat_arch *archs; + uint32_t i; + + fat_header = file_header_view.data; + arch_count = + macho_file_to_host_u32 (commands_view->bytes_swapped, + fat_header->nfat_arch); + + archs_total_size = arch_count * sizeof (struct fat_arch); + + if (!backtrace_get_view (state, descriptor, sizeof (struct fat_header), + archs_total_size, error_callback, + data, &fat_archs_view)) + goto end; + fat_archs_view_valid = 1; + + native_slice_offset = 0; + archs = fat_archs_view.data; + for (i = 0; i < arch_count; i++) + { + const struct fat_arch *raw_arch = archs + i; + int cpu_type = + (int) macho_file_to_host_u32 (commands_view->bytes_swapped, + (uint32_t) raw_arch->cputype); + + if (cpu_type == NATIVE_CPU_TYPE) + { + native_slice_offset = + macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_arch->offset); + + break; + } + } + + if (native_slice_offset == 0) + { + *incompatible = 1; + goto end; + } + + backtrace_release_view (state, &file_header_view, error_callback, data); + file_header_view_valid = 0; + if (!backtrace_get_view (state, descriptor, native_slice_offset, + sizeof (mach_header_native_t), error_callback, + data, &file_header_view)) + goto end; + file_header_view_valid = 1; + + // The endianess of the slice may be different than the fat image + switch (*(uint32_t *) file_header_view.data) + { + case MH_MAGIC: + if (BACKTRACE_BITS == 32) + commands_view->bytes_swapped = 0; + else + goto end; + break; + case MH_CIGAM: + if (BACKTRACE_BITS == 32) + commands_view->bytes_swapped = 1; + else + goto end; + break; + case MH_MAGIC_64: + if (BACKTRACE_BITS == 64) + commands_view->bytes_swapped = 0; + else + goto end; + break; + case MH_CIGAM_64: + if (BACKTRACE_BITS == 64) + commands_view->bytes_swapped = 1; + else + goto end; + break; + default: + goto end; + } + + commands_view->base_offset = native_slice_offset; + } + else + commands_view->base_offset = 0; + + file_header = file_header_view.data; + commands_view->commands_count = + macho_file_to_host_u32 (commands_view->bytes_swapped, + file_header->ncmds); + commands_view->commands_total_size = + macho_file_to_host_u32 (commands_view->bytes_swapped, + file_header->sizeofcmds); + commands_offset = + commands_view->base_offset + sizeof (mach_header_native_t); + + if (!backtrace_get_view (state, descriptor, commands_offset, + commands_view->commands_total_size, error_callback, + data, &commands_view->view)) + goto end; + + ret = 1; + +end: + if (file_header_view_valid) + backtrace_release_view (state, &file_header_view, error_callback, data); + if (fat_archs_view_valid) + backtrace_release_view (state, &fat_archs_view, error_callback, data); + return ret; +} + +int +macho_get_uuid (struct backtrace_state *state ATTRIBUTE_UNUSED, + int descriptor ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, + void *data, struct macho_commands_view *commands_view, + uuid_t *uuid) +{ + size_t offset = 0; + uint32_t i = 0; + + for (i = 0; i < commands_view->commands_count; i++) + { + const struct load_command *raw_command; + struct load_command command; + + if (offset + sizeof (struct load_command) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + raw_command = + commands_view->view.data + offset; + command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmd); + command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmdsize); + + if (command.cmd == LC_UUID) + { + const struct uuid_command *uuid_command; + + if (offset + sizeof (struct uuid_command) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + uuid_command = + (struct uuid_command *) raw_command; + memcpy (uuid, uuid_command->uuid, sizeof (uuid_t)); + return 1; + } + + offset += command.cmdsize; + } + + error_callback (data, "executable file is missing an identifying UUID", 0); + return 0; +} + +/* Returns the base address of a Mach-O image, as encoded in the file header. + * WARNING: This does not take ASLR into account, which is ubiquitous on recent + * Darwin platforms. + */ +int +macho_get_addr_range (struct backtrace_state *state ATTRIBUTE_UNUSED, + int descriptor ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, + void *data, struct macho_commands_view *commands_view, + uintptr_t *base_address, uintptr_t *max_address) +{ + size_t offset = 0; + int found_text = 0; + uint32_t i = 0; + + *max_address = 0; + + for (i = 0; i < commands_view->commands_count; i++) + { + const struct load_command *raw_command; + struct load_command command; + + if (offset + sizeof (struct load_command) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + raw_command = commands_view->view.data + offset; + command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmd); + command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmdsize); + + if (command.cmd == LC_SEGMENT_NATIVE) + { + const segment_command_native_t *raw_segment; + uintptr_t segment_vmaddr; + uintptr_t segment_vmsize; + uintptr_t segment_maxaddr; + uintptr_t text_fileoff; + + if (offset + sizeof (segment_command_native_t) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + raw_segment = (segment_command_native_t *) raw_command; + + segment_vmaddr = macho_file_to_host_usize ( + commands_view->bytes_swapped, raw_segment->vmaddr); + segment_vmsize = macho_file_to_host_usize ( + commands_view->bytes_swapped, raw_segment->vmsize); + segment_maxaddr = segment_vmaddr + segment_vmsize; + + if (strncmp (raw_segment->segname, "__TEXT", + sizeof (raw_segment->segname)) == 0) + { + text_fileoff = macho_file_to_host_usize ( + commands_view->bytes_swapped, raw_segment->fileoff); + *base_address = segment_vmaddr - text_fileoff; + + found_text = 1; + } + + if (segment_maxaddr > *max_address) + *max_address = segment_maxaddr; + } + + offset += command.cmdsize; + } + + if (found_text) + return 1; + else + { + error_callback (data, "executable is missing __TEXT segment", 0); + return 0; + } +} + +static int +macho_symbol_compare_addr (const void *left_raw, const void *right_raw) +{ + const struct macho_symbol *left = left_raw; + const struct macho_symbol *right = right_raw; + + if (left->addr > right->addr) + return 1; + else if (left->addr < right->addr) + return -1; + else + return 0; +} + +int +macho_symbol_type_relevant (uint8_t type) +{ + uint8_t type_field = (uint8_t) (type & N_TYPE); + + return !(type & N_EXT) && + (type_field == N_ABS || type_field == N_SECT); +} + +int +macho_add_symtab (struct backtrace_state *state, + backtrace_error_callback error_callback, + void *data, int descriptor, + struct macho_commands_view *commands_view, + uintptr_t base_address, uintptr_t max_image_address, + intptr_t vmslide, int *found_sym) +{ + struct macho_syminfo_data *syminfo_data; + + int ret = 0; + size_t offset = 0; + struct backtrace_view symtab_view; + int symtab_view_valid = 0; + struct backtrace_view strtab_view; + int strtab_view_valid = 0; + size_t syminfo_index = 0; + size_t function_count = 0; + uint32_t i = 0; + uint32_t j = 0; + uint32_t symtab_index = 0; + + *found_sym = 0; + + for (i = 0; i < commands_view->commands_count; i++) + { + const struct load_command *raw_command; + struct load_command command; + + if (offset + sizeof (struct load_command) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + raw_command = commands_view->view.data + offset; + command.cmd = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmd); + command.cmdsize = macho_file_to_host_u32 (commands_view->bytes_swapped, + raw_command->cmdsize); + + if (command.cmd == LC_SYMTAB) + { + const struct symtab_command *symtab_command; + uint32_t symbol_table_offset; + uint32_t symbol_count; + uint32_t string_table_offset; + uint32_t string_table_size; + + if (offset + sizeof (struct symtab_command) + > commands_view->commands_total_size) + { + error_callback (data, + "executable file contains out of range command offset", + 0); + return 0; + } + + symtab_command = (struct symtab_command *) raw_command; + + symbol_table_offset = macho_file_to_host_u32 ( + commands_view->bytes_swapped, symtab_command->symoff); + symbol_count = macho_file_to_host_u32 ( + commands_view->bytes_swapped, symtab_command->nsyms); + string_table_offset = macho_file_to_host_u32 ( + commands_view->bytes_swapped, symtab_command->stroff); + string_table_size = macho_file_to_host_u32 ( + commands_view->bytes_swapped, symtab_command->strsize); + + + if (!macho_get_view (state, descriptor, symbol_table_offset, + symbol_count * sizeof (nlist_native_t), + error_callback, data, commands_view, + &symtab_view)) + goto end; + symtab_view_valid = 1; + + if (!macho_get_view (state, descriptor, string_table_offset, + string_table_size, error_callback, data, + commands_view, &strtab_view)) + goto end; + strtab_view_valid = 1; + + // Count functions first + for (j = 0; j < symbol_count; j++) + { + const nlist_native_t *raw_sym = + ((const nlist_native_t *) symtab_view.data) + j; + + if (macho_symbol_type_relevant (raw_sym->n_type)) + { + function_count += 1; + } + } + + // Allocate space for the: + // (a) macho_syminfo_data for this image + // (b) macho_symbol entries + syminfo_data = + backtrace_alloc (state, + sizeof (struct macho_syminfo_data), + error_callback, data); + if (syminfo_data == NULL) + goto end; + + syminfo_data->symbols = backtrace_alloc ( + state, function_count * sizeof (struct macho_symbol), + error_callback, data); + if (syminfo_data->symbols == NULL) + goto end; + + syminfo_data->symbol_count = function_count; + syminfo_data->next = NULL; + syminfo_data->min_addr = base_address; + syminfo_data->max_addr = max_image_address; + + for (symtab_index = 0; + symtab_index < symbol_count; symtab_index++) + { + const nlist_native_t *raw_sym = + ((const nlist_native_t *) symtab_view.data) + + symtab_index; + + if (macho_symbol_type_relevant (raw_sym->n_type)) + { + size_t strtab_index; + const char *name; + size_t max_len_plus_one; + + syminfo_data->symbols[syminfo_index].addr = + macho_file_to_host_usize (commands_view->bytes_swapped, + raw_sym->n_value) + vmslide; + + strtab_index = macho_file_to_host_u32 ( + commands_view->bytes_swapped, + raw_sym->n_un.n_strx); + + // Check the range of the supposed "string" we've been + // given + if (strtab_index >= string_table_size) + { + error_callback ( + data, + "dSYM file contains out of range string table index", + 0); + goto end; + } + + name = strtab_view.data + strtab_index; + max_len_plus_one = string_table_size - strtab_index; + + if (strnlen (name, max_len_plus_one) >= max_len_plus_one) + { + error_callback ( + data, + "dSYM file contains unterminated string", + 0); + goto end; + } + + // Remove underscore prefixes + if (name[0] == '_') + { + name = name + 1; + } + + syminfo_data->symbols[syminfo_index].name = name; + + syminfo_index += 1; + } + } + + backtrace_qsort (syminfo_data->symbols, + syminfo_data->symbol_count, + sizeof (struct macho_symbol), + macho_symbol_compare_addr); + + // Calculate symbol sizes + for (syminfo_index = 0; + syminfo_index < syminfo_data->symbol_count; syminfo_index++) + { + if (syminfo_index + 1 < syminfo_data->symbol_count) + { + syminfo_data->symbols[syminfo_index].size = + syminfo_data->symbols[syminfo_index + 1].addr - + syminfo_data->symbols[syminfo_index].addr; + } + else + { + syminfo_data->symbols[syminfo_index].size = + max_image_address - + syminfo_data->symbols[syminfo_index].addr; + } + } + + if (!state->threaded) + { + struct macho_syminfo_data **pp; + + for (pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + *pp != NULL; + pp = &(*pp)->next); + *pp = syminfo_data; + } + else + { + while (1) + { + struct macho_syminfo_data **pp; + + pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + + while (1) + { + struct macho_syminfo_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, syminfo_data)) + break; + } + } + + strtab_view_valid = 0; // We need to keep string table around + *found_sym = 1; + ret = 1; + goto end; + } + + offset += command.cmdsize; + } + + // No symbol table here + ret = 1; + goto end; + +end: + if (symtab_view_valid) + backtrace_release_view (state, &symtab_view, error_callback, data); + if (strtab_view_valid) + backtrace_release_view (state, &strtab_view, error_callback, data); + return ret; +} + +int +macho_try_dwarf (struct backtrace_state *state, + backtrace_error_callback error_callback, + void *data, fileline *fileline_fn, uuid_t *executable_uuid, + uintptr_t base_address, uintptr_t max_image_address, + intptr_t vmslide, char *dwarf_filename, int *matched, + int *found_sym, int *found_dwarf) +{ + uuid_t dwarf_uuid; + + int ret = 0; + int dwarf_descriptor; + int dwarf_descriptor_valid = 0; + struct macho_commands_view commands_view; + int commands_view_valid = 0; + struct backtrace_view dwarf_view; + int dwarf_view_valid = 0; + size_t offset = 0; + struct found_dwarf_section dwarf_sections[DEBUG_MAX]; + uintptr_t min_dwarf_offset = 0; + uintptr_t max_dwarf_offset = 0; + uint32_t i = 0; + uint32_t j = 0; + int k = 0; + + *matched = 0; + *found_sym = 0; + *found_dwarf = 0; + + if ((dwarf_descriptor = backtrace_open (dwarf_filename, error_callback, + data, NULL)) == 0) + goto end; + dwarf_descriptor_valid = 1; + + int incompatible; + if (!macho_get_commands (state, dwarf_descriptor, error_callback, data, + &commands_view, &incompatible)) + { + // Failing to read the header here is fine, because this dSYM may be + // for a different architecture + if (incompatible) + { + ret = 1; + } + goto end; + } + commands_view_valid = 1; + + // Get dSYM UUID and compare + if (!macho_get_uuid (state, dwarf_descriptor, error_callback, data, + &commands_view, &dwarf_uuid)) + { + error_callback (data, "dSYM file is missing an identifying uuid", 0); + goto end; + } + if (memcmp (executable_uuid, &dwarf_uuid, sizeof (uuid_t)) != 0) + { + // DWARF doesn't belong to desired executable + ret = 1; + goto end; + } + + *matched = 1; + + // Read symbol table + if (!macho_add_symtab (state, error_callback, data, dwarf_descriptor, + &commands_view, base_address, max_image_address, + vmslide, found_sym)) + goto end; + + // Get DWARF sections + + memset (dwarf_sections, 0, sizeof (dwarf_sections)); + offset = 0; + for (i = 0; i < commands_view.commands_count; i++) + { + const struct load_command *raw_command; + struct load_command command; + + if (offset + sizeof (struct load_command) + > commands_view.commands_total_size) + { + error_callback (data, + "dSYM file contains out of range command offset", 0); + goto end; + } + + raw_command = commands_view.view.data + offset; + command.cmd = macho_file_to_host_u32 (commands_view.bytes_swapped, + raw_command->cmd); + command.cmdsize = macho_file_to_host_u32 (commands_view.bytes_swapped, + raw_command->cmdsize); + + if (command.cmd == LC_SEGMENT_NATIVE) + { + uint32_t section_count; + size_t section_offset; + const segment_command_native_t *raw_segment; + + if (offset + sizeof (segment_command_native_t) + > commands_view.commands_total_size) + { + error_callback (data, + "dSYM file contains out of range command offset", + 0); + goto end; + } + + raw_segment = (const segment_command_native_t *) raw_command; + + if (strncmp (raw_segment->segname, "__DWARF", + sizeof (raw_segment->segname)) == 0) + { + section_count = macho_file_to_host_u32 ( + commands_view.bytes_swapped, + raw_segment->nsects); + + section_offset = offset + sizeof (segment_command_native_t); + + // Search sections for relevant DWARF section names + for (j = 0; j < section_count; j++) + { + const section_native_t *raw_section; + + if (section_offset + sizeof (section_native_t) > + commands_view.commands_total_size) + { + error_callback (data, + "dSYM file contains out of range command offset", + 0); + goto end; + } + + raw_section = commands_view.view.data + section_offset; + + for (k = 0; k < DEBUG_MAX; k++) + { + uintptr_t dwarf_section_end; + + if (strncmp (raw_section->sectname, + debug_section_names[k], + sizeof (raw_section->sectname)) == 0) + { + *found_dwarf = 1; + + dwarf_sections[k].file_offset = + macho_file_to_host_u32 ( + commands_view.bytes_swapped, + raw_section->offset); + dwarf_sections[k].file_size = + macho_file_to_host_usize ( + commands_view.bytes_swapped, + raw_section->size); + + if (min_dwarf_offset == 0 || + dwarf_sections[k].file_offset < + min_dwarf_offset) + min_dwarf_offset = dwarf_sections[k].file_offset; + + dwarf_section_end = + dwarf_sections[k].file_offset + + dwarf_sections[k].file_size; + if (dwarf_section_end > max_dwarf_offset) + max_dwarf_offset = dwarf_section_end; + + break; + } + } + + section_offset += sizeof (section_native_t); + } + + break; + } + } + + offset += command.cmdsize; + } + + if (!*found_dwarf) + { + // No DWARF in this file + ret = 1; + goto end; + } + + if (!macho_get_view (state, dwarf_descriptor, (off_t) min_dwarf_offset, + max_dwarf_offset - min_dwarf_offset, error_callback, + data, &commands_view, &dwarf_view)) + goto end; + dwarf_view_valid = 1; + + for (i = 0; i < DEBUG_MAX; i++) + { + if (dwarf_sections[i].file_offset == 0) + dwarf_sections[i].data = NULL; + else + dwarf_sections[i].data = + dwarf_view.data + dwarf_sections[i].file_offset - min_dwarf_offset; + } + + if (!backtrace_dwarf_add (state, vmslide, + dwarf_sections[DEBUG_INFO].data, + dwarf_sections[DEBUG_INFO].file_size, + dwarf_sections[DEBUG_LINE].data, + dwarf_sections[DEBUG_LINE].file_size, + dwarf_sections[DEBUG_ABBREV].data, + dwarf_sections[DEBUG_ABBREV].file_size, + dwarf_sections[DEBUG_RANGES].data, + dwarf_sections[DEBUG_RANGES].file_size, + dwarf_sections[DEBUG_STR].data, + dwarf_sections[DEBUG_STR].file_size, + ((__DARWIN_BYTE_ORDER == __DARWIN_BIG_ENDIAN) + ^ commands_view.bytes_swapped), + error_callback, data, fileline_fn)) + goto end; + + // Don't release the DWARF view because it is still in use + dwarf_descriptor_valid = 0; + dwarf_view_valid = 0; + ret = 1; + +end: + if (dwarf_descriptor_valid) + backtrace_close (dwarf_descriptor, error_callback, data); + if (commands_view_valid) + backtrace_release_view (state, &commands_view.view, error_callback, data); + if (dwarf_view_valid) + backtrace_release_view (state, &dwarf_view, error_callback, data); + return ret; +} + +int +macho_try_dsym (struct backtrace_state *state, + backtrace_error_callback error_callback, + void *data, fileline *fileline_fn, uuid_t *executable_uuid, + uintptr_t base_address, uintptr_t max_image_address, + intptr_t vmslide, char *dsym_filename, int *matched, + int *found_sym, int *found_dwarf) +{ + int ret = 0; + char dwarf_image_dir_path[PATH_MAX]; + DIR *dwarf_image_dir; + int dwarf_image_dir_valid = 0; + struct dirent *directory_entry; + char dwarf_filename[PATH_MAX]; + int dwarf_matched; + int dwarf_had_sym; + int dwarf_had_dwarf; + + *matched = 0; + *found_sym = 0; + *found_dwarf = 0; + + strncpy (dwarf_image_dir_path, dsym_filename, PATH_MAX); + strncat (dwarf_image_dir_path, "/Contents/Resources/DWARF", PATH_MAX); + + if (!(dwarf_image_dir = opendir (dwarf_image_dir_path))) + { + error_callback (data, "could not open DWARF directory in dSYM", + 0); + goto end; + } + dwarf_image_dir_valid = 1; + + while ((directory_entry = readdir (dwarf_image_dir))) + { + if (directory_entry->d_type != DT_REG) + continue; + + strncpy (dwarf_filename, dwarf_image_dir_path, PATH_MAX); + strncat (dwarf_filename, "/", PATH_MAX); + strncat (dwarf_filename, directory_entry->d_name, PATH_MAX); + + if (!macho_try_dwarf (state, error_callback, data, fileline_fn, + executable_uuid, base_address, max_image_address, + vmslide, dwarf_filename, + &dwarf_matched, &dwarf_had_sym, &dwarf_had_dwarf)) + goto end; + + if (dwarf_matched) + { + *matched = 1; + *found_sym = dwarf_had_sym; + *found_dwarf = dwarf_had_dwarf; + ret = 1; + goto end; + } + } + + // No matching DWARF in this dSYM + ret = 1; + goto end; + +end: + if (dwarf_image_dir_valid) + closedir (dwarf_image_dir); + return ret; +} + +int +macho_add (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data, int descriptor, + const char *filename, fileline *fileline_fn, intptr_t vmslide, + int *found_sym, int *found_dwarf) +{ + uuid_t image_uuid; + uintptr_t image_file_base_address; + uintptr_t image_file_max_address; + uintptr_t image_actual_base_address = 0; + uintptr_t image_actual_max_address = 0; + + int ret = 0; + struct macho_commands_view commands_view; + int commands_view_valid = 0; + char executable_dirname[PATH_MAX]; + size_t filename_len; + DIR *executable_dir = NULL; + int executable_dir_valid = 0; + struct dirent *directory_entry; + char dsym_full_path[PATH_MAX]; + static const char *extension; + size_t extension_len; + ssize_t i; + + *found_sym = 0; + *found_dwarf = 0; + + // Find Mach-O commands list + int incompatible; + if (!macho_get_commands (state, descriptor, error_callback, data, + &commands_view, &incompatible)) + goto end; + commands_view_valid = 1; + + // First we need to get the uuid of our file so we can hunt down the correct + // dSYM + if (!macho_get_uuid (state, descriptor, error_callback, data, &commands_view, + &image_uuid)) + goto end; + + // Now we need to find the in memory base address. Step one is to find out + // what the executable thinks the base address is + if (!macho_get_addr_range (state, descriptor, error_callback, data, + &commands_view, + &image_file_base_address, + &image_file_max_address)) + goto end; + + image_actual_base_address = + image_file_base_address + vmslide; + image_actual_max_address = + image_file_max_address + vmslide; + + if (image_actual_base_address == 0) + { + error_callback (data, "executable file is not loaded", 0); + goto end; + } + + // Look for dSYM in our executable's directory + strncpy (executable_dirname, filename, PATH_MAX); + filename_len = strlen (executable_dirname); + for (i = filename_len - 1; i >= 0; i--) + { + if (executable_dirname[i] == '/') + { + executable_dirname[i] = '\0'; + break; + } + else if (i == 0) + { + executable_dirname[0] = '.'; + executable_dirname[1] = '\0'; + break; + } + } + + if (!(executable_dir = opendir (executable_dirname))) + { + error_callback (data, "could not open directory containing executable", + 0); + goto end; + } + executable_dir_valid = 1; + + extension = ".dSYM"; + extension_len = strlen (extension); + while ((directory_entry = readdir (executable_dir))) + { + if (directory_entry->d_namlen < extension_len) + continue; + if (strncasecmp (directory_entry->d_name + directory_entry->d_namlen + - extension_len, extension, extension_len) == 0) + { + int matched; + int dsym_had_sym; + int dsym_had_dwarf; + + // Found a dSYM + strncpy (dsym_full_path, executable_dirname, PATH_MAX); + strncat (dsym_full_path, "/", PATH_MAX); + strncat (dsym_full_path, directory_entry->d_name, PATH_MAX); + + if (!macho_try_dsym (state, error_callback, data, + fileline_fn, &image_uuid, + image_actual_base_address, + image_actual_max_address, vmslide, + dsym_full_path, + &matched, &dsym_had_sym, &dsym_had_dwarf)) + goto end; + + if (matched) + { + *found_sym = dsym_had_sym; + *found_dwarf = dsym_had_dwarf; + ret = 1; + goto end; + } + } + } + + // No matching dSYM + ret = 1; + goto end; + +end: + if (commands_view_valid) + backtrace_release_view (state, &commands_view.view, error_callback, + data); + if (executable_dir_valid) + closedir (executable_dir); + return ret; +} + +static int +macho_symbol_search (const void *vkey, const void *ventry) +{ + const uintptr_t *key = (const uintptr_t *) vkey; + const struct macho_symbol *entry = (const struct macho_symbol *) ventry; + uintptr_t addr; + + addr = *key; + if (addr < entry->addr) + return -1; + else if (addr >= entry->addr + entry->size) + return 1; + else + return 0; +} + +static void +macho_syminfo (struct backtrace_state *state, + uintptr_t addr, + backtrace_syminfo_callback callback, + backtrace_error_callback error_callback ATTRIBUTE_UNUSED, + void *data) +{ + struct macho_syminfo_data *edata; + struct macho_symbol *sym = NULL; + + if (!state->threaded) + { + for (edata = (struct macho_syminfo_data *) state->syminfo_data; + edata != NULL; + edata = edata->next) + { + if (addr >= edata->min_addr && addr <= edata->max_addr) + { + sym = ((struct macho_symbol *) + bsearch (&addr, edata->symbols, edata->symbol_count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; + } + } + } + else + { + struct macho_syminfo_data **pp; + + pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + while (1) + { + edata = backtrace_atomic_load_pointer (pp); + if (edata == NULL) + break; + + if (addr >= edata->min_addr && addr <= edata->max_addr) + { + sym = ((struct macho_symbol *) + bsearch (&addr, edata->symbols, edata->symbol_count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; + } + + pp = &edata->next; + } + } + + if (sym == NULL) + callback (data, addr, NULL, 0, 0); + else + callback (data, addr, sym->name, sym->addr, sym->size); +} + + +static int +macho_nodebug (struct backtrace_state *state ATTRIBUTE_UNUSED, + uintptr_t pc ATTRIBUTE_UNUSED, + backtrace_full_callback callback ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, void *data) +{ + error_callback (data, "no debug info in Mach-O executable", -1); + return 0; +} + +static void +macho_nosyms (struct backtrace_state *state ATTRIBUTE_UNUSED, + uintptr_t addr ATTRIBUTE_UNUSED, + backtrace_syminfo_callback callback ATTRIBUTE_UNUSED, + backtrace_error_callback error_callback, void *data) +{ + error_callback (data, "no symbol table in Mach-O executable", -1); +} + +int +backtrace_initialize (struct backtrace_state *state, int descriptor, + backtrace_error_callback error_callback, + void *data, fileline *fileline_fn) +{ + int ret; + fileline macho_fileline_fn = macho_nodebug; + int found_sym = 0; + int found_dwarf = 0; + uint32_t i = 0; + uint32_t loaded_image_count; + + // Add all loaded images + loaded_image_count = _dyld_image_count (); + for (i = 0; i < loaded_image_count; i++) + { + int current_found_sym; + int current_found_dwarf; + int current_descriptor; + intptr_t current_vmslide; + const char *current_name; + + current_vmslide = _dyld_get_image_vmaddr_slide (i); + current_name = _dyld_get_image_name (i); + + if (current_name == NULL || (i != 0 && current_vmslide == 0)) + continue; + + if (!(current_descriptor = + backtrace_open (current_name, error_callback, data, NULL))) + { + continue; + } + + if (macho_add (state, error_callback, data, current_descriptor, + current_name, &macho_fileline_fn, current_vmslide, + ¤t_found_sym, ¤t_found_dwarf)) + { + found_sym = found_sym || current_found_sym; + found_dwarf = found_dwarf || current_found_dwarf; + } + + backtrace_close (current_descriptor, error_callback, data); + } + + if (!state->threaded) + { + if (found_sym) + state->syminfo_fn = macho_syminfo; + else if (state->syminfo_fn == NULL) + state->syminfo_fn = macho_nosyms; + } + else + { + if (found_sym) + backtrace_atomic_store_pointer (&state->syminfo_fn, macho_syminfo); + else + (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, + macho_nosyms); + } + + if (!state->threaded) + { + if (state->fileline_fn == NULL || state->fileline_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + } + else + { + fileline current_fn; + + current_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + if (current_fn == NULL || current_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + } + + return 1; +} + diff --git a/src/libstd/build.rs b/src/libstd/build.rs index f57dec98b795b..ebd0db2edd00c 100644 --- a/src/libstd/build.rs +++ b/src/libstd/build.rs @@ -20,7 +20,7 @@ use build_helper::{run, native_lib_boilerplate}; fn main() { let target = env::var("TARGET").expect("TARGET was not set"); let host = env::var("HOST").expect("HOST was not set"); - if cfg!(feature = "backtrace") && !target.contains("apple") && !target.contains("msvc") && + if cfg!(feature = "backtrace") && !target.contains("msvc") && !target.contains("emscripten") && !target.contains("fuchsia") { let _ = build_libbacktrace(&host, &target); } diff --git a/src/libstd/sys/unix/backtrace/mod.rs b/src/libstd/sys/unix/backtrace/mod.rs index bf52da2ed4ac5..b5bf20c747be6 100644 --- a/src/libstd/sys/unix/backtrace/mod.rs +++ b/src/libstd/sys/unix/backtrace/mod.rs @@ -91,15 +91,29 @@ mod tracing; // symbol resolvers: mod printing; -#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "emscripten")))] +#[cfg(not(target_os = "emscripten"))] pub mod gnu { use io; use fs; use libc::c_char; + #[cfg(not(any(target_os = "macos", target_os = "ios")))] pub fn get_executable_filename() -> io::Result<(Vec, fs::File)> { Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub fn get_executable_filename() -> io::Result<(Vec, fs::File)> { + use env; + use os::unix::ffi::OsStrExt; + + let filename = env::current_exe()?; + let file = fs::File::open(&filename)?; + let mut filename_cstr: Vec<_> = filename.as_os_str().as_bytes().iter() + .map(|&x| x as c_char).collect(); + filename_cstr.push(0); // Null terminate + Ok((filename_cstr, file)) + } } pub struct BacktraceContext; diff --git a/src/libstd/sys/unix/backtrace/printing/dladdr.rs b/src/libstd/sys/unix/backtrace/printing/dladdr.rs index 05a071a797838..3a3912af021ce 100644 --- a/src/libstd/sys/unix/backtrace/printing/dladdr.rs +++ b/src/libstd/sys/unix/backtrace/printing/dladdr.rs @@ -31,14 +31,6 @@ pub fn resolve_symname(frame: Frame, } } -pub fn foreach_symbol_fileline(_symbol_addr: Frame, - _f: F, - _: &BacktraceContext) -> io::Result - where F: FnMut(&[u8], libc::c_int) -> io::Result<()> -{ - Ok(false) -} - #[repr(C)] struct Dl_info { dli_fname: *const libc::c_char, diff --git a/src/libstd/sys/unix/backtrace/printing/mod.rs b/src/libstd/sys/unix/backtrace/printing/mod.rs index 1ae82e0110016..8bd2d9eccd82a 100644 --- a/src/libstd/sys/unix/backtrace/printing/mod.rs +++ b/src/libstd/sys/unix/backtrace/printing/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT +// Copyright 2014-2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -8,15 +8,36 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -pub use self::imp::{foreach_symbol_fileline, resolve_symname}; +mod dladdr; -#[cfg(any(target_os = "macos", target_os = "ios", - target_os = "emscripten"))] -#[path = "dladdr.rs"] -mod imp; +use sys::backtrace::BacktraceContext; +use sys_common::backtrace::Frame; +use io; -#[cfg(not(any(target_os = "macos", target_os = "ios", - target_os = "emscripten")))] -mod imp { - pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname}; +#[cfg(target_os = "emscripten")] +pub use self::dladdr::resolve_symname; + +#[cfg(target_os = "emscripten")] +pub fn foreach_symbol_fileline(_: Frame, _: F, _: &BacktraceContext) -> io::Result +where + F: FnMut(&[u8], ::libc::c_int) -> io::Result<()> +{ + Ok(false) +} + +#[cfg(not(target_os = "emscripten"))] +pub use sys_common::gnu::libbacktrace::foreach_symbol_fileline; + +#[cfg(not(target_os = "emscripten"))] +pub fn resolve_symname(frame: Frame, callback: F, bc: &BacktraceContext) -> io::Result<()> +where + F: FnOnce(Option<&str>) -> io::Result<()> +{ + ::sys_common::gnu::libbacktrace::resolve_symname(frame, |symname| { + if symname.is_some() { + callback(symname) + } else { + dladdr::resolve_symname(frame, callback, bc) + } + }, bc) } diff --git a/src/libstd/sys_common/mod.rs b/src/libstd/sys_common/mod.rs index ccd4b91a7b733..30241819bd512 100644 --- a/src/libstd/sys_common/mod.rs +++ b/src/libstd/sys_common/mod.rs @@ -51,7 +51,7 @@ pub use sys::net; pub mod net; #[cfg(feature = "backtrace")] -#[cfg(any(all(unix, not(any(target_os = "macos", target_os = "ios", target_os = "emscripten"))), +#[cfg(any(all(unix, not(target_os = "emscripten")), all(windows, target_env = "gnu"), target_os = "redox"))] pub mod gnu; diff --git a/src/test/run-pass/backtrace-debuginfo.rs b/src/test/run-pass/backtrace-debuginfo.rs index b2ab25c44b82a..f81352e177322 100644 --- a/src/test/run-pass/backtrace-debuginfo.rs +++ b/src/test/run-pass/backtrace-debuginfo.rs @@ -33,9 +33,7 @@ macro_rules! dump_and_die { ($($pos:expr),*) => ({ // FIXME(#18285): we cannot include the current position because // the macro span takes over the last frame's file/line. - if cfg!(any(target_os = "macos", - target_os = "ios", - target_os = "android", + if cfg!(any(target_os = "android", all(target_os = "linux", target_arch = "arm"), target_os = "freebsd", target_os = "dragonfly",