mirror of
https://github.com/wolfpld/tracy.git
synced 2024-11-23 06:44:35 +00:00
1423 lines
44 KiB
C++
1423 lines
44 KiB
C++
|
/* 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 <sys/types.h>
|
||
|
#include <sys/syslimits.h>
|
||
|
#include <string.h>
|
||
|
#include <mach-o/loader.h>
|
||
|
#include <mach-o/nlist.h>
|
||
|
#include <mach-o/fat.h>
|
||
|
#include <mach-o/dyld.h>
|
||
|
#include <uuid/uuid.h>
|
||
|
#include <dirent.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#include "backtrace.hpp"
|
||
|
#include "internal.hpp"
|
||
|
|
||
|
namespace tracy
|
||
|
{
|
||
|
|
||
|
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 = (const struct 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 = (const struct fat_arch *)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 = (const mach_header_native_t *)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 =
|
||
|
(const struct load_command *)((const char*)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 = (const struct load_command *)((const char*)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 = (const struct macho_symbol *)left_raw;
|
||
|
const struct macho_symbol *right = (const struct macho_symbol *)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 = (const struct load_command *)((const char*)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 = (struct macho_syminfo_data *)
|
||
|
backtrace_alloc (state,
|
||
|
sizeof (struct macho_syminfo_data),
|
||
|
error_callback, data);
|
||
|
if (syminfo_data == NULL)
|
||
|
goto end;
|
||
|
|
||
|
syminfo_data->symbols = (struct macho_symbol *)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 = (const char*)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 = (struct macho_syminfo_data *)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 = (const struct load_command *)((const char*)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 = (const section_native_t *)((const char*)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 = (const unsigned char*)
|
||
|
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 = (struct macho_syminfo_data *)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, const char *filename,
|
||
|
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
|
||
|
{
|
||
|
#ifdef HAVE_SYNC_FUNCTIONS
|
||
|
fileline current_fn;
|
||
|
|
||
|
current_fn = backtrace_atomic_load_pointer (&state->fileline_fn);
|
||
|
if (current_fn == NULL || current_fn == macho_nodebug)
|
||
|
#endif
|
||
|
*fileline_fn = macho_fileline_fn;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
}
|