diff --git a/libbacktrace/alloc.cpp b/libbacktrace/alloc.cpp index 7aeae2b8..d7f02234 100644 --- a/libbacktrace/alloc.cpp +++ b/libbacktrace/alloc.cpp @@ -1,5 +1,5 @@ /* alloc.c -- Memory allocation without mmap. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -150,13 +150,24 @@ backtrace_vector_release (struct backtrace_state *state ATTRIBUTE_UNUSED, backtrace_error_callback error_callback, void *data) { + vec->alc = 0; + + if (vec->size == 0) + { + /* As of C17, realloc with size 0 is marked as an obsolescent feature, use + free instead. */ + rpfree (vec->base); + vec->base = NULL; + return 1; + } + vec->base = rprealloc (vec->base, vec->size); if (vec->base == NULL) { error_callback (data, "realloc", errno); return 0; } - vec->alc = 0; + return 1; } diff --git a/libbacktrace/backtrace.hpp b/libbacktrace/backtrace.hpp index 125a62cd..583421a7 100644 --- a/libbacktrace/backtrace.hpp +++ b/libbacktrace/backtrace.hpp @@ -1,5 +1,5 @@ /* backtrace.h -- Public header file for stack backtrace library. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -74,7 +74,13 @@ typedef void (*backtrace_error_callback) (void *data, const char *msg, use appropriate atomic operations. If THREADED is zero the state may only be accessed by one thread at a time. This returns a state pointer on success, NULL on error. If an error occurs, this will - call the ERROR_CALLBACK routine. */ + call the ERROR_CALLBACK routine. + + Calling this function allocates resources that cannot be freed. + There is no backtrace_free_state function. The state is used to + cache information that is expensive to recompute. Programs are + expected to call this function at most once and to save the return + value for all later calls to backtrace functions. */ extern struct backtrace_state *backtrace_create_state ( const char *filename, int threaded, diff --git a/libbacktrace/config.h b/libbacktrace/config.h index fde87532..becdbcbb 100644 --- a/libbacktrace/config.h +++ b/libbacktrace/config.h @@ -5,9 +5,11 @@ # define BACKTRACE_ELF_SIZE 32 #endif -#define HAVE_DLFCN_H -#define HAVE_FCNTL -#define HAVE_INTTYPES_H -#define HAVE_LSTAT -#define HAVE_READLINK -#define HAVE_DL_ITERATE_PHDR +#define HAVE_DLFCN_H 1 +#define HAVE_FCNTL 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LSTAT 1 +#define HAVE_READLINK 1 +#define HAVE_DL_ITERATE_PHDR 1 +#define HAVE_ATOMIC_FUNCTIONS 1 +#define HAVE_DECL_STRNLEN 1 diff --git a/libbacktrace/dwarf.cpp b/libbacktrace/dwarf.cpp index 74576da8..a58d2aac 100644 --- a/libbacktrace/dwarf.cpp +++ b/libbacktrace/dwarf.cpp @@ -1,5 +1,5 @@ /* dwarf.c -- Get file/line information from DWARF for backtraces. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -37,6 +37,8 @@ POSSIBILITY OF SUCH DAMAGE. */ #include #include +#include "filenames.hpp" + #include "backtrace.hpp" #include "internal.hpp" @@ -53,11 +55,11 @@ enum dwarf_tag { }; enum dwarf_form { - DW_FORM_addr = 0x1, - DW_FORM_block2 = 0x3, - DW_FORM_block4 = 0x4, - DW_FORM_data2 = 0x5, - DW_FORM_data4 = 0x6, + DW_FORM_addr = 0x01, + DW_FORM_block2 = 0x03, + DW_FORM_block4 = 0x04, + DW_FORM_data2 = 0x05, + DW_FORM_data4 = 0x06, DW_FORM_data8 = 0x07, DW_FORM_string = 0x08, DW_FORM_block = 0x09, @@ -78,25 +80,248 @@ enum dwarf_form { DW_FORM_exprloc = 0x18, DW_FORM_flag_present = 0x19, DW_FORM_ref_sig8 = 0x20, + DW_FORM_strx = 0x1a, + DW_FORM_addrx = 0x1b, + DW_FORM_ref_sup4 = 0x1c, + DW_FORM_strp_sup = 0x1d, + DW_FORM_data16 = 0x1e, + DW_FORM_line_strp = 0x1f, + DW_FORM_implicit_const = 0x21, + DW_FORM_loclistx = 0x22, + DW_FORM_rnglistx = 0x23, + DW_FORM_ref_sup8 = 0x24, + DW_FORM_strx1 = 0x25, + DW_FORM_strx2 = 0x26, + DW_FORM_strx3 = 0x27, + DW_FORM_strx4 = 0x28, + DW_FORM_addrx1 = 0x29, + DW_FORM_addrx2 = 0x2a, + DW_FORM_addrx3 = 0x2b, + DW_FORM_addrx4 = 0x2c, DW_FORM_GNU_addr_index = 0x1f01, DW_FORM_GNU_str_index = 0x1f02, DW_FORM_GNU_ref_alt = 0x1f20, - DW_FORM_GNU_strp_alt = 0x1f21, + DW_FORM_GNU_strp_alt = 0x1f21 }; enum dwarf_attribute { - DW_AT_name = 0x3, + DW_AT_sibling = 0x01, + DW_AT_location = 0x02, + DW_AT_name = 0x03, + DW_AT_ordering = 0x09, + DW_AT_subscr_data = 0x0a, + DW_AT_byte_size = 0x0b, + DW_AT_bit_offset = 0x0c, + DW_AT_bit_size = 0x0d, + DW_AT_element_list = 0x0f, DW_AT_stmt_list = 0x10, DW_AT_low_pc = 0x11, DW_AT_high_pc = 0x12, + DW_AT_language = 0x13, + DW_AT_member = 0x14, + DW_AT_discr = 0x15, + DW_AT_discr_value = 0x16, + DW_AT_visibility = 0x17, + DW_AT_import = 0x18, + DW_AT_string_length = 0x19, + DW_AT_common_reference = 0x1a, DW_AT_comp_dir = 0x1b, + DW_AT_const_value = 0x1c, + DW_AT_containing_type = 0x1d, + DW_AT_default_value = 0x1e, + DW_AT_inline = 0x20, + DW_AT_is_optional = 0x21, + DW_AT_lower_bound = 0x22, + DW_AT_producer = 0x25, + DW_AT_prototyped = 0x27, + DW_AT_return_addr = 0x2a, + DW_AT_start_scope = 0x2c, + DW_AT_bit_stride = 0x2e, + DW_AT_upper_bound = 0x2f, DW_AT_abstract_origin = 0x31, + DW_AT_accessibility = 0x32, + DW_AT_address_class = 0x33, + DW_AT_artificial = 0x34, + DW_AT_base_types = 0x35, + DW_AT_calling_convention = 0x36, + DW_AT_count = 0x37, + DW_AT_data_member_location = 0x38, + DW_AT_decl_column = 0x39, + DW_AT_decl_file = 0x3a, + DW_AT_decl_line = 0x3b, + DW_AT_declaration = 0x3c, + DW_AT_discr_list = 0x3d, + DW_AT_encoding = 0x3e, + DW_AT_external = 0x3f, + DW_AT_frame_base = 0x40, + DW_AT_friend = 0x41, + DW_AT_identifier_case = 0x42, + DW_AT_macro_info = 0x43, + DW_AT_namelist_items = 0x44, + DW_AT_priority = 0x45, + DW_AT_segment = 0x46, DW_AT_specification = 0x47, + DW_AT_static_link = 0x48, + DW_AT_type = 0x49, + DW_AT_use_location = 0x4a, + DW_AT_variable_parameter = 0x4b, + DW_AT_virtuality = 0x4c, + DW_AT_vtable_elem_location = 0x4d, + DW_AT_allocated = 0x4e, + DW_AT_associated = 0x4f, + DW_AT_data_location = 0x50, + DW_AT_byte_stride = 0x51, + DW_AT_entry_pc = 0x52, + DW_AT_use_UTF8 = 0x53, + DW_AT_extension = 0x54, DW_AT_ranges = 0x55, + DW_AT_trampoline = 0x56, + DW_AT_call_column = 0x57, DW_AT_call_file = 0x58, DW_AT_call_line = 0x59, + DW_AT_description = 0x5a, + DW_AT_binary_scale = 0x5b, + DW_AT_decimal_scale = 0x5c, + DW_AT_small = 0x5d, + DW_AT_decimal_sign = 0x5e, + DW_AT_digit_count = 0x5f, + DW_AT_picture_string = 0x60, + DW_AT_mutable = 0x61, + DW_AT_threads_scaled = 0x62, + DW_AT_explicit = 0x63, + DW_AT_object_pointer = 0x64, + DW_AT_endianity = 0x65, + DW_AT_elemental = 0x66, + DW_AT_pure = 0x67, + DW_AT_recursive = 0x68, + DW_AT_signature = 0x69, + DW_AT_main_subprogram = 0x6a, + DW_AT_data_bit_offset = 0x6b, + DW_AT_const_expr = 0x6c, + DW_AT_enum_class = 0x6d, DW_AT_linkage_name = 0x6e, + DW_AT_string_length_bit_size = 0x6f, + DW_AT_string_length_byte_size = 0x70, + DW_AT_rank = 0x71, + DW_AT_str_offsets_base = 0x72, + DW_AT_addr_base = 0x73, + DW_AT_rnglists_base = 0x74, + DW_AT_dwo_name = 0x76, + DW_AT_reference = 0x77, + DW_AT_rvalue_reference = 0x78, + DW_AT_macros = 0x79, + DW_AT_call_all_calls = 0x7a, + DW_AT_call_all_source_calls = 0x7b, + DW_AT_call_all_tail_calls = 0x7c, + DW_AT_call_return_pc = 0x7d, + DW_AT_call_value = 0x7e, + DW_AT_call_origin = 0x7f, + DW_AT_call_parameter = 0x80, + DW_AT_call_pc = 0x81, + DW_AT_call_tail_call = 0x82, + DW_AT_call_target = 0x83, + DW_AT_call_target_clobbered = 0x84, + DW_AT_call_data_location = 0x85, + DW_AT_call_data_value = 0x86, + DW_AT_noreturn = 0x87, + DW_AT_alignment = 0x88, + DW_AT_export_symbols = 0x89, + DW_AT_deleted = 0x8a, + DW_AT_defaulted = 0x8b, + DW_AT_loclists_base = 0x8c, + DW_AT_lo_user = 0x2000, + DW_AT_hi_user = 0x3fff, + DW_AT_MIPS_fde = 0x2001, + DW_AT_MIPS_loop_begin = 0x2002, + DW_AT_MIPS_tail_loop_begin = 0x2003, + DW_AT_MIPS_epilog_begin = 0x2004, + DW_AT_MIPS_loop_unroll_factor = 0x2005, + DW_AT_MIPS_software_pipeline_depth = 0x2006, DW_AT_MIPS_linkage_name = 0x2007, + DW_AT_MIPS_stride = 0x2008, + DW_AT_MIPS_abstract_name = 0x2009, + DW_AT_MIPS_clone_origin = 0x200a, + DW_AT_MIPS_has_inlines = 0x200b, + DW_AT_HP_block_index = 0x2000, + DW_AT_HP_unmodifiable = 0x2001, + DW_AT_HP_prologue = 0x2005, + DW_AT_HP_epilogue = 0x2008, + DW_AT_HP_actuals_stmt_list = 0x2010, + DW_AT_HP_proc_per_section = 0x2011, + DW_AT_HP_raw_data_ptr = 0x2012, + DW_AT_HP_pass_by_reference = 0x2013, + DW_AT_HP_opt_level = 0x2014, + DW_AT_HP_prof_version_id = 0x2015, + DW_AT_HP_opt_flags = 0x2016, + DW_AT_HP_cold_region_low_pc = 0x2017, + DW_AT_HP_cold_region_high_pc = 0x2018, + DW_AT_HP_all_variables_modifiable = 0x2019, + DW_AT_HP_linkage_name = 0x201a, + DW_AT_HP_prof_flags = 0x201b, + DW_AT_HP_unit_name = 0x201f, + DW_AT_HP_unit_size = 0x2020, + DW_AT_HP_widened_byte_size = 0x2021, + DW_AT_HP_definition_points = 0x2022, + DW_AT_HP_default_location = 0x2023, + DW_AT_HP_is_result_param = 0x2029, + DW_AT_sf_names = 0x2101, + DW_AT_src_info = 0x2102, + DW_AT_mac_info = 0x2103, + DW_AT_src_coords = 0x2104, + DW_AT_body_begin = 0x2105, + DW_AT_body_end = 0x2106, + DW_AT_GNU_vector = 0x2107, + DW_AT_GNU_guarded_by = 0x2108, + DW_AT_GNU_pt_guarded_by = 0x2109, + DW_AT_GNU_guarded = 0x210a, + DW_AT_GNU_pt_guarded = 0x210b, + DW_AT_GNU_locks_excluded = 0x210c, + DW_AT_GNU_exclusive_locks_required = 0x210d, + DW_AT_GNU_shared_locks_required = 0x210e, + DW_AT_GNU_odr_signature = 0x210f, + DW_AT_GNU_template_name = 0x2110, + DW_AT_GNU_call_site_value = 0x2111, + DW_AT_GNU_call_site_data_value = 0x2112, + DW_AT_GNU_call_site_target = 0x2113, + DW_AT_GNU_call_site_target_clobbered = 0x2114, + DW_AT_GNU_tail_call = 0x2115, + DW_AT_GNU_all_tail_call_sites = 0x2116, + DW_AT_GNU_all_call_sites = 0x2117, + DW_AT_GNU_all_source_call_sites = 0x2118, + DW_AT_GNU_macros = 0x2119, + DW_AT_GNU_deleted = 0x211a, + DW_AT_GNU_dwo_name = 0x2130, + DW_AT_GNU_dwo_id = 0x2131, + DW_AT_GNU_ranges_base = 0x2132, + DW_AT_GNU_addr_base = 0x2133, + DW_AT_GNU_pubnames = 0x2134, + DW_AT_GNU_pubtypes = 0x2135, + DW_AT_GNU_discriminator = 0x2136, + DW_AT_GNU_locviews = 0x2137, + DW_AT_GNU_entry_view = 0x2138, + DW_AT_VMS_rtnbeg_pd_address = 0x2201, + DW_AT_use_GNAT_descriptive_type = 0x2301, + DW_AT_GNAT_descriptive_type = 0x2302, + DW_AT_GNU_numerator = 0x2303, + DW_AT_GNU_denominator = 0x2304, + DW_AT_GNU_bias = 0x2305, + DW_AT_upc_threads_scaled = 0x3210, + DW_AT_PGI_lbase = 0x3a00, + DW_AT_PGI_soffset = 0x3a01, + DW_AT_PGI_lstride = 0x3a02, + DW_AT_APPLE_optimized = 0x3fe1, + DW_AT_APPLE_flags = 0x3fe2, + DW_AT_APPLE_isa = 0x3fe3, + DW_AT_APPLE_block = 0x3fe4, + DW_AT_APPLE_major_runtime_vers = 0x3fe5, + DW_AT_APPLE_runtime_class = 0x3fe6, + DW_AT_APPLE_omit_frame_ptr = 0x3fe7, + DW_AT_APPLE_property_name = 0x3fe8, + DW_AT_APPLE_property_getter = 0x3fe9, + DW_AT_APPLE_property_setter = 0x3fea, + DW_AT_APPLE_property_attribute = 0x3feb, + DW_AT_APPLE_objc_complete_type = 0x3fec, + DW_AT_APPLE_property = 0x3fed }; enum dwarf_line_number_op { @@ -115,24 +340,46 @@ enum dwarf_line_number_op { DW_LNS_set_isa = 0xc, }; -enum dwarf_extedned_line_number_op { +enum dwarf_extended_line_number_op { DW_LNE_end_sequence = 0x1, DW_LNE_set_address = 0x2, DW_LNE_define_file = 0x3, DW_LNE_set_discriminator = 0x4, }; -#if defined(__MSDOS__) || defined(_WIN32) || defined(__OS2__) || defined (__CYGWIN__) -# define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') -#define HAS_DRIVE_SPEC(f) ((f)[0] && (f)[1] == ':') -# define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR(f[0]) || HAS_DRIVE_SPEC(f)) -#else -# define IS_DIR_SEPARATOR(c) ((c) == '/') -# define IS_ABSOLUTE_PATH(f) IS_DIR_SEPARATOR(f[0]) -#endif +enum dwarf_line_number_content_type { + DW_LNCT_path = 0x1, + DW_LNCT_directory_index = 0x2, + DW_LNCT_timestamp = 0x3, + DW_LNCT_size = 0x4, + DW_LNCT_MD5 = 0x5, + DW_LNCT_lo_user = 0x2000, + DW_LNCT_hi_user = 0x3fff +}; + +enum dwarf_range_list_entry { + DW_RLE_end_of_list = 0x00, + DW_RLE_base_addressx = 0x01, + DW_RLE_startx_endx = 0x02, + DW_RLE_startx_length = 0x03, + DW_RLE_offset_pair = 0x04, + DW_RLE_base_address = 0x05, + DW_RLE_start_end = 0x06, + DW_RLE_start_length = 0x07 +}; + +enum dwarf_unit_type { + DW_UT_compile = 0x01, + DW_UT_type = 0x02, + DW_UT_partial = 0x03, + DW_UT_skeleton = 0x04, + DW_UT_split_compile = 0x05, + DW_UT_split_type = 0x06, + DW_UT_lo_user = 0x80, + DW_UT_hi_user = 0xff +}; #if !defined(HAVE_DECL_STRNLEN) || !HAVE_DECL_STRNLEN -#define HAVE_DECL_STRNLEN 1 /* If strnlen is not declared, provide our own version. */ @@ -181,6 +428,8 @@ struct attr enum dwarf_attribute name; /* The attribute form. */ enum dwarf_form form; + /* The attribute value, for DW_FORM_implicit_const. */ + int64_t val; }; /* A single DWARF abbreviation. */ @@ -218,22 +467,33 @@ struct abbrevs enum attr_val_encoding { + /* No attribute value. */ + ATTR_VAL_NONE, /* An address. */ ATTR_VAL_ADDRESS, + /* An index into the .debug_addr section, whose value is relative to + * the DW_AT_addr_base attribute of the compilation unit. */ + ATTR_VAL_ADDRESS_INDEX, /* A unsigned integer. */ ATTR_VAL_UINT, /* A sigd integer. */ ATTR_VAL_SINT, /* A string. */ ATTR_VAL_STRING, + /* An index into the .debug_str_offsets section. */ + ATTR_VAL_STRING_INDEX, /* An offset to other data in the containing unit. */ ATTR_VAL_REF_UNIT, - /* An offset to other data within the .dwarf_info section. */ + /* An offset to other data within the .debug_info section. */ ATTR_VAL_REF_INFO, + /* An offset to other data within the alt .debug_info section. */ + ATTR_VAL_REF_ALT_INFO, /* An offset to data in some other section. */ ATTR_VAL_REF_SECTION, /* A type signature. */ ATTR_VAL_REF_TYPE, + /* An index into the .debug_rnglists section. */ + ATTR_VAL_RNGLISTS_INDEX, /* A block of data (not represented). */ ATTR_VAL_BLOCK, /* An expression (not represented). */ @@ -248,7 +508,7 @@ struct attr_val enum attr_val_encoding encoding; union { - /* ATTR_VAL_ADDRESS, ATTR_VAL_UINT, ATTR_VAL_REF*. */ + /* ATTR_VAL_ADDRESS*, ATTR_VAL_UINT, ATTR_VAL_REF*. */ uint64_t uint; /* ATTR_VAL_SINT. */ int64_t sint; @@ -264,6 +524,8 @@ struct line_header { /* The version of the line number information. */ int version; + /* Address size. */ + int addrsize; /* The minimum instruction length. */ unsigned int min_insn_len; /* The maximum number of ops per instruction. */ @@ -286,6 +548,14 @@ struct line_header const char **filenames; }; +/* A format description from a line header. */ + +struct line_header_format +{ + int lnct; /* LNCT code. */ + enum dwarf_form form; /* Form of entry data. */ +}; + /* Map a single PC value to a file/line. We will keep a vector of these sorted by PC value. Each file/line will be correct from the PC up to the PC of the next entry if there is one. We allocate one @@ -368,6 +638,12 @@ struct unit /* The offset of UNIT_DATA from the start of the information for this compilation unit. */ size_t unit_data_offset; + /* Offset of the start of the compilation unit from the start of the + .debug_info section. */ + size_t low_offset; + /* Offset of the end of the compilation unit from the start of the + .debug_info section. */ + size_t high_offset; /* DWARF version. */ int version; /* Whether unit is DWARF64. */ @@ -376,6 +652,12 @@ struct unit int addrsize; /* Offset into line number information. */ off_t lineoff; + /* Offset of compilation unit in .debug_str_offsets. */ + uint64_t str_offsets_base; + /* Offset of compilation unit in .debug_addr. */ + uint64_t addr_base; + /* Offset of compilation unit in .debug_rnglists. */ + uint64_t rnglists_base; /* Primary source file. */ const char *filename; /* Compilation command working directory. */ @@ -426,30 +708,34 @@ struct unit_addrs_vector size_t count; }; +/* A growable vector of compilation unit pointer. */ + +struct unit_vector +{ + struct backtrace_vector vec; + size_t count; +}; + /* The information we need to map a PC to a file and line. */ struct dwarf_data { /* The data for the next file we know about. */ struct dwarf_data *next; + /* The data for .gnu_debugaltlink. */ + struct dwarf_data *altlink; /* The base address for this file. */ uintptr_t base_address; /* A sorted list of address ranges. */ struct unit_addrs *addrs; /* Number of address ranges in list. */ size_t addrs_count; - /* The unparsed .debug_info section. */ - const unsigned char *dwarf_info; - size_t dwarf_info_size; - /* The unparsed .debug_line section. */ - const unsigned char *dwarf_line; - size_t dwarf_line_size; - /* The unparsed .debug_ranges section. */ - const unsigned char *dwarf_ranges; - size_t dwarf_ranges_size; - /* The unparsed .debug_str section. */ - const unsigned char *dwarf_str; - size_t dwarf_str_size; + /* A sorted list of units. */ + struct unit **units; + /* Number of units in the list. */ + size_t units_count; + /* The unparsed DWARF debug data. */ + struct dwarf_sections dwarf_sections; /* Whether the data is big-endian or not. */ int is_bigendian; /* A vector used for function addresses. We keep this here so that @@ -500,6 +786,25 @@ advance (struct dwarf_buf *buf, size_t count) return 1; } +/* Read one zero-terminated string from BUF and advance past the string. */ + +static const char * +read_string (struct dwarf_buf *buf) +{ + const char *p = (const char *)buf->buf; + size_t len = strnlen (p, buf->left); + + /* - If len == left, we ran out of buffer before finding the zero terminator. + Generate an error by advancing len + 1. + - If len < left, advance by len + 1 to skip past the zero terminator. */ + size_t count = len + 1; + + if (!advance (buf, count)) + return NULL; + + return p; +} + /* Read one byte from BUF and advance 1 byte. */ static unsigned char @@ -539,6 +844,23 @@ read_uint16 (struct dwarf_buf *buf) return ((uint16_t) p[1] << 8) | (uint16_t) p[0]; } +/* Read a 24 bit value from BUF and advance 3 bytes. */ + +static uint32_t +read_uint24 (struct dwarf_buf *buf) +{ + const unsigned char *p = buf->buf; + + if (!advance (buf, 3)) + return 0; + if (buf->is_bigendian) + return (((uint32_t) p[0] << 16) | ((uint32_t) p[1] << 8) + | (uint32_t) p[2]); + else + return (((uint32_t) p[2] << 16) | ((uint32_t) p[1] << 8) + | (uint32_t) p[0]); +} + /* Read a uint32 from BUF and advance 4 bytes. */ static uint32_t @@ -721,6 +1043,25 @@ leb128_len (const unsigned char *p) return ret; } +/* Read initial_length from BUF and advance the appropriate number of bytes. */ + +static uint64_t +read_initial_length (struct dwarf_buf *buf, int *is_dwarf64) +{ + uint64_t len; + + len = read_uint32 (buf); + if (len == 0xffffffff) + { + len = read_uint64 (buf); + *is_dwarf64 = 1; + } + else + *is_dwarf64 = 0; + + return len; +} + /* Free an abbreviations structure. */ static void @@ -746,10 +1087,10 @@ free_abbrevs (struct backtrace_state *state, struct abbrevs *abbrevs, forms, because we don't care about them. */ static int -read_attribute (enum dwarf_form form, struct dwarf_buf *buf, - int is_dwarf64, int version, int addrsize, - const unsigned char *dwarf_str, size_t dwarf_str_size, - struct attr_val *val) +read_attribute (enum dwarf_form form, uint64_t implicit_val, + struct dwarf_buf *buf, int is_dwarf64, int version, + int addrsize, const struct dwarf_sections *dwarf_sections, + struct dwarf_data *altlink, struct attr_val *val) { /* Avoid warnings about val.u.FIELD may be used uninitialized if this function is inlined. The warnings aren't valid but can @@ -781,10 +1122,13 @@ read_attribute (enum dwarf_form form, struct dwarf_buf *buf, val->encoding = ATTR_VAL_UINT; val->u.uint = read_uint64 (buf); return 1; + case DW_FORM_data16: + val->encoding = ATTR_VAL_BLOCK; + return advance (buf, 16); case DW_FORM_string: val->encoding = ATTR_VAL_STRING; - val->u.string = (const char *) buf->buf; - return advance (buf, strnlen ((const char *) buf->buf, buf->left) + 1); + val->u.string = read_string (buf); + return val->u.string == NULL ? 0 : 1; case DW_FORM_block: val->encoding = ATTR_VAL_BLOCK; return advance (buf, read_uleb128 (buf)); @@ -808,13 +1152,29 @@ read_attribute (enum dwarf_form form, struct dwarf_buf *buf, uint64_t offset; offset = read_offset (buf, is_dwarf64); - if (offset >= dwarf_str_size) + if (offset >= dwarf_sections->size[DEBUG_STR]) { dwarf_buf_error (buf, "DW_FORM_strp out of range"); return 0; } val->encoding = ATTR_VAL_STRING; - val->u.string = (const char *) dwarf_str + offset; + val->u.string = + (const char *) dwarf_sections->data[DEBUG_STR] + offset; + return 1; + } + case DW_FORM_line_strp: + { + uint64_t offset; + + offset = read_offset (buf, is_dwarf64); + if (offset >= dwarf_sections->size[DEBUG_LINE_STR]) + { + dwarf_buf_error (buf, "DW_FORM_line_strp out of range"); + return 0; + } + val->encoding = ATTR_VAL_STRING; + val->u.string = + (const char *) dwarf_sections->data[DEBUG_LINE_STR] + offset; return 1; } case DW_FORM_udata: @@ -850,11 +1210,17 @@ read_attribute (enum dwarf_form form, struct dwarf_buf *buf, return 1; case DW_FORM_indirect: { - uint64_t form2; + uint64_t form; - form2 = read_uleb128 (buf); - return read_attribute ((enum dwarf_form) form2, buf, is_dwarf64, - version, addrsize, dwarf_str, dwarf_str_size, + form = read_uleb128 (buf); + if (form == DW_FORM_implicit_const) + { + dwarf_buf_error (buf, + "DW_FORM_indirect to DW_FORM_implicit_const"); + return 0; + } + return read_attribute ((enum dwarf_form) form, 0, buf, is_dwarf64, + version, addrsize, dwarf_sections, altlink, val); } case DW_FORM_sec_offset: @@ -872,6 +1238,88 @@ read_attribute (enum dwarf_form form, struct dwarf_buf *buf, val->encoding = ATTR_VAL_REF_TYPE; val->u.uint = read_uint64 (buf); return 1; + case DW_FORM_strx: case DW_FORM_strx1: case DW_FORM_strx2: + case DW_FORM_strx3: case DW_FORM_strx4: + { + uint64_t offset; + + switch (form) + { + case DW_FORM_strx: + offset = read_uleb128 (buf); + break; + case DW_FORM_strx1: + offset = read_byte (buf); + break; + case DW_FORM_strx2: + offset = read_uint16 (buf); + break; + case DW_FORM_strx3: + offset = read_uint24 (buf); + break; + case DW_FORM_strx4: + offset = read_uint32 (buf); + break; + default: + /* This case can't happen. */ + return 0; + } + val->encoding = ATTR_VAL_STRING_INDEX; + val->u.uint = offset; + return 1; + } + case DW_FORM_addrx: case DW_FORM_addrx1: case DW_FORM_addrx2: + case DW_FORM_addrx3: case DW_FORM_addrx4: + { + uint64_t offset; + + switch (form) + { + case DW_FORM_addrx: + offset = read_uleb128 (buf); + break; + case DW_FORM_addrx1: + offset = read_byte (buf); + break; + case DW_FORM_addrx2: + offset = read_uint16 (buf); + break; + case DW_FORM_addrx3: + offset = read_uint24 (buf); + break; + case DW_FORM_addrx4: + offset = read_uint32 (buf); + break; + default: + /* This case can't happen. */ + return 0; + } + val->encoding = ATTR_VAL_ADDRESS_INDEX; + val->u.uint = offset; + return 1; + } + case DW_FORM_ref_sup4: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uint32 (buf); + return 1; + case DW_FORM_ref_sup8: + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uint64 (buf); + return 1; + case DW_FORM_implicit_const: + val->encoding = ATTR_VAL_UINT; + val->u.uint = implicit_val; + return 1; + case DW_FORM_loclistx: + /* We don't distinguish this from DW_FORM_sec_offset. It + * shouldn't matter since we don't care about loclists. */ + val->encoding = ATTR_VAL_REF_SECTION; + val->u.uint = read_uleb128 (buf); + return 1; + case DW_FORM_rnglistx: + val->encoding = ATTR_VAL_RNGLISTS_INDEX; + val->u.uint = read_uleb128 (buf); + return 1; case DW_FORM_GNU_addr_index: val->encoding = ATTR_VAL_REF_SECTION; val->u.uint = read_uleb128 (buf); @@ -881,19 +1329,157 @@ read_attribute (enum dwarf_form form, struct dwarf_buf *buf, val->u.uint = read_uleb128 (buf); return 1; case DW_FORM_GNU_ref_alt: - val->encoding = ATTR_VAL_REF_SECTION; - val->u.uint = read_offset (buf, is_dwarf64); - return 1; - case DW_FORM_GNU_strp_alt: - val->encoding = ATTR_VAL_REF_SECTION; val->u.uint = read_offset (buf, is_dwarf64); + if (altlink == NULL) + { + val->encoding = ATTR_VAL_NONE; + return 1; + } + val->encoding = ATTR_VAL_REF_ALT_INFO; return 1; + case DW_FORM_strp_sup: case DW_FORM_GNU_strp_alt: + { + uint64_t offset; + + offset = read_offset (buf, is_dwarf64); + if (altlink == NULL) + { + val->encoding = ATTR_VAL_NONE; + return 1; + } + if (offset >= altlink->dwarf_sections.size[DEBUG_STR]) + { + dwarf_buf_error (buf, "DW_FORM_strp_sup out of range"); + return 0; + } + val->encoding = ATTR_VAL_STRING; + val->u.string = + (const char *) altlink->dwarf_sections.data[DEBUG_STR] + offset; + return 1; + } default: dwarf_buf_error (buf, "unrecognized DWARF form"); return 0; } } +/* If we can determine the value of a string attribute, set *STRING to + point to the string. Return 1 on success, 0 on error. If we don't + know the value, we consider that a success, and we don't change + *STRING. An error is only reported for some sort of out of range + offset. */ + +static int +resolve_string (const struct dwarf_sections *dwarf_sections, int is_dwarf64, + int is_bigendian, uint64_t str_offsets_base, + const struct attr_val *val, + backtrace_error_callback error_callback, void *data, + const char **string) +{ + switch (val->encoding) + { + case ATTR_VAL_STRING: + *string = val->u.string; + return 1; + + case ATTR_VAL_STRING_INDEX: + { + uint64_t offset; + struct dwarf_buf offset_buf; + + offset = val->u.uint * (is_dwarf64 ? 8 : 4) + str_offsets_base; + if (offset + (is_dwarf64 ? 8 : 4) + >= dwarf_sections->size[DEBUG_STR_OFFSETS]) + { + error_callback (data, "DW_FORM_strx value out of range", 0); + return 0; + } + + offset_buf.name = ".debug_str_offsets"; + offset_buf.start = dwarf_sections->data[DEBUG_STR_OFFSETS]; + offset_buf.buf = dwarf_sections->data[DEBUG_STR_OFFSETS] + offset; + offset_buf.left = dwarf_sections->size[DEBUG_STR_OFFSETS] - offset; + offset_buf.is_bigendian = is_bigendian; + offset_buf.error_callback = error_callback; + offset_buf.data = data; + offset_buf.reported_underflow = 0; + + offset = read_offset (&offset_buf, is_dwarf64); + if (offset >= dwarf_sections->size[DEBUG_STR]) + { + dwarf_buf_error (&offset_buf, "DW_FORM_strx offset out of range"); + return 0; + } + *string = (const char *) dwarf_sections->data[DEBUG_STR] + offset; + return 1; + } + + default: + return 1; + } +} + +/* Set *ADDRESS to the real address for a ATTR_VAL_ADDRESS_INDEX. + Return 1 on success, 0 on error. */ + +static int +resolve_addr_index (const struct dwarf_sections *dwarf_sections, + uint64_t addr_base, int addrsize, int is_bigendian, + uint64_t addr_index, + backtrace_error_callback error_callback, void *data, + uint64_t *address) +{ + uint64_t offset; + struct dwarf_buf addr_buf; + + offset = addr_index * addrsize + addr_base; + if (offset + addrsize >= dwarf_sections->size[DEBUG_ADDR]) + { + error_callback (data, "DW_FORM_addrx value out of range", 0); + return 0; + } + + addr_buf.name = ".debug_addr"; + addr_buf.start = dwarf_sections->data[DEBUG_ADDR]; + addr_buf.buf = dwarf_sections->data[DEBUG_ADDR] + offset; + addr_buf.left = dwarf_sections->size[DEBUG_ADDR] - offset; + addr_buf.is_bigendian = is_bigendian; + addr_buf.error_callback = error_callback; + addr_buf.data = data; + addr_buf.reported_underflow = 0; + + *address = read_address (&addr_buf, addrsize); + return 1; +} + +/* Compare a unit offset against a unit for bsearch. */ + +static int +units_search (const void *vkey, const void *ventry) +{ + const size_t *key = (const size_t *) vkey; + const struct unit *entry = *((const struct unit *const *) ventry); + size_t offset; + + offset = *key; + if (offset < entry->low_offset) + return -1; + else if (offset >= entry->high_offset) + return 1; + else + return 0; +} + +/* Find a unit in PU containing OFFSET. */ + +static struct unit * +find_unit (struct unit **pu, size_t units_count, size_t offset) +{ + struct unit **u; + u = (struct unit**)bsearch (&offset, pu, units_count, sizeof (struct unit *), units_search); + return u == NULL ? NULL : *u; +} + /* Compare function_addrs for qsort. When ranges are nested, make the smallest one sort last. */ @@ -934,31 +1520,28 @@ function_addrs_search (const void *vkey, const void *ventry) return 0; } -/* Add a new compilation unit address range to a vector. Returns 1 on - success, 0 on failure. */ +/* Add a new compilation unit address range to a vector. This is + called via add_ranges. Returns 1 on success, 0 on failure. */ static int -add_unit_addr (struct backtrace_state *state, uintptr_t base_address, - struct unit_addrs addrs, +add_unit_addr (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, backtrace_error_callback error_callback, void *data, - struct unit_addrs_vector *vec) + void *pvec) { + struct unit *u = (struct unit *) rdata; + struct unit_addrs_vector *vec = (struct unit_addrs_vector *) pvec; struct unit_addrs *p; - /* Add in the base address of the module here, so that we can look - up the PC directly. */ - addrs.low += base_address; - addrs.high += base_address; - /* Try to merge with the last entry. */ if (vec->count > 0) { p = (struct unit_addrs *) vec->vec.base + (vec->count - 1); - if ((addrs.low == p->high || addrs.low == p->high + 1) - && addrs.u == p->u) + if ((lowpc == p->high || lowpc == p->high + 1) + && u == p->u) { - if (addrs.high > p->high) - p->high = addrs.high; + if (highpc > p->high) + p->high = highpc; return 1; } } @@ -969,26 +1552,15 @@ add_unit_addr (struct backtrace_state *state, uintptr_t base_address, if (p == NULL) return 0; - *p = addrs; + p->low = lowpc; + p->high = highpc; + p->u = u; + ++vec->count; + return 1; } -/* Free a unit address vector. */ - -static void -free_unit_addrs_vector (struct backtrace_state *state, - struct unit_addrs_vector *vec, - backtrace_error_callback error_callback, void *data) -{ - struct unit_addrs *addrs; - size_t i; - - addrs = (struct unit_addrs *) vec->vec.base; - for (i = 0; i < vec->count; ++i) - free_abbrevs (state, &addrs[i].u->abbrevs, error_callback, data); -} - /* Compare unit_addrs for qsort. When ranges are nested, make the smallest one sort last. */ @@ -1145,7 +1717,13 @@ read_abbrevs (struct backtrace_state *state, uint64_t abbrev_offset, read_byte (&count_buf); // Skip attributes. while (read_uleb128 (&count_buf) != 0) - read_uleb128 (&count_buf); + { + uint64_t form; + + form = read_uleb128 (&count_buf); + if ((enum dwarf_form) form == DW_FORM_implicit_const) + read_sleb128 (&count_buf); + } // Skip form of last attribute. read_uleb128 (&count_buf); } @@ -1156,13 +1734,13 @@ read_abbrevs (struct backtrace_state *state, uint64_t abbrev_offset, if (num_abbrevs == 0) return 1; - abbrevs->num_abbrevs = num_abbrevs; abbrevs->abbrevs = ((struct abbrev *) backtrace_alloc (state, num_abbrevs * sizeof (struct abbrev), error_callback, data)); if (abbrevs->abbrevs == NULL) return 0; + abbrevs->num_abbrevs = num_abbrevs; memset (abbrevs->abbrevs, 0, num_abbrevs * sizeof (struct abbrev)); num_abbrevs = 0; @@ -1188,8 +1766,12 @@ read_abbrevs (struct backtrace_state *state, uint64_t abbrev_offset, num_attrs = 0; while (read_uleb128 (&count_buf) != 0) { + uint64_t form; + ++num_attrs; - read_uleb128 (&count_buf); + form = read_uleb128 (&count_buf); + if ((enum dwarf_form) form == DW_FORM_implicit_const) + read_sleb128 (&count_buf); } if (num_attrs == 0) @@ -1217,6 +1799,10 @@ read_abbrevs (struct backtrace_state *state, uint64_t abbrev_offset, break; attrs[num_attrs].name = (enum dwarf_attribute) name; attrs[num_attrs].form = (enum dwarf_form) form; + if ((enum dwarf_form) form == DW_FORM_implicit_const) + attrs[num_attrs].val = read_sleb128 (&abbrev_buf); + else + attrs[num_attrs].val = 0; ++num_attrs; } } @@ -1266,29 +1852,165 @@ lookup_abbrev (struct abbrevs *abbrevs, uint64_t code, return (const struct abbrev *) p; } -/* Add non-contiguous address ranges for a compilation unit. Returns - 1 on success, 0 on failure. */ +/* This struct is used to gather address range information while + reading attributes. We use this while building a mapping from + address ranges to compilation units and then again while mapping + from address ranges to function entries. Normally either + lowpc/highpc is set or ranges is set. */ + +struct pcrange { + uint64_t lowpc; /* The low PC value. */ + int have_lowpc; /* Whether a low PC value was found. */ + int lowpc_is_addr_index; /* Whether lowpc is in .debug_addr. */ + uint64_t highpc; /* The high PC value. */ + int have_highpc; /* Whether a high PC value was found. */ + int highpc_is_relative; /* Whether highpc is relative to lowpc. */ + int highpc_is_addr_index; /* Whether highpc is in .debug_addr. */ + uint64_t ranges; /* Offset in ranges section. */ + int have_ranges; /* Whether ranges is valid. */ + int ranges_is_index; /* Whether ranges is DW_FORM_rnglistx. */ +}; + +/* Update PCRANGE from an attribute value. */ + +static void +update_pcrange (const struct attr* attr, const struct attr_val* val, + struct pcrange *pcrange) +{ + switch (attr->name) + { + case DW_AT_low_pc: + if (val->encoding == ATTR_VAL_ADDRESS) + { + pcrange->lowpc = val->u.uint; + pcrange->have_lowpc = 1; + } + else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) + { + pcrange->lowpc = val->u.uint; + pcrange->have_lowpc = 1; + pcrange->lowpc_is_addr_index = 1; + } + break; + + case DW_AT_high_pc: + if (val->encoding == ATTR_VAL_ADDRESS) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + } + else if (val->encoding == ATTR_VAL_UINT) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + pcrange->highpc_is_relative = 1; + } + else if (val->encoding == ATTR_VAL_ADDRESS_INDEX) + { + pcrange->highpc = val->u.uint; + pcrange->have_highpc = 1; + pcrange->highpc_is_addr_index = 1; + } + break; + + case DW_AT_ranges: + if (val->encoding == ATTR_VAL_UINT + || val->encoding == ATTR_VAL_REF_SECTION) + { + pcrange->ranges = val->u.uint; + pcrange->have_ranges = 1; + } + else if (val->encoding == ATTR_VAL_RNGLISTS_INDEX) + { + pcrange->ranges = val->u.uint; + pcrange->have_ranges = 1; + pcrange->ranges_is_index = 1; + } + break; + + default: + break; + } +} + +/* Call ADD_RANGE for a low/high PC pair. Returns 1 on success, 0 on + error. */ static int -add_unit_ranges (struct backtrace_state *state, uintptr_t base_address, - struct unit *u, uint64_t ranges, uint64_t base, - int is_bigendian, const unsigned char *dwarf_ranges, - size_t dwarf_ranges_size, - backtrace_error_callback error_callback, void *data, - struct unit_addrs_vector *addrs) +add_low_high_range (struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, + void *rdata, uint64_t lowpc, + uint64_t highpc, + backtrace_error_callback error_callback, + void *data, void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + uint64_t lowpc; + uint64_t highpc; + + lowpc = pcrange->lowpc; + if (pcrange->lowpc_is_addr_index) + { + if (!resolve_addr_index (dwarf_sections, u->addr_base, u->addrsize, + is_bigendian, lowpc, error_callback, data, + &lowpc)) + return 0; + } + + highpc = pcrange->highpc; + if (pcrange->highpc_is_addr_index) + { + if (!resolve_addr_index (dwarf_sections, u->addr_base, u->addrsize, + is_bigendian, highpc, error_callback, data, + &highpc)) + return 0; + } + if (pcrange->highpc_is_relative) + highpc += lowpc; + + /* Add in the base address of the module when recording PC values, + so that we can look up the PC directly. */ + lowpc += base_address; + highpc += base_address; + + return add_range (state, rdata, lowpc, highpc, error_callback, data, vec); +} + +/* Call ADD_RANGE for each range read from .debug_ranges, as used in + DWARF versions 2 through 4. */ + +static int +add_ranges_from_ranges ( + struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, + const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) { struct dwarf_buf ranges_buf; - if (ranges >= dwarf_ranges_size) + if (pcrange->ranges >= dwarf_sections->size[DEBUG_RANGES]) { error_callback (data, "ranges offset out of range", 0); return 0; } ranges_buf.name = ".debug_ranges"; - ranges_buf.start = dwarf_ranges; - ranges_buf.buf = dwarf_ranges + ranges; - ranges_buf.left = dwarf_ranges_size - ranges; + ranges_buf.start = dwarf_sections->data[DEBUG_RANGES]; + ranges_buf.buf = dwarf_sections->data[DEBUG_RANGES] + pcrange->ranges; + ranges_buf.left = dwarf_sections->size[DEBUG_RANGES] - pcrange->ranges; ranges_buf.is_bigendian = is_bigendian; ranges_buf.error_callback = error_callback; ranges_buf.data = data; @@ -1312,13 +2034,10 @@ add_unit_ranges (struct backtrace_state *state, uintptr_t base_address, base = high; else { - struct unit_addrs a; - - a.low = low + base; - a.high = high + base; - a.u = u; - if (!add_unit_addr (state, base_address, a, error_callback, data, - addrs)) + if (!add_range (state, rdata, + low + base + base_address, + high + base + base_address, + error_callback, data, vec)) return 0; } } @@ -1329,6 +2048,220 @@ add_unit_ranges (struct backtrace_state *state, uintptr_t base_address, return 1; } +/* Call ADD_RANGE for each range read from .debug_rnglists, as used in + DWARF version 5. */ + +static int +add_ranges_from_rnglists ( + struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, + const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + uint64_t offset; + struct dwarf_buf rnglists_buf; + + if (!pcrange->ranges_is_index) + offset = pcrange->ranges; + else + offset = u->rnglists_base + pcrange->ranges * (u->is_dwarf64 ? 8 : 4); + if (offset >= dwarf_sections->size[DEBUG_RNGLISTS]) + { + error_callback (data, "rnglists offset out of range", 0); + return 0; + } + + rnglists_buf.name = ".debug_rnglists"; + rnglists_buf.start = dwarf_sections->data[DEBUG_RNGLISTS]; + rnglists_buf.buf = dwarf_sections->data[DEBUG_RNGLISTS] + offset; + rnglists_buf.left = dwarf_sections->size[DEBUG_RNGLISTS] - offset; + rnglists_buf.is_bigendian = is_bigendian; + rnglists_buf.error_callback = error_callback; + rnglists_buf.data = data; + rnglists_buf.reported_underflow = 0; + + if (pcrange->ranges_is_index) + { + offset = read_offset (&rnglists_buf, u->is_dwarf64); + offset += u->rnglists_base; + if (offset >= dwarf_sections->size[DEBUG_RNGLISTS]) + { + error_callback (data, "rnglists index offset out of range", 0); + return 0; + } + rnglists_buf.buf = dwarf_sections->data[DEBUG_RNGLISTS] + offset; + rnglists_buf.left = dwarf_sections->size[DEBUG_RNGLISTS] - offset; + } + + while (1) + { + unsigned char rle; + + rle = read_byte (&rnglists_buf); + if (rle == DW_RLE_end_of_list) + break; + switch (rle) + { + case DW_RLE_base_addressx: + { + uint64_t index; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &base)) + return 0; + } + break; + + case DW_RLE_startx_endx: + { + uint64_t index; + uint64_t low; + uint64_t high; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &low)) + return 0; + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &high)) + return 0; + if (!add_range (state, rdata, low + base_address, + high + base_address, error_callback, data, + vec)) + return 0; + } + break; + + case DW_RLE_startx_length: + { + uint64_t index; + uint64_t low; + uint64_t length; + + index = read_uleb128 (&rnglists_buf); + if (!resolve_addr_index (dwarf_sections, u->addr_base, + u->addrsize, is_bigendian, index, + error_callback, data, &low)) + return 0; + length = read_uleb128 (&rnglists_buf); + low += base_address; + if (!add_range (state, rdata, low, low + length, + error_callback, data, vec)) + return 0; + } + break; + + case DW_RLE_offset_pair: + { + uint64_t low; + uint64_t high; + + low = read_uleb128 (&rnglists_buf); + high = read_uleb128 (&rnglists_buf); + if (!add_range (state, rdata, low + base + base_address, + high + base + base_address, + error_callback, data, vec)) + return 0; + } + break; + + case DW_RLE_base_address: + base = read_address (&rnglists_buf, u->addrsize); + break; + + case DW_RLE_start_end: + { + uint64_t low; + uint64_t high; + + low = read_address (&rnglists_buf, u->addrsize); + high = read_address (&rnglists_buf, u->addrsize); + if (!add_range (state, rdata, low + base_address, + high + base_address, error_callback, data, + vec)) + return 0; + } + break; + + case DW_RLE_start_length: + { + uint64_t low; + uint64_t length; + + low = read_address (&rnglists_buf, u->addrsize); + length = read_uleb128 (&rnglists_buf); + low += base_address; + if (!add_range (state, rdata, low, low + length, + error_callback, data, vec)) + return 0; + } + break; + + default: + dwarf_buf_error (&rnglists_buf, "unrecognized DW_RLE value"); + return 0; + } + } + + if (rnglists_buf.reported_underflow) + return 0; + + return 1; +} + +/* Call ADD_RANGE for each lowpc/highpc pair in PCRANGE. RDATA is + passed to ADD_RANGE, and is either a struct unit * or a struct + function *. VEC is the vector we are adding ranges to, and is + either a struct unit_addrs_vector * or a struct function_vector *. + Returns 1 on success, 0 on error. */ + +static int +add_ranges (struct backtrace_state *state, + const struct dwarf_sections *dwarf_sections, + uintptr_t base_address, int is_bigendian, + struct unit *u, uint64_t base, const struct pcrange *pcrange, + int (*add_range) (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, + void *data, void *vec), + void *rdata, + backtrace_error_callback error_callback, void *data, + void *vec) +{ + if (pcrange->have_lowpc && pcrange->have_highpc) + return add_low_high_range (state, dwarf_sections, base_address, + is_bigendian, u, pcrange, add_range, rdata, + error_callback, data, vec); + + if (!pcrange->have_ranges) + { + /* Did not find any address ranges to add. */ + return 1; + } + + if (u->version < 5) + return add_ranges_from_ranges (state, dwarf_sections, base_address, + is_bigendian, u, base, pcrange, add_range, + rdata, error_callback, data, vec); + else + return add_ranges_from_rnglists (state, dwarf_sections, base_address, + is_bigendian, u, base, pcrange, add_range, + rdata, error_callback, data, vec); +} + /* Find the address range covered by a compilation unit, reading from UNIT_BUF and adding values to U. Returns 1 if all data could be read, 0 if there is some error. */ @@ -1336,24 +2269,21 @@ add_unit_ranges (struct backtrace_state *state, uintptr_t base_address, static int find_address_ranges (struct backtrace_state *state, uintptr_t base_address, struct dwarf_buf *unit_buf, - const unsigned char *dwarf_str, size_t dwarf_str_size, - const unsigned char *dwarf_ranges, - size_t dwarf_ranges_size, - int is_bigendian, backtrace_error_callback error_callback, - void *data, struct unit *u, - struct unit_addrs_vector *addrs) + const struct dwarf_sections *dwarf_sections, + int is_bigendian, struct dwarf_data *altlink, + backtrace_error_callback error_callback, void *data, + struct unit *u, struct unit_addrs_vector *addrs, + enum dwarf_tag *unit_tag) { while (unit_buf->left > 0) { uint64_t code; const struct abbrev *abbrev; - uint64_t lowpc; - int have_lowpc; - uint64_t highpc; - int have_highpc; - int highpc_is_relative; - uint64_t ranges; - int have_ranges; + struct pcrange pcrange; + struct attr_val name_val; + int have_name_val; + struct attr_val comp_dir_val; + int have_comp_dir_val; size_t i; code = read_uleb128 (unit_buf); @@ -1364,53 +2294,27 @@ find_address_ranges (struct backtrace_state *state, uintptr_t base_address, if (abbrev == NULL) return 0; - lowpc = 0; - have_lowpc = 0; - highpc = 0; - have_highpc = 0; - highpc_is_relative = 0; - ranges = 0; - have_ranges = 0; + if (unit_tag != NULL) + *unit_tag = abbrev->tag; + + memset (&pcrange, 0, sizeof pcrange); + memset (&name_val, 0, sizeof name_val); + have_name_val = 0; + memset (&comp_dir_val, 0, sizeof comp_dir_val); + have_comp_dir_val = 0; for (i = 0; i < abbrev->num_attrs; ++i) { struct attr_val val; - if (!read_attribute (abbrev->attrs[i].form, unit_buf, - u->is_dwarf64, u->version, u->addrsize, - dwarf_str, dwarf_str_size, &val)) + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + unit_buf, u->is_dwarf64, u->version, + u->addrsize, dwarf_sections, altlink, &val)) return 0; switch (abbrev->attrs[i].name) { - case DW_AT_low_pc: - if (val.encoding == ATTR_VAL_ADDRESS) - { - lowpc = val.u.uint; - have_lowpc = 1; - } - break; - - case DW_AT_high_pc: - if (val.encoding == ATTR_VAL_ADDRESS) - { - highpc = val.u.uint; - have_highpc = 1; - } - else if (val.encoding == ATTR_VAL_UINT) - { - highpc = val.u.uint; - have_highpc = 1; - highpc_is_relative = 1; - } - break; - - case DW_AT_ranges: - if (val.encoding == ATTR_VAL_UINT - || val.encoding == ATTR_VAL_REF_SECTION) - { - ranges = val.u.uint; - have_ranges = 1; - } + case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: + update_pcrange (&abbrev->attrs[i], &val, &pcrange); break; case DW_AT_stmt_list: @@ -1421,15 +2325,37 @@ find_address_ranges (struct backtrace_state *state, uintptr_t base_address, break; case DW_AT_name: - if (abbrev->tag == DW_TAG_compile_unit - && val.encoding == ATTR_VAL_STRING) - u->filename = val.u.string; + if (abbrev->tag == DW_TAG_compile_unit) + { + name_val = val; + have_name_val = 1; + } break; case DW_AT_comp_dir: + if (abbrev->tag == DW_TAG_compile_unit) + { + comp_dir_val = val; + have_comp_dir_val = 1; + } + break; + + case DW_AT_str_offsets_base: if (abbrev->tag == DW_TAG_compile_unit - && val.encoding == ATTR_VAL_STRING) - u->comp_dir = val.u.string; + && val.encoding == ATTR_VAL_REF_SECTION) + u->str_offsets_base = val.u.uint; + break; + + case DW_AT_addr_base: + if (abbrev->tag == DW_TAG_compile_unit + && val.encoding == ATTR_VAL_REF_SECTION) + u->addr_base = val.u.uint; + break; + + case DW_AT_rnglists_base: + if (abbrev->tag == DW_TAG_compile_unit + && val.encoding == ATTR_VAL_REF_SECTION) + u->rnglists_base = val.u.uint; break; default: @@ -1437,46 +2363,45 @@ find_address_ranges (struct backtrace_state *state, uintptr_t base_address, } } + // Resolve strings after we're sure that we have seen + // DW_AT_str_offsets_base. + if (have_name_val) + { + if (!resolve_string (dwarf_sections, u->is_dwarf64, is_bigendian, + u->str_offsets_base, &name_val, + error_callback, data, &u->filename)) + return 0; + } + if (have_comp_dir_val) + { + if (!resolve_string (dwarf_sections, u->is_dwarf64, is_bigendian, + u->str_offsets_base, &comp_dir_val, + error_callback, data, &u->comp_dir)) + return 0; + } + if (abbrev->tag == DW_TAG_compile_unit || abbrev->tag == DW_TAG_subprogram) { - if (have_ranges) - { - if (!add_unit_ranges (state, base_address, u, ranges, lowpc, - is_bigendian, dwarf_ranges, - dwarf_ranges_size, error_callback, - data, addrs)) - return 0; - } - else if (have_lowpc && have_highpc) - { - struct unit_addrs a; - - if (highpc_is_relative) - highpc += lowpc; - a.low = lowpc; - a.high = highpc; - a.u = u; - - if (!add_unit_addr (state, base_address, a, error_callback, data, - addrs)) - return 0; - } + if (!add_ranges (state, dwarf_sections, base_address, + is_bigendian, u, pcrange.lowpc, &pcrange, + add_unit_addr, (void *) u, error_callback, data, + (void *) addrs)) + return 0; /* If we found the PC range in the DW_TAG_compile_unit, we can stop now. */ if (abbrev->tag == DW_TAG_compile_unit - && (have_ranges || (have_lowpc && have_highpc))) + && (pcrange.have_ranges + || (pcrange.have_lowpc && pcrange.have_highpc))) return 1; } if (abbrev->has_children) { if (!find_address_ranges (state, base_address, unit_buf, - dwarf_str, dwarf_str_size, - dwarf_ranges, dwarf_ranges_size, - is_bigendian, error_callback, data, - u, addrs)) + dwarf_sections, is_bigendian, altlink, + error_callback, data, u, addrs, NULL)) return 0; } } @@ -1490,33 +2415,40 @@ find_address_ranges (struct backtrace_state *state, uintptr_t base_address, static int build_address_map (struct backtrace_state *state, uintptr_t base_address, - const unsigned char *dwarf_info, size_t dwarf_info_size, - const unsigned char *dwarf_abbrev, size_t dwarf_abbrev_size, - const unsigned char *dwarf_ranges, size_t dwarf_ranges_size, - const unsigned char *dwarf_str, size_t dwarf_str_size, - int is_bigendian, backtrace_error_callback error_callback, - void *data, struct unit_addrs_vector *addrs) + const struct dwarf_sections *dwarf_sections, + int is_bigendian, struct dwarf_data *altlink, + backtrace_error_callback error_callback, void *data, + struct unit_addrs_vector *addrs, + struct unit_vector *unit_vec) { struct dwarf_buf info; - struct abbrevs abbrevs; + struct backtrace_vector units; + size_t units_count; + size_t i; + struct unit **pu; + size_t unit_offset = 0; memset (&addrs->vec, 0, sizeof addrs->vec); + memset (&unit_vec->vec, 0, sizeof unit_vec->vec); addrs->count = 0; + unit_vec->count = 0; /* Read through the .debug_info section. FIXME: Should we use the .debug_aranges section? gdb and addr2line don't use it, but I'm not sure why. */ info.name = ".debug_info"; - info.start = dwarf_info; - info.buf = dwarf_info; - info.left = dwarf_info_size; + info.start = dwarf_sections->data[DEBUG_INFO]; + info.buf = info.start; + info.left = dwarf_sections->size[DEBUG_INFO]; info.is_bigendian = is_bigendian; info.error_callback = error_callback; info.data = data; info.reported_underflow = 0; - memset (&abbrevs, 0, sizeof abbrevs); + memset (&units, 0, sizeof units); + units_count = 0; + while (info.left > 0) { const unsigned char *unit_data_start; @@ -1524,23 +2456,18 @@ build_address_map (struct backtrace_state *state, uintptr_t base_address, int is_dwarf64; struct dwarf_buf unit_buf; int version; + int unit_type; uint64_t abbrev_offset; int addrsize; struct unit *u; + enum dwarf_tag unit_tag; if (info.reported_underflow) goto fail; unit_data_start = info.buf; - is_dwarf64 = 0; - len = read_uint32 (&info); - if (len == 0xffffffff) - { - len = read_uint64 (&info); - is_dwarf64 = 1; - } - + len = read_initial_length (&info, &is_dwarf64); unit_buf = info; unit_buf.left = len; @@ -1548,23 +2475,70 @@ build_address_map (struct backtrace_state *state, uintptr_t base_address, goto fail; version = read_uint16 (&unit_buf); - if (version < 2 || version > 4) + if (version < 2 || version > 5) { dwarf_buf_error (&unit_buf, "unrecognized DWARF version"); goto fail; } - abbrev_offset = read_offset (&unit_buf, is_dwarf64); - if (!read_abbrevs (state, abbrev_offset, dwarf_abbrev, dwarf_abbrev_size, - is_bigendian, error_callback, data, &abbrevs)) - goto fail; + if (version < 5) + unit_type = 0; + else + { + unit_type = read_byte (&unit_buf); + if (unit_type == DW_UT_type || unit_type == DW_UT_split_type) + { + /* This unit doesn't have anything we need. */ + continue; + } + } - addrsize = read_byte (&unit_buf); + pu = ((struct unit **) + backtrace_vector_grow (state, sizeof (struct unit *), + error_callback, data, &units)); + if (pu == NULL) + goto fail; u = ((struct unit *) backtrace_alloc (state, sizeof *u, error_callback, data)); if (u == NULL) goto fail; + + *pu = u; + ++units_count; + + if (version < 5) + addrsize = 0; /* Set below. */ + else + addrsize = read_byte (&unit_buf); + + memset (&u->abbrevs, 0, sizeof u->abbrevs); + abbrev_offset = read_offset (&unit_buf, is_dwarf64); + if (!read_abbrevs (state, abbrev_offset, + dwarf_sections->data[DEBUG_ABBREV], + dwarf_sections->size[DEBUG_ABBREV], + is_bigendian, error_callback, data, &u->abbrevs)) + goto fail; + + if (version < 5) + addrsize = read_byte (&unit_buf); + + switch (unit_type) + { + case 0: + break; + case DW_UT_compile: case DW_UT_partial: + break; + case DW_UT_skeleton: case DW_UT_split_compile: + read_uint64 (&unit_buf); /* dwo_id */ + break; + default: + break; + } + + u->low_offset = unit_offset; + unit_offset += len + (is_dwarf64 ? 12 : 4); + u->high_offset = unit_offset; u->unit_data = unit_buf.buf; u->unit_data_len = unit_buf.left; u->unit_data_offset = unit_buf.buf - unit_data_start; @@ -1575,8 +2549,6 @@ build_address_map (struct backtrace_state *state, uintptr_t base_address, u->comp_dir = NULL; u->abs_filename = NULL; u->lineoff = 0; - u->abbrevs = abbrevs; - memset (&abbrevs, 0, sizeof abbrevs); /* The actual line number mappings will be read as needed. */ u->lines = NULL; @@ -1584,32 +2556,37 @@ build_address_map (struct backtrace_state *state, uintptr_t base_address, u->function_addrs = NULL; u->function_addrs_count = 0; - if (!find_address_ranges (state, base_address, &unit_buf, - dwarf_str, dwarf_str_size, - dwarf_ranges, dwarf_ranges_size, - is_bigendian, error_callback, data, - u, addrs)) - { - free_abbrevs (state, &u->abbrevs, error_callback, data); - backtrace_free (state, u, sizeof *u, error_callback, data); - goto fail; - } + if (!find_address_ranges (state, base_address, &unit_buf, dwarf_sections, + is_bigendian, altlink, error_callback, data, + u, addrs, &unit_tag)) + goto fail; if (unit_buf.reported_underflow) - { - free_abbrevs (state, &u->abbrevs, error_callback, data); - backtrace_free (state, u, sizeof *u, error_callback, data); - goto fail; - } + goto fail; } if (info.reported_underflow) goto fail; + unit_vec->vec = units; + unit_vec->count = units_count; return 1; fail: - free_abbrevs (state, &abbrevs, error_callback, data); - free_unit_addrs_vector (state, addrs, error_callback, data); + if (units_count > 0) + { + pu = (struct unit **) units.base; + for (i = 0; i < units_count; i++) + { + free_abbrevs (state, &pu[i]->abbrevs, error_callback, data); + backtrace_free (state, pu[i], sizeof **pu, error_callback, data); + } + backtrace_vector_free (state, &units, error_callback, data); + } + if (addrs->count > 0) + { + backtrace_vector_free (state, &addrs->vec, error_callback, data); + addrs->count = 0; + } return 0; } @@ -1666,26 +2643,334 @@ free_line_header (struct backtrace_state *state, struct line_header *hdr, error_callback, data); } -/* Read the line header. Return 1 on success, 0 on failure. */ +/* Read the directories and file names for a line header for version + 2, setting fields in HDR. Return 1 on success, 0 on failure. */ static int -read_line_header (struct backtrace_state *state, struct unit *u, - int is_dwarf64, struct dwarf_buf *line_buf, - struct line_header *hdr) +read_v2_paths (struct backtrace_state *state, struct unit *u, + struct dwarf_buf *hdr_buf, struct line_header *hdr) { - uint64_t hdrlen; - struct dwarf_buf hdr_buf; const unsigned char *p; const unsigned char *pend; size_t i; + /* Count the number of directory entries. */ + hdr->dirs_count = 0; + p = hdr_buf->buf; + pend = p + hdr_buf->left; + while (p < pend && *p != '\0') + { + p += strnlen((const char *) p, pend - p) + 1; + ++hdr->dirs_count; + } + + hdr->dirs = NULL; + if (hdr->dirs_count != 0) + { + hdr->dirs = ((const char **) + backtrace_alloc (state, + hdr->dirs_count * sizeof (const char *), + hdr_buf->error_callback, + hdr_buf->data)); + if (hdr->dirs == NULL) + return 0; + } + + i = 0; + while (*hdr_buf->buf != '\0') + { + if (hdr_buf->reported_underflow) + return 0; + + hdr->dirs[i] = read_string (hdr_buf); + if (hdr->dirs[i] == NULL) + return 0; + ++i; + } + if (!advance (hdr_buf, 1)) + return 0; + + /* Count the number of file entries. */ + hdr->filenames_count = 0; + p = hdr_buf->buf; + pend = p + hdr_buf->left; + while (p < pend && *p != '\0') + { + p += strnlen ((const char *) p, pend - p) + 1; + p += leb128_len (p); + p += leb128_len (p); + p += leb128_len (p); + ++hdr->filenames_count; + } + + hdr->filenames = ((const char **) + backtrace_alloc (state, + hdr->filenames_count * sizeof (char *), + hdr_buf->error_callback, + hdr_buf->data)); + if (hdr->filenames == NULL) + return 0; + i = 0; + while (*hdr_buf->buf != '\0') + { + const char *filename; + uint64_t dir_index; + + if (hdr_buf->reported_underflow) + return 0; + + filename = read_string (hdr_buf); + if (filename == NULL) + return 0; + dir_index = read_uleb128 (hdr_buf); + if (IS_ABSOLUTE_PATH (filename) + || (dir_index == 0 && u->comp_dir == NULL)) + hdr->filenames[i] = filename; + else + { + const char *dir; + size_t dir_len; + size_t filename_len; + char *s; + + if (dir_index == 0) + dir = u->comp_dir; + else if (dir_index - 1 < hdr->dirs_count) + dir = hdr->dirs[dir_index - 1]; + else + { + dwarf_buf_error (hdr_buf, + ("invalid directory index in " + "line number program header")); + return 0; + } + dir_len = strlen (dir); + filename_len = strlen (filename); + s = ((char *) backtrace_alloc (state, dir_len + filename_len + 2, + hdr_buf->error_callback, + hdr_buf->data)); + if (s == NULL) + return 0; + memcpy (s, dir, dir_len); + /* FIXME: If we are on a DOS-based file system, and the + directory or the file name use backslashes, then we + should use a backslash here. */ + s[dir_len] = '/'; + memcpy (s + dir_len + 1, filename, filename_len + 1); + hdr->filenames[i] = s; + } + + /* Ignore the modification time and size. */ + read_uleb128 (hdr_buf); + read_uleb128 (hdr_buf); + + ++i; + } + + return 1; +} + +/* Read a single version 5 LNCT entry for a directory or file name in a + line header. Sets *STRING to the resulting name, ignoring other + data. Return 1 on success, 0 on failure. */ + +static int +read_lnct (struct backtrace_state *state, struct dwarf_data *ddata, + struct unit *u, struct dwarf_buf *hdr_buf, + const struct line_header *hdr, size_t formats_count, + const struct line_header_format *formats, const char **string) +{ + size_t i; + const char *dir; + const char *path; + + dir = NULL; + path = NULL; + for (i = 0; i < formats_count; i++) + { + struct attr_val val; + + if (!read_attribute (formats[i].form, 0, hdr_buf, u->is_dwarf64, + u->version, hdr->addrsize, &ddata->dwarf_sections, + ddata->altlink, &val)) + return 0; + switch (formats[i].lnct) + { + case DW_LNCT_path: + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, hdr_buf->error_callback, hdr_buf->data, + &path)) + return 0; + break; + case DW_LNCT_directory_index: + if (val.encoding == ATTR_VAL_UINT) + { + if (val.u.uint >= hdr->dirs_count) + { + dwarf_buf_error (hdr_buf, + ("invalid directory index in " + "line number program header")); + return 0; + } + dir = hdr->dirs[val.u.uint]; + } + break; + default: + /* We don't care about timestamps or sizes or hashes. */ + break; + } + } + + if (path == NULL) + { + dwarf_buf_error (hdr_buf, + "missing file name in line number program header"); + return 0; + } + + if (dir == NULL) + *string = path; + else + { + size_t dir_len; + size_t path_len; + char *s; + + dir_len = strlen (dir); + path_len = strlen (path); + s = (char *) backtrace_alloc (state, dir_len + path_len + 2, + hdr_buf->error_callback, hdr_buf->data); + if (s == NULL) + return 0; + memcpy (s, dir, dir_len); + /* FIXME: If we are on a DOS-based file system, and the + directory or the path name use backslashes, then we should + use a backslash here. */ + s[dir_len] = '/'; + memcpy (s + dir_len + 1, path, path_len + 1); + *string = s; + } + + return 1; +} + +/* Read a set of DWARF 5 line header format entries, setting *PCOUNT + and *PPATHS. Return 1 on success, 0 on failure. */ + +static int +read_line_header_format_entries (struct backtrace_state *state, + struct dwarf_data *ddata, + struct unit *u, + struct dwarf_buf *hdr_buf, + struct line_header *hdr, + size_t *pcount, + const char ***ppaths) +{ + size_t formats_count; + struct line_header_format *formats; + size_t paths_count; + const char **paths; + size_t i; + int ret; + + formats_count = read_byte (hdr_buf); + if (formats_count == 0) + formats = NULL; + else + { + formats = ((struct line_header_format *) + backtrace_alloc (state, + (formats_count + * sizeof (struct line_header_format)), + hdr_buf->error_callback, + hdr_buf->data)); + if (formats == NULL) + return 0; + + for (i = 0; i < formats_count; i++) + { + formats[i].lnct = (int) read_uleb128(hdr_buf); + formats[i].form = (enum dwarf_form) read_uleb128 (hdr_buf); + } + } + + paths_count = read_uleb128 (hdr_buf); + if (paths_count == 0) + { + *pcount = 0; + *ppaths = NULL; + ret = 1; + goto exit; + } + + paths = ((const char **) + backtrace_alloc (state, paths_count * sizeof (const char *), + hdr_buf->error_callback, hdr_buf->data)); + if (paths == NULL) + { + ret = 0; + goto exit; + } + for (i = 0; i < paths_count; i++) + { + if (!read_lnct (state, ddata, u, hdr_buf, hdr, formats_count, + formats, &paths[i])) + { + backtrace_free (state, paths, + paths_count * sizeof (const char *), + hdr_buf->error_callback, hdr_buf->data); + ret = 0; + goto exit; + } + } + + *pcount = paths_count; + *ppaths = paths; + + ret = 1; + + exit: + if (formats != NULL) + backtrace_free (state, formats, + formats_count * sizeof (struct line_header_format), + hdr_buf->error_callback, hdr_buf->data); + + return ret; +} + +/* Read the line header. Return 1 on success, 0 on failure. */ + +static int +read_line_header (struct backtrace_state *state, struct dwarf_data *ddata, + struct unit *u, int is_dwarf64, struct dwarf_buf *line_buf, + struct line_header *hdr) +{ + uint64_t hdrlen; + struct dwarf_buf hdr_buf; + hdr->version = read_uint16 (line_buf); - if (hdr->version < 2 || hdr->version > 4) + if (hdr->version < 2 || hdr->version > 5) { dwarf_buf_error (line_buf, "unsupported line number version"); return 0; } + if (hdr->version < 5) + hdr->addrsize = u->addrsize; + else + { + hdr->addrsize = read_byte (line_buf); + /* We could support a non-zero segment_selector_size but I doubt + we'll ever see it. */ + if (read_byte (line_buf) != 0) + { + dwarf_buf_error (line_buf, + "non-zero segment_selector_size not supported"); + return 0; + } + } + hdrlen = read_offset (line_buf, is_dwarf64); hdr_buf = *line_buf; @@ -1711,119 +2996,22 @@ read_line_header (struct backtrace_state *state, struct unit *u, if (!advance (&hdr_buf, hdr->opcode_base - 1)) return 0; - /* Count the number of directory entries. */ - hdr->dirs_count = 0; - p = hdr_buf.buf; - pend = p + hdr_buf.left; - while (p < pend && *p != '\0') + if (hdr->version < 5) { - p += strnlen((const char *) p, pend - p) + 1; - ++hdr->dirs_count; - } - - hdr->dirs = NULL; - if (hdr->dirs_count != 0) - { - hdr->dirs = ((const char **) - backtrace_alloc (state, - hdr->dirs_count * sizeof (const char *), - line_buf->error_callback, line_buf->data)); - if (hdr->dirs == NULL) + if (!read_v2_paths (state, u, &hdr_buf, hdr)) return 0; } - - i = 0; - while (*hdr_buf.buf != '\0') + else { - if (hdr_buf.reported_underflow) + if (!read_line_header_format_entries (state, ddata, u, &hdr_buf, hdr, + &hdr->dirs_count, + &hdr->dirs)) return 0; - - hdr->dirs[i] = (const char *) hdr_buf.buf; - ++i; - if (!advance (&hdr_buf, - strnlen ((const char *) hdr_buf.buf, hdr_buf.left) + 1)) + if (!read_line_header_format_entries (state, ddata, u, &hdr_buf, hdr, + &hdr->filenames_count, + &hdr->filenames)) return 0; } - if (!advance (&hdr_buf, 1)) - return 0; - - /* Count the number of file entries. */ - hdr->filenames_count = 0; - p = hdr_buf.buf; - pend = p + hdr_buf.left; - while (p < pend && *p != '\0') - { - p += strnlen ((const char *) p, pend - p) + 1; - p += leb128_len (p); - p += leb128_len (p); - p += leb128_len (p); - ++hdr->filenames_count; - } - - hdr->filenames = ((const char **) - backtrace_alloc (state, - hdr->filenames_count * sizeof (char *), - line_buf->error_callback, - line_buf->data)); - if (hdr->filenames == NULL) - return 0; - i = 0; - while (*hdr_buf.buf != '\0') - { - const char *filename; - uint64_t dir_index; - - if (hdr_buf.reported_underflow) - return 0; - - filename = (const char *) hdr_buf.buf; - if (!advance (&hdr_buf, - strnlen ((const char *) hdr_buf.buf, hdr_buf.left) + 1)) - return 0; - dir_index = read_uleb128 (&hdr_buf); - if (IS_ABSOLUTE_PATH (filename) - || (dir_index == 0 && u->comp_dir == NULL)) - hdr->filenames[i] = filename; - else - { - const char *dir; - size_t dir_len; - size_t filename_len; - char *s; - - if (dir_index == 0) - dir = u->comp_dir; - else if (dir_index - 1 < hdr->dirs_count) - dir = hdr->dirs[dir_index - 1]; - else - { - dwarf_buf_error (line_buf, - ("invalid directory index in " - "line number program header")); - return 0; - } - dir_len = strlen (dir); - filename_len = strlen (filename); - s = ((char *) - backtrace_alloc (state, dir_len + filename_len + 2, - line_buf->error_callback, line_buf->data)); - if (s == NULL) - return 0; - memcpy (s, dir, dir_len); - /* FIXME: If we are on a DOS-based file system, and the - directory or the file name use backslashes, then we - should use a backslash here. */ - s[dir_len] = '/'; - memcpy (s + dir_len + 1, filename, filename_len + 1); - hdr->filenames[i] = s; - } - - /* Ignore the modification time and size. */ - read_uleb128 (&hdr_buf); - read_uleb128 (&hdr_buf); - - ++i; - } if (hdr_buf.reported_underflow) return 0; @@ -1890,15 +3078,15 @@ read_line_program (struct backtrace_state *state, struct dwarf_data *ddata, lineno = 1; break; case DW_LNE_set_address: - address = read_address (line_buf, u->addrsize); + address = read_address (line_buf, hdr->addrsize); break; case DW_LNE_define_file: { const char *f; unsigned int dir_index; - f = (const char *) line_buf->buf; - if (!advance (line_buf, strnlen (f, line_buf->left) + 1)) + f = read_string (line_buf); + if (f == NULL) return 0; dir_index = read_uleb128 (line_buf); /* Ignore that time and length. */ @@ -1913,7 +3101,7 @@ read_line_program (struct backtrace_state *state, struct dwarf_data *ddata, size_t f_len; char *p; - if (dir_index == 0) + if (dir_index == 0 && hdr->version < 5) dir = u->comp_dir; else if (dir_index - 1 < hdr->dirs_count) dir = hdr->dirs[dir_index - 1]; @@ -2059,31 +3247,25 @@ read_line_info (struct backtrace_state *state, struct dwarf_data *ddata, memset (hdr, 0, sizeof *hdr); if (u->lineoff != (off_t) (size_t) u->lineoff - || (size_t) u->lineoff >= ddata->dwarf_line_size) + || (size_t) u->lineoff >= ddata->dwarf_sections.size[DEBUG_LINE]) { error_callback (data, "unit line offset out of range", 0); goto fail; } line_buf.name = ".debug_line"; - line_buf.start = ddata->dwarf_line; - line_buf.buf = ddata->dwarf_line + u->lineoff; - line_buf.left = ddata->dwarf_line_size - u->lineoff; + line_buf.start = ddata->dwarf_sections.data[DEBUG_LINE]; + line_buf.buf = ddata->dwarf_sections.data[DEBUG_LINE] + u->lineoff; + line_buf.left = ddata->dwarf_sections.size[DEBUG_LINE] - u->lineoff; line_buf.is_bigendian = ddata->is_bigendian; line_buf.error_callback = error_callback; line_buf.data = data; line_buf.reported_underflow = 0; - is_dwarf64 = 0; - len = read_uint32 (&line_buf); - if (len == 0xffffffff) - { - len = read_uint64 (&line_buf); - is_dwarf64 = 1; - } + len = read_initial_length (&line_buf, &is_dwarf64); line_buf.left = len; - if (!read_line_header (state, u, is_dwarf64, &line_buf, hdr)) + if (!read_line_header (state, ddata, u, is_dwarf64, &line_buf, hdr)) goto fail; if (!read_line_program (state, ddata, u, hdr, &line_buf, &vec)) @@ -2123,15 +3305,69 @@ read_line_info (struct backtrace_state *state, struct dwarf_data *ddata, return 1; fail: - vec.vec.alc += vec.vec.size; - vec.vec.size = 0; - backtrace_vector_release (state, &vec.vec, error_callback, data); + backtrace_vector_free (state, &vec.vec, error_callback, data); free_line_header (state, hdr, error_callback, data); *lines = (struct line *) (uintptr_t) -1; *lines_count = 0; return 0; } +static const char *read_referenced_name (struct dwarf_data *, struct unit *, + uint64_t, backtrace_error_callback, + void *); + +/* Read the name of a function from a DIE referenced by ATTR with VAL. */ + +static const char * +read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, + struct attr *attr, struct attr_val *val, + backtrace_error_callback error_callback, + void *data) +{ + switch (attr->name) + { + case DW_AT_abstract_origin: + case DW_AT_specification: + break; + default: + return NULL; + } + + if (attr->form == DW_FORM_ref_sig8) + return NULL; + + if (val->encoding == ATTR_VAL_REF_INFO) + { + struct unit *unit + = find_unit (ddata->units, ddata->units_count, + val->u.uint); + if (unit == NULL) + return NULL; + + uint64_t offset = val->u.uint - unit->low_offset; + return read_referenced_name (ddata, unit, offset, error_callback, data); + } + + if (val->encoding == ATTR_VAL_UINT + || val->encoding == ATTR_VAL_REF_UNIT) + return read_referenced_name (ddata, u, val->u.uint, error_callback, data); + + if (val->encoding == ATTR_VAL_REF_ALT_INFO) + { + struct unit *alt_unit + = find_unit (ddata->altlink->units, ddata->altlink->units_count, + val->u.uint); + if (alt_unit == NULL) + return NULL; + + uint64_t offset = val->u.uint - alt_unit->low_offset; + return read_referenced_name (ddata->altlink, alt_unit, offset, + error_callback, data); + } + + return NULL; +} + /* Read the name of a function from a DIE referenced by a DW_AT_abstract_origin or DW_AT_specification tag. OFFSET is within the same compilation unit. */ @@ -2163,7 +3399,7 @@ read_referenced_name (struct dwarf_data *ddata, struct unit *u, offset -= u->unit_data_offset; unit_buf.name = ".debug_info"; - unit_buf.start = ddata->dwarf_info; + unit_buf.start = ddata->dwarf_sections.data[DEBUG_INFO]; unit_buf.buf = u->unit_data + offset; unit_buf.left = u->unit_data_len - offset; unit_buf.is_bigendian = ddata->is_bigendian; @@ -2187,45 +3423,52 @@ read_referenced_name (struct dwarf_data *ddata, struct unit *u, { struct attr_val val; - if (!read_attribute (abbrev->attrs[i].form, &unit_buf, - u->is_dwarf64, u->version, u->addrsize, - ddata->dwarf_str, ddata->dwarf_str_size, - &val)) + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + &unit_buf, u->is_dwarf64, u->version, u->addrsize, + &ddata->dwarf_sections, ddata->altlink, &val)) return NULL; switch (abbrev->attrs[i].name) { case DW_AT_name: - /* We prefer the linkage name if get one. */ - if (val.encoding == ATTR_VAL_STRING) - ret = val.u.string; + /* Third name preference: don't override. A name we found in some + other way, will normally be more useful -- e.g., this name is + normally not mangled. */ + if (ret != NULL) + break; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, error_callback, data, &ret)) + return NULL; break; case DW_AT_linkage_name: case DW_AT_MIPS_linkage_name: - if (val.encoding == ATTR_VAL_STRING) - return val.u.string; + /* First name preference: override all. */ + { + const char *s; + + s = NULL; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, u->str_offsets_base, + &val, error_callback, data, &s)) + return NULL; + if (s != NULL) + return s; + } break; case DW_AT_specification: - if (abbrev->attrs[i].form == DW_FORM_ref_addr - || abbrev->attrs[i].form == DW_FORM_ref_sig8) - { - /* This refers to a specification defined in some other - compilation unit. We can handle this case if we - must, but it's harder. */ - break; - } - if (val.encoding == ATTR_VAL_UINT - || val.encoding == ATTR_VAL_REF_UNIT) - { - const char *name; + /* Second name preference: override DW_AT_name, don't override + DW_AT_linkage_name. */ + { + const char *name; - name = read_referenced_name (ddata, u, val.u.uint, - error_callback, data); - if (name != NULL) - ret = name; - } + name = read_referenced_name_from_attr (ddata, u, &abbrev->attrs[i], + &val, error_callback, data); + if (name != NULL) + ret = name; + } break; default: @@ -2236,25 +3479,22 @@ read_referenced_name (struct dwarf_data *ddata, struct unit *u, return ret; } -/* Add a single range to U that maps to function. Returns 1 on - success, 0 on error. */ +/* Add a range to a unit that maps to a function. This is called via + add_ranges. Returns 1 on success, 0 on error. */ static int -add_function_range (struct backtrace_state *state, struct dwarf_data *ddata, - struct function *function, uint64_t lowpc, uint64_t highpc, - backtrace_error_callback error_callback, - void *data, struct function_vector *vec) +add_function_range (struct backtrace_state *state, void *rdata, + uint64_t lowpc, uint64_t highpc, + backtrace_error_callback error_callback, void *data, + void *pvec) { + struct function *function = (struct function *) rdata; + struct function_vector *vec = (struct function_vector *) pvec; struct function_addrs *p; - /* Add in the base address here, so that we can look up the PC - directly. */ - lowpc += ddata->base_address; - highpc += ddata->base_address; - if (vec->count > 0) { - p = (struct function_addrs *) vec->vec.base + vec->count - 1; + p = (struct function_addrs *) vec->vec.base + (vec->count - 1); if ((lowpc == p->high || lowpc == p->high + 1) && function == p->function) { @@ -2273,63 +3513,8 @@ add_function_range (struct backtrace_state *state, struct dwarf_data *ddata, p->low = lowpc; p->high = highpc; p->function = function; + ++vec->count; - return 1; -} - -/* Add PC ranges to U that map to FUNCTION. Returns 1 on success, 0 - on error. */ - -static int -add_function_ranges (struct backtrace_state *state, struct dwarf_data *ddata, - struct unit *u, struct function *function, - uint64_t ranges, uint64_t base, - backtrace_error_callback error_callback, void *data, - struct function_vector *vec) -{ - struct dwarf_buf ranges_buf; - - if (ranges >= ddata->dwarf_ranges_size) - { - error_callback (data, "function ranges offset out of range", 0); - return 0; - } - - ranges_buf.name = ".debug_ranges"; - ranges_buf.start = ddata->dwarf_ranges; - ranges_buf.buf = ddata->dwarf_ranges + ranges; - ranges_buf.left = ddata->dwarf_ranges_size - ranges; - ranges_buf.is_bigendian = ddata->is_bigendian; - ranges_buf.error_callback = error_callback; - ranges_buf.data = data; - ranges_buf.reported_underflow = 0; - - while (1) - { - uint64_t low; - uint64_t high; - - if (ranges_buf.reported_underflow) - return 0; - - low = read_address (&ranges_buf, u->addrsize); - high = read_address (&ranges_buf, u->addrsize); - - if (low == 0 && high == 0) - break; - - if (is_highest_address (low, u->addrsize)) - base = high; - else - { - if (!add_function_range (state, ddata, function, low + base, - high + base, error_callback, data, vec)) - return 0; - } - } - - if (ranges_buf.reported_underflow) - return 0; return 1; } @@ -2353,13 +3538,8 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, struct function *function; struct function_vector *vec; size_t i; - uint64_t lowpc; - int have_lowpc; - uint64_t highpc; - int have_highpc; - int highpc_is_relative; - uint64_t ranges; - int have_ranges; + struct pcrange pcrange; + int have_linkage_name; code = read_uleb128 (unit_buf); if (code == 0) @@ -2389,29 +3569,34 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, memset (function, 0, sizeof *function); } - lowpc = 0; - have_lowpc = 0; - highpc = 0; - have_highpc = 0; - highpc_is_relative = 0; - ranges = 0; - have_ranges = 0; + memset (&pcrange, 0, sizeof pcrange); + have_linkage_name = 0; for (i = 0; i < abbrev->num_attrs; ++i) { struct attr_val val; - if (!read_attribute (abbrev->attrs[i].form, unit_buf, - u->is_dwarf64, u->version, u->addrsize, - ddata->dwarf_str, ddata->dwarf_str_size, - &val)) + if (!read_attribute (abbrev->attrs[i].form, abbrev->attrs[i].val, + unit_buf, u->is_dwarf64, u->version, + u->addrsize, &ddata->dwarf_sections, + ddata->altlink, &val)) return 0; /* The compile unit sets the base address for any address ranges in the function entries. */ if (abbrev->tag == DW_TAG_compile_unit - && abbrev->attrs[i].name == DW_AT_low_pc - && val.encoding == ATTR_VAL_ADDRESS) - base = val.u.uint; + && abbrev->attrs[i].name == DW_AT_low_pc) + { + if (val.encoding == ATTR_VAL_ADDRESS) + base = val.u.uint; + else if (val.encoding == ATTR_VAL_ADDRESS_INDEX) + { + if (!resolve_addr_index (&ddata->dwarf_sections, + u->addr_base, u->addrsize, + ddata->is_bigendian, val.u.uint, + error_callback, data, &base)) + return 0; + } + } if (is_function) { @@ -2444,73 +3629,55 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, case DW_AT_abstract_origin: case DW_AT_specification: - if (abbrev->attrs[i].form == DW_FORM_ref_addr - || abbrev->attrs[i].form == DW_FORM_ref_sig8) - { - /* This refers to an abstract origin defined in - some other compilation unit. We can handle - this case if we must, but it's harder. */ - break; - } - if (val.encoding == ATTR_VAL_UINT - || val.encoding == ATTR_VAL_REF_UNIT) - { - const char *name; + /* Second name preference: override DW_AT_name, don't override + DW_AT_linkage_name. */ + if (have_linkage_name) + break; + { + const char *name; - name = read_referenced_name (ddata, u, val.u.uint, - error_callback, data); - if (name != NULL) - function->name = name; - } + name + = read_referenced_name_from_attr (ddata, u, + &abbrev->attrs[i], &val, + error_callback, data); + if (name != NULL) + function->name = name; + } break; case DW_AT_name: - if (val.encoding == ATTR_VAL_STRING) - { - /* Don't override a name we found in some other - way, as it will normally be more - useful--e.g., this name is normally not - mangled. */ - if (function->name == NULL) - function->name = val.u.string; - } + /* Third name preference: don't override. */ + if (function->name != NULL) + break; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, + u->str_offsets_base, &val, + error_callback, data, &function->name)) + return 0; break; case DW_AT_linkage_name: case DW_AT_MIPS_linkage_name: - if (val.encoding == ATTR_VAL_STRING) - function->name = val.u.string; + /* First name preference: override all. */ + { + const char *s; + + s = NULL; + if (!resolve_string (&ddata->dwarf_sections, u->is_dwarf64, + ddata->is_bigendian, + u->str_offsets_base, &val, + error_callback, data, &s)) + return 0; + if (s != NULL) + { + function->name = s; + have_linkage_name = 1; + } + } break; - case DW_AT_low_pc: - if (val.encoding == ATTR_VAL_ADDRESS) - { - lowpc = val.u.uint; - have_lowpc = 1; - } - break; - - case DW_AT_high_pc: - if (val.encoding == ATTR_VAL_ADDRESS) - { - highpc = val.u.uint; - have_highpc = 1; - } - else if (val.encoding == ATTR_VAL_UINT) - { - highpc = val.u.uint; - have_highpc = 1; - highpc_is_relative = 1; - } - break; - - case DW_AT_ranges: - if (val.encoding == ATTR_VAL_UINT - || val.encoding == ATTR_VAL_REF_SECTION) - { - ranges = val.u.uint; - have_ranges = 1; - } + case DW_AT_low_pc: case DW_AT_high_pc: case DW_AT_ranges: + update_pcrange (&abbrev->attrs[i], &val, &pcrange); break; default: @@ -2530,18 +3697,14 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, if (is_function) { - if (have_ranges) + if (pcrange.have_ranges + || (pcrange.have_lowpc && pcrange.have_highpc)) { - if (!add_function_ranges (state, ddata, u, function, ranges, - base, error_callback, data, vec)) - return 0; - } - else if (have_lowpc && have_highpc) - { - if (highpc_is_relative) - highpc += lowpc; - if (!add_function_range (state, ddata, function, lowpc, highpc, - error_callback, data, vec)) + if (!add_ranges (state, &ddata->dwarf_sections, + ddata->base_address, ddata->is_bigendian, + u, base, &pcrange, add_function_range, + (void *) function, error_callback, data, + (void *) vec)) return 0; } else @@ -2625,7 +3788,7 @@ read_function_info (struct backtrace_state *state, struct dwarf_data *ddata, } unit_buf.name = ".debug_info"; - unit_buf.start = ddata->dwarf_info; + unit_buf.start = ddata->dwarf_sections.data[DEBUG_INFO]; unit_buf.buf = u->unit_data; unit_buf.left = u->unit_data_len; unit_buf.is_bigendian = ddata->is_bigendian; @@ -2748,8 +3911,10 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, *found = 1; /* Find an address range that includes PC. */ - entry = (unit_addrs*)bsearch (&pc, ddata->addrs, ddata->addrs_count, - sizeof (struct unit_addrs), unit_addrs_search); + entry = (ddata->addrs_count == 0 + ? NULL + : (struct unit_addrs*)bsearch (&pc, ddata->addrs, ddata->addrs_count, + sizeof (struct unit_addrs), unit_addrs_search)); if (entry == NULL) { @@ -2782,6 +3947,9 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, && pc >= (entry - 1)->low && pc < (entry - 1)->high) { + if (state->threaded) + lines = (struct line *) backtrace_atomic_load_pointer (&u->lines); + if (lines != (struct line *) (uintptr_t) -1) break; @@ -2791,6 +3959,9 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, lines = u->lines; } + if (state->threaded) + lines = backtrace_atomic_load_pointer (&u->lines); + new_data = 0; if (lines == NULL) { @@ -2808,7 +3979,12 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, { struct function_vector *pfvec; - pfvec = &ddata->fvec; + /* If not threaded, reuse DDATA->FVEC for better memory + consumption. */ + if (state->threaded) + pfvec = NULL; + else + pfvec = &ddata->fvec; read_function_info (state, ddata, &lhdr, error_callback, data, entry->u, pfvec, &function_addrs, &function_addrs_count); @@ -2823,10 +3999,21 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, write the lines field last, so that the acquire-loads above ensure that the other fields are set. */ + if (!state->threaded) + { u->lines_count = count; u->function_addrs = function_addrs; u->function_addrs_count = function_addrs_count; u->lines = lines; + } + else + { + backtrace_atomic_store_size_t (&u->lines_count, count); + backtrace_atomic_store_pointer (&u->function_addrs, function_addrs); + backtrace_atomic_store_size_t (&u->function_addrs_count, + function_addrs_count); + backtrace_atomic_store_pointer (&u->lines, lines); + } } /* Now all fields of U have been initialized. */ @@ -2855,22 +4042,22 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, if (entry->u->abs_filename == NULL) { - const char *filename2; + const char *filename; - filename2 = entry->u->filename; - if (filename2 != NULL - && !IS_ABSOLUTE_PATH (filename2) + filename = entry->u->filename; + if (filename != NULL + && !IS_ABSOLUTE_PATH (filename) && entry->u->comp_dir != NULL) { - size_t filename2_len; + size_t filename_len; const char *dir; size_t dir_len; char *s; - filename2_len = strlen (filename2); + filename_len = strlen (filename); dir = entry->u->comp_dir; dir_len = strlen (dir); - s = (char *) backtrace_alloc (state, dir_len + filename2_len + 2, + s = (char *) backtrace_alloc (state, dir_len + filename_len + 2, error_callback, data); if (s == NULL) { @@ -2880,10 +4067,10 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, memcpy (s, dir, dir_len); /* FIXME: Should use backslash if DOS file system. */ s[dir_len] = '/'; - memcpy (s + dir_len + 1, filename2, filename2_len + 1); - filename2 = s; + memcpy (s + dir_len + 1, filename, filename_len + 1); + filename = s; } - entry->u->abs_filename = filename2; + entry->u->abs_filename = filename; } return callback (data, pc, entry->u->abs_filename, 0, NULL); @@ -2937,6 +4124,8 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, int found; int ret; + if (!state->threaded) + { for (ddata = (struct dwarf_data *) state->fileline_data; ddata != NULL; ddata = ddata->next) @@ -2946,6 +4135,26 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, if (ret != 0 || found) return ret; } + } + else + { + struct dwarf_data **pp; + + pp = (struct dwarf_data **) (void *) &state->fileline_data; + while (1) + { + ddata = backtrace_atomic_load_pointer (pp); + if (ddata == NULL) + break; + + ret = dwarf_lookup_pc (state, ddata, pc, callback, error_callback, + data, &found); + if (ret != 0 || found) + return ret; + + pp = &ddata->next; + } + } /* FIXME: See if any libraries have been dlopen'ed. */ @@ -2958,37 +4167,36 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, static struct dwarf_data * build_dwarf_data (struct backtrace_state *state, uintptr_t base_address, - const unsigned char *dwarf_info, - size_t dwarf_info_size, - const unsigned char *dwarf_line, - size_t dwarf_line_size, - const unsigned char *dwarf_abbrev, - size_t dwarf_abbrev_size, - const unsigned char *dwarf_ranges, - size_t dwarf_ranges_size, - const unsigned char *dwarf_str, - size_t dwarf_str_size, + const struct dwarf_sections *dwarf_sections, int is_bigendian, + struct dwarf_data *altlink, backtrace_error_callback error_callback, void *data) { struct unit_addrs_vector addrs_vec; struct unit_addrs *addrs; size_t addrs_count; + struct unit_vector units_vec; + struct unit **units; + size_t units_count; struct dwarf_data *fdata; - if (!build_address_map (state, base_address, dwarf_info, dwarf_info_size, - dwarf_abbrev, dwarf_abbrev_size, dwarf_ranges, - dwarf_ranges_size, dwarf_str, dwarf_str_size, - is_bigendian, error_callback, data, &addrs_vec)) + if (!build_address_map (state, base_address, dwarf_sections, is_bigendian, + altlink, error_callback, data, &addrs_vec, + &units_vec)) return NULL; if (!backtrace_vector_release (state, &addrs_vec.vec, error_callback, data)) return NULL; + if (!backtrace_vector_release (state, &units_vec.vec, error_callback, data)) + return NULL; addrs = (struct unit_addrs *) addrs_vec.vec.base; + units = (struct unit **) units_vec.vec.base; addrs_count = addrs_vec.count; + units_count = units_vec.count; backtrace_qsort (addrs, addrs_count, sizeof (struct unit_addrs), unit_addrs_compare); + /* No qsort for units required, already sorted. */ fdata = ((struct dwarf_data *) backtrace_alloc (state, sizeof (struct dwarf_data), @@ -2997,17 +4205,13 @@ build_dwarf_data (struct backtrace_state *state, return NULL; fdata->next = NULL; + fdata->altlink = altlink; fdata->base_address = base_address; fdata->addrs = addrs; fdata->addrs_count = addrs_count; - fdata->dwarf_info = dwarf_info; - fdata->dwarf_info_size = dwarf_info_size; - fdata->dwarf_line = dwarf_line; - fdata->dwarf_line_size = dwarf_line_size; - fdata->dwarf_ranges = dwarf_ranges; - fdata->dwarf_ranges_size = dwarf_ranges_size; - fdata->dwarf_str = dwarf_str; - fdata->dwarf_str_size = dwarf_str_size; + fdata->units = units; + fdata->units_count = units_count; + fdata->dwarf_sections = *dwarf_sections; fdata->is_bigendian = is_bigendian; memset (&fdata->fvec, 0, sizeof fdata->fvec); @@ -3021,30 +4225,25 @@ build_dwarf_data (struct backtrace_state *state, int backtrace_dwarf_add (struct backtrace_state *state, uintptr_t base_address, - const unsigned char *dwarf_info, - size_t dwarf_info_size, - const unsigned char *dwarf_line, - size_t dwarf_line_size, - const unsigned char *dwarf_abbrev, - size_t dwarf_abbrev_size, - const unsigned char *dwarf_ranges, - size_t dwarf_ranges_size, - const unsigned char *dwarf_str, - size_t dwarf_str_size, + const struct dwarf_sections *dwarf_sections, int is_bigendian, + struct dwarf_data *fileline_altlink, backtrace_error_callback error_callback, - void *data, fileline *fileline_fn) + void *data, fileline *fileline_fn, + struct dwarf_data **fileline_entry) { struct dwarf_data *fdata; - fdata = build_dwarf_data (state, base_address, dwarf_info, dwarf_info_size, - dwarf_line, dwarf_line_size, dwarf_abbrev, - dwarf_abbrev_size, dwarf_ranges, dwarf_ranges_size, - dwarf_str, dwarf_str_size, is_bigendian, - error_callback, data); + fdata = build_dwarf_data (state, base_address, dwarf_sections, is_bigendian, + fileline_altlink, error_callback, data); if (fdata == NULL) return 0; + if (fileline_entry != NULL) + *fileline_entry = fdata; + + if (!state->threaded) + { struct dwarf_data **pp; for (pp = (struct dwarf_data **) (void *) &state->fileline_data; @@ -3052,6 +4251,31 @@ backtrace_dwarf_add (struct backtrace_state *state, pp = &(*pp)->next) ; *pp = fdata; + } + else + { + while (1) + { + struct dwarf_data **pp; + + pp = (struct dwarf_data **) (void *) &state->fileline_data; + + while (1) + { + struct dwarf_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, fdata)) + break; + } + } *fileline_fn = dwarf_fileline; diff --git a/libbacktrace/elf.cpp b/libbacktrace/elf.cpp index 8eba0245..700be542 100644 --- a/libbacktrace/elf.cpp +++ b/libbacktrace/elf.cpp @@ -1,5 +1,5 @@ /* elf.c -- Get debug data from an ELF file for backtraces. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -57,15 +57,13 @@ POSSIBILITY OF SUCH DAMAGE. */ #endif #ifndef __GNUC__ -#ifndef __builtin_prefetch -# define __builtin_prefetch(p, r, l) -#endif +#define __builtin_prefetch(p, r, l) #ifndef unlikely -# define unlikely(x) (x) +#define unlikely(x) (x) #endif #else #ifndef unlikely -# define unlikely(x) __builtin_expect(!!(x), 0) +#define unlikely(x) __builtin_expect(!!(x), 0) #endif #endif @@ -120,6 +118,29 @@ xreadlink (const char *path ATTRIBUTE_UNUSED, char *buf ATTRIBUTE_UNUSED, #endif +#ifndef HAVE_DL_ITERATE_PHDR + +/* Dummy version of dl_iterate_phdr for systems that don't have it. */ + +#define dl_phdr_info x_dl_phdr_info +#define dl_iterate_phdr x_dl_iterate_phdr + +struct dl_phdr_info +{ + uintptr_t dlpi_addr; + const char *dlpi_name; +}; + +static int +dl_iterate_phdr (int (*callback) (struct dl_phdr_info *, + size_t, void *) ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) +{ + return 0; +} + +#endif /* ! defined (HAVE_DL_ITERATE_PHDR) */ + /* The configure script must tell us whether we are 32-bit or 64-bit ELF. We could make this code test and support either possibility, but there is no point. This code only works for the currently @@ -323,41 +344,19 @@ typedef struct #define ELFCOMPRESS_ZLIB 1 -/* An index of ELF sections we care about. */ +/* Names of sections, indexed by enum dwarf_section in internal.h. */ -enum debug_section -{ - DEBUG_INFO, - DEBUG_LINE, - DEBUG_ABBREV, - DEBUG_RANGES, - DEBUG_STR, - - /* The old style compressed sections. This list must correspond to - the list of normal debug sections. */ - ZDEBUG_INFO, - ZDEBUG_LINE, - ZDEBUG_ABBREV, - ZDEBUG_RANGES, - ZDEBUG_STR, - - DEBUG_MAX -}; - -/* Names of sections, indexed by enum elf_section. */ - -static const char * const debug_section_names[DEBUG_MAX] = +static const char * const dwarf_section_names[DEBUG_MAX] = { ".debug_info", ".debug_line", ".debug_abbrev", ".debug_ranges", ".debug_str", - ".zdebug_info", - ".zdebug_line", - ".zdebug_abbrev", - ".zdebug_ranges", - ".zdebug_str" + ".debug_addr", + ".debug_str_offsets", + ".debug_line_str", + ".debug_rnglists" }; /* Information we gather for the sections we care about. */ @@ -659,6 +658,8 @@ static void elf_add_syminfo_data (struct backtrace_state *state, struct elf_syminfo_data *edata) { + if (!state->threaded) + { struct elf_syminfo_data **pp; for (pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; @@ -666,6 +667,31 @@ elf_add_syminfo_data (struct backtrace_state *state, pp = &(*pp)->next) ; *pp = edata; + } + else + { + while (1) + { + struct elf_syminfo_data **pp; + + pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; + + while (1) + { + struct elf_syminfo_data *p; + + p = backtrace_atomic_load_pointer (pp); + + if (p == NULL) + break; + + pp = &p->next; + } + + if (__sync_bool_compare_and_swap (pp, NULL, edata)) + break; + } + } } /* Return the symbol name and value for an ADDR. */ @@ -679,6 +705,8 @@ elf_syminfo (struct backtrace_state *state, uintptr_t addr, struct elf_syminfo_data *edata; struct elf_symbol *sym = NULL; + if (!state->threaded) + { for (edata = (struct elf_syminfo_data *) state->syminfo_data; edata != NULL; edata = edata->next) @@ -689,6 +717,27 @@ elf_syminfo (struct backtrace_state *state, uintptr_t addr, if (sym != NULL) break; } + } + else + { + struct elf_syminfo_data **pp; + + pp = (struct elf_syminfo_data **) (void *) &state->syminfo_data; + while (1) + { + edata = backtrace_atomic_load_pointer (pp); + if (edata == NULL) + break; + + sym = ((struct elf_symbol *) + bsearch (&addr, edata->symbols, edata->count, + sizeof (struct elf_symbol), elf_symbol_search)); + if (sym != NULL) + break; + + pp = &edata->next; + } + } if (sym == NULL) callback (data, addr, NULL, 0, 0); @@ -745,6 +794,8 @@ elf_readlink (struct backtrace_state *state, const char *filename, } } +#define SYSTEM_BUILD_ID_DIR "/usr/lib/debug/.build-id/" + /* Open a separate debug info file, using the build ID to find it. Returns an open file descriptor, or -1. @@ -757,7 +808,7 @@ elf_open_debugfile_by_buildid (struct backtrace_state *state, backtrace_error_callback error_callback, void *data) { - const char * const prefix = "/usr/lib/debug/.build-id/"; + const char * const prefix = SYSTEM_BUILD_ID_DIR; const size_t prefix_len = strlen (prefix); const char * const suffix = ".debug"; const size_t suffix_len = strlen (suffix); @@ -2574,7 +2625,8 @@ static int elf_add (struct backtrace_state *state, const char *filename, int descriptor, uintptr_t base_address, backtrace_error_callback error_callback, void *data, fileline *fileline_fn, int *found_sym, int *found_dwarf, - int exe, int debuginfo) + struct dwarf_data **fileline_entry, int exe, int debuginfo, + const char *with_buildid_data, uint32_t with_buildid_size) { struct backtrace_view ehdr_view; b_elf_ehdr ehdr; @@ -2594,6 +2646,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, unsigned int dynsym_shndx; unsigned int i; struct debug_section_info sections[DEBUG_MAX]; + struct debug_section_info zsections[DEBUG_MAX]; struct backtrace_view symtab_view; int symtab_view_valid; struct backtrace_view strtab_view; @@ -2606,13 +2659,23 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, int debuglink_view_valid; const char *debuglink_name; uint32_t debuglink_crc; + struct backtrace_view debugaltlink_view; + int debugaltlink_view_valid; + const char *debugaltlink_name; + const char *debugaltlink_buildid_data; + uint32_t debugaltlink_buildid_size; off_t min_offset; off_t max_offset; + off_t debug_size; struct backtrace_view debug_view; int debug_view_valid; unsigned int using_debug_view; uint16_t *zdebug_table; + struct backtrace_view split_debug_view[DEBUG_MAX]; + unsigned char split_debug_view_valid[DEBUG_MAX]; struct elf_ppc64_opd_data opd_data, *opd; + struct dwarf_sections dwarf_sections; + struct dwarf_data *fileline_altlink = NULL; if (!debuginfo) { @@ -2630,7 +2693,12 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, debuglink_view_valid = 0; debuglink_name = NULL; debuglink_crc = 0; + debugaltlink_view_valid = 0; + debugaltlink_name = NULL; + debugaltlink_buildid_data = NULL; + debugaltlink_buildid_size = 0; debug_view_valid = 0; + memset (&split_debug_view_valid[0], 0, sizeof split_debug_view_valid); opd = NULL; if (!backtrace_get_view (state, descriptor, 0, sizeof ehdr, error_callback, @@ -2739,7 +2807,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, shstr_size = shstrhdr->sh_size; shstr_off = shstrhdr->sh_offset; - if (!backtrace_get_view (state, descriptor, shstr_off, shstr_size, + if (!backtrace_get_view (state, descriptor, shstr_off, shstrhdr->sh_size, error_callback, data, &names_view)) goto fail; names_view_valid = 1; @@ -2749,6 +2817,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, dynsym_shndx = 0; memset (sections, 0, sizeof sections); + memset (zsections, 0, sizeof zsections); /* Look for the symbol table. */ for (i = 1; i < shnum; ++i) @@ -2776,7 +2845,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, for (j = 0; j < (int) DEBUG_MAX; ++j) { - if (strcmp (name, debug_section_names[j]) == 0) + if (strcmp (name, dwarf_section_names[j]) == 0) { sections[j].offset = shdr->sh_offset; sections[j].size = shdr->sh_size; @@ -2785,10 +2854,23 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, } } + if (name[0] == '.' && name[1] == 'z') + { + for (j = 0; j < (int) DEBUG_MAX; ++j) + { + if (strcmp (name + 2, dwarf_section_names[j] + 1) == 0) + { + zsections[j].offset = shdr->sh_offset; + zsections[j].size = shdr->sh_size; + break; + } + } + } + /* Read the build ID if present. This could check for any SHT_NOTE section with the right note name and type, but gdb looks for a specific section name. */ - if (!debuginfo + if ((!debuginfo || with_buildid_data != NULL) && !buildid_view_valid && strcmp (name, ".note.gnu.build-id") == 0) { @@ -2804,11 +2886,20 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, if (note->type == NT_GNU_BUILD_ID && note->namesz == 4 && strncmp (note->name, "GNU", 4) == 0 - && shdr->sh_size < 12 + ((note->namesz + 3) & ~ 3) + note->descsz) + && shdr->sh_size <= 12 + ((note->namesz + 3) & ~ 3) + note->descsz) { buildid_data = ¬e->name[0] + ((note->namesz + 3) & ~ 3); buildid_size = note->descsz; } + + if (with_buildid_size != 0) + { + if (buildid_size != with_buildid_size) + goto fail; + + if (memcmp (buildid_data, with_buildid_data, buildid_size) != 0) + goto fail; + } } /* Read the debuglink file if present. */ @@ -2835,6 +2926,32 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, } } + if (!debugaltlink_view_valid + && strcmp (name, ".gnu_debugaltlink") == 0) + { + const char *debugaltlink_data; + size_t debugaltlink_name_len; + + if (!backtrace_get_view (state, descriptor, shdr->sh_offset, + shdr->sh_size, error_callback, data, + &debugaltlink_view)) + goto fail; + + debugaltlink_view_valid = 1; + debugaltlink_data = (const char *) debugaltlink_view.data; + debugaltlink_name = debugaltlink_data; + debugaltlink_name_len = strnlen (debugaltlink_data, shdr->sh_size); + if (debugaltlink_name_len < shdr->sh_size) + { + /* Include terminating zero. */ + debugaltlink_name_len += 1; + + debugaltlink_buildid_data + = debugaltlink_data + debugaltlink_name_len; + debugaltlink_buildid_size = shdr->sh_size - debugaltlink_name_len; + } + } + /* Read the .opd section on PowerPC64 ELFv1. */ if (ehdr.e_machine == EM_PPC64 && (ehdr.e_flags & EF_PPC64_ABI) < 2 @@ -2902,6 +3019,7 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, string table permanently. */ backtrace_release_view (state, &symtab_view, error_callback, data); symtab_view_valid = 0; + strtab_view_valid = 0; *found_sym = 1; @@ -2929,8 +3047,12 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, if (debuglink_view_valid) backtrace_release_view (state, &debuglink_view, error_callback, data); - ret = elf_add (state, NULL, d, base_address, error_callback, data, - fileline_fn, found_sym, found_dwarf, 0, 1); + if (debugaltlink_view_valid) + backtrace_release_view (state, &debugaltlink_view, error_callback, + data); + ret = elf_add (state, "", d, base_address, error_callback, data, + fileline_fn, found_sym, found_dwarf, NULL, 0, 1, NULL, + 0); if (ret < 0) backtrace_close (d, error_callback, data); else @@ -2964,8 +3086,12 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, backtrace_release_view (state, &debuglink_view, error_callback, data); - ret = elf_add (state, NULL, d, base_address, error_callback, data, - fileline_fn, found_sym, found_dwarf, 0, 1); + if (debugaltlink_view_valid) + backtrace_release_view (state, &debugaltlink_view, error_callback, + data); + ret = elf_add (state, "", d, base_address, error_callback, data, + fileline_fn, found_sym, found_dwarf, NULL, 0, 1, NULL, + 0); if (ret < 0) backtrace_close (d, error_callback, data); else @@ -2980,22 +3106,66 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, debuglink_view_valid = 0; } + if (debugaltlink_name != NULL) + { + int d; + + d = elf_open_debugfile_by_debuglink (state, filename, debugaltlink_name, + 0, error_callback, data); + if (d >= 0) + { + int ret; + + ret = elf_add (state, filename, d, base_address, error_callback, data, + fileline_fn, found_sym, found_dwarf, &fileline_altlink, + 0, 1, debugaltlink_buildid_data, + debugaltlink_buildid_size); + backtrace_release_view (state, &debugaltlink_view, error_callback, + data); + debugaltlink_view_valid = 0; + if (ret < 0) + { + backtrace_close (d, error_callback, data); + return ret; + } + } + } + + if (debugaltlink_view_valid) + { + backtrace_release_view (state, &debugaltlink_view, error_callback, data); + debugaltlink_view_valid = 0; + } + /* Read all the debug sections in a single view, since they are - probably adjacent in the file. We never release this view. */ + probably adjacent in the file. If any of sections are + uncompressed, we never release this view. */ min_offset = 0; max_offset = 0; + debug_size = 0; for (i = 0; i < (int) DEBUG_MAX; ++i) { off_t end; - if (sections[i].size == 0) - continue; - if (min_offset == 0 || sections[i].offset < min_offset) - min_offset = sections[i].offset; - end = sections[i].offset + sections[i].size; - if (end > max_offset) - max_offset = end; + if (sections[i].size != 0) + { + if (min_offset == 0 || sections[i].offset < min_offset) + min_offset = sections[i].offset; + end = sections[i].offset + sections[i].size; + if (end > max_offset) + max_offset = end; + debug_size += sections[i].size; + } + if (zsections[i].size != 0) + { + if (min_offset == 0 || zsections[i].offset < min_offset) + min_offset = zsections[i].offset; + end = zsections[i].offset + zsections[i].size; + if (end > max_offset) + max_offset = end; + debug_size += zsections[i].size; + } } if (min_offset == 0 || max_offset == 0) { @@ -3004,11 +3174,45 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, return 1; } - if (!backtrace_get_view (state, descriptor, min_offset, - max_offset - min_offset, - error_callback, data, &debug_view)) - goto fail; - debug_view_valid = 1; + /* If the total debug section size is large, assume that there are + gaps between the sections, and read them individually. */ + + if (max_offset - min_offset < 0x20000000 + || max_offset - min_offset < debug_size + 0x10000) + { + if (!backtrace_get_view (state, descriptor, min_offset, + max_offset - min_offset, + error_callback, data, &debug_view)) + goto fail; + debug_view_valid = 1; + } + else + { + memset (&split_debug_view[0], 0, sizeof split_debug_view); + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + struct debug_section_info *dsec; + + if (sections[i].size != 0) + dsec = §ions[i]; + else if (zsections[i].size != 0) + dsec = &zsections[i]; + else + continue; + + if (!backtrace_get_view (state, descriptor, dsec->offset, dsec->size, + error_callback, data, &split_debug_view[i])) + goto fail; + split_debug_view_valid[i] = 1; + + if (sections[i].size != 0) + sections[i].data = ((const unsigned char *) + split_debug_view[i].data); + else + zsections[i].data = ((const unsigned char *) + split_debug_view[i].data); + } + } /* We've read all we need from the executable. */ if (!backtrace_close (descriptor, error_callback, data)) @@ -3016,28 +3220,33 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, descriptor = -1; using_debug_view = 0; - for (i = 0; i < (int) DEBUG_MAX; ++i) + if (debug_view_valid) { - if (sections[i].size == 0) - sections[i].data = NULL; - else + for (i = 0; i < (int) DEBUG_MAX; ++i) { - sections[i].data = ((const unsigned char *) debug_view.data - + (sections[i].offset - min_offset)); - if (i < ZDEBUG_INFO) - ++using_debug_view; + if (sections[i].size == 0) + sections[i].data = NULL; + else + { + sections[i].data = ((const unsigned char *) debug_view.data + + (sections[i].offset - min_offset)); + ++using_debug_view; + } + + if (zsections[i].size == 0) + zsections[i].data = NULL; + else + zsections[i].data = ((const unsigned char *) debug_view.data + + (zsections[i].offset - min_offset)); } } /* Uncompress the old format (--compress-debug-sections=zlib-gnu). */ zdebug_table = NULL; - for (i = 0; i < ZDEBUG_INFO; ++i) + for (i = 0; i < (int) DEBUG_MAX; ++i) { - struct debug_section_info *pz; - - pz = §ions[i + ZDEBUG_INFO - DEBUG_INFO]; - if (sections[i].size == 0 && pz->size > 0) + if (sections[i].size == 0 && zsections[i].size > 0) { unsigned char *uncompressed_data; size_t uncompressed_size; @@ -3053,19 +3262,27 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, uncompressed_data = NULL; uncompressed_size = 0; - if (!elf_uncompress_zdebug (state, pz->data, pz->size, zdebug_table, + if (!elf_uncompress_zdebug (state, zsections[i].data, + zsections[i].size, zdebug_table, error_callback, data, &uncompressed_data, &uncompressed_size)) goto fail; sections[i].data = uncompressed_data; sections[i].size = uncompressed_size; sections[i].compressed = 0; + + if (split_debug_view_valid[i]) + { + backtrace_release_view (state, &split_debug_view[i], + error_callback, data); + split_debug_view_valid[i] = 0; + } } } /* Uncompress the official ELF format (--compress-debug-sections=zlib-gabi). */ - for (i = 0; i < ZDEBUG_INFO; ++i) + for (i = 0; i < (int) DEBUG_MAX; ++i) { unsigned char *uncompressed_data; size_t uncompressed_size; @@ -3092,7 +3309,14 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, sections[i].size = uncompressed_size; sections[i].compressed = 0; - --using_debug_view; + if (debug_view_valid) + --using_debug_view; + else if (split_debug_view_valid[i]) + { + backtrace_release_view (state, &split_debug_view[i], + error_callback, data); + split_debug_view_valid[i] = 0; + } } if (zdebug_table != NULL) @@ -3105,19 +3329,17 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, debug_view_valid = 0; } - if (!backtrace_dwarf_add (state, base_address, - sections[DEBUG_INFO].data, - sections[DEBUG_INFO].size, - sections[DEBUG_LINE].data, - sections[DEBUG_LINE].size, - sections[DEBUG_ABBREV].data, - sections[DEBUG_ABBREV].size, - sections[DEBUG_RANGES].data, - sections[DEBUG_RANGES].size, - sections[DEBUG_STR].data, - sections[DEBUG_STR].size, + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + dwarf_sections.data[i] = sections[i].data; + dwarf_sections.size[i] = sections[i].size; + } + + if (!backtrace_dwarf_add (state, base_address, &dwarf_sections, ehdr.e_ident[EI_DATA] == ELFDATA2MSB, - error_callback, data, fileline_fn)) + fileline_altlink, + error_callback, data, fileline_fn, + fileline_entry)) goto fail; *found_dwarf = 1; @@ -3135,10 +3357,18 @@ elf_add (struct backtrace_state *state, const char *filename, int descriptor, backtrace_release_view (state, &strtab_view, error_callback, data); if (debuglink_view_valid) backtrace_release_view (state, &debuglink_view, error_callback, data); + if (debugaltlink_view_valid) + backtrace_release_view (state, &debugaltlink_view, error_callback, data); if (buildid_view_valid) backtrace_release_view (state, &buildid_view, error_callback, data); if (debug_view_valid) backtrace_release_view (state, &debug_view, error_callback, data); + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (split_debug_view_valid[i]) + backtrace_release_view (state, &split_debug_view[i], + error_callback, data); + } if (opd) backtrace_release_view (state, &opd->view, error_callback, data); if (descriptor != -1) @@ -3205,7 +3435,7 @@ phdr_callback (struct dl_phdr_info *info, size_t size ATTRIBUTE_UNUSED, if (elf_add (pd->state, filename, descriptor, info->dlpi_addr, pd->error_callback, pd->data, &elf_fileline_fn, pd->found_sym, - &found_dwarf, 0, 0)) + &found_dwarf, NULL, 0, 0, NULL, 0)) { if (found_dwarf) { @@ -3233,7 +3463,8 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, struct phdr_data pd; ret = elf_add (state, filename, descriptor, 0, error_callback, data, - &elf_fileline_fn, &found_sym, &found_dwarf, 1, 0); + &elf_fileline_fn, &found_sym, &found_dwarf, NULL, 1, 0, NULL, + 0); if (!ret) return 0; @@ -3248,12 +3479,26 @@ backtrace_initialize (struct backtrace_state *state, const char *filename, dl_iterate_phdr (phdr_callback, (void *) &pd); + if (!state->threaded) + { if (found_sym) state->syminfo_fn = elf_syminfo; else if (state->syminfo_fn == NULL) state->syminfo_fn = elf_nosyms; + } + else + { + if (found_sym) + backtrace_atomic_store_pointer (&state->syminfo_fn, &elf_syminfo); + else + (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, + elf_nosyms); + } + if (!state->threaded) *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); if (*fileline_fn == NULL || *fileline_fn == elf_nodebug) *fileline_fn = elf_fileline_fn; diff --git a/libbacktrace/fileline.cpp b/libbacktrace/fileline.cpp index d75fd49e..3c3103d1 100644 --- a/libbacktrace/fileline.cpp +++ b/libbacktrace/fileline.cpp @@ -1,5 +1,5 @@ /* fileline.c -- Get file and line number information in a backtrace. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -34,72 +34,97 @@ POSSIBILITY OF SUCH DAMAGE. */ #include #include -#include #include #include #include #include -#ifdef BSD -# include -# include -#endif - -#ifdef __APPLE__ -# include +#if defined (HAVE_KERN_PROC_ARGS) || defined (HAVE_KERN_PROC) +#include #endif #include "backtrace.hpp" #include "internal.hpp" -#ifdef BSD -# if !defined HAVE_GETEXECNAME && defined KERN_PROC_PATHNAME -# define HAVE_GETEXECNAME -static char execname[PATH_MAX + 1]; -static const char * -getexecname(void) -{ - size_t path_len = sizeof(execname); - int mib[] = { - CTL_KERN, -#if defined(__NetBSD__) - KERN_PROC_ARGS, - -1, - KERN_PROC_PATHNAME, -#else - KERN_PROC, - KERN_PROC_PATHNAME, - -1, -#endif - }; - u_int miblen = sizeof(mib) / sizeof(mib[0]); - int rc = sysctl(mib, miblen, execname, &path_len, NULL, 0); - return rc ? NULL : execname; -} -# endif -#endif - -#if !defined HAVE_GETEXECNAME && defined __APPLE__ -# define HAVE_GETEXECNAME -static char execname[PATH_MAX + 1]; -static const char * -getexecname(void) -{ - uint32_t size = sizeof(execname); - if (_NSGetExecutablePath(execname, &size) == 0) - return execname; - else - return NULL; -} -#endif - #ifndef HAVE_GETEXECNAME -# define getexecname() NULL +#define getexecname() NULL #endif namespace tracy { +#if !defined (HAVE_KERN_PROC_ARGS) && !defined (HAVE_KERN_PROC) + +#define sysctl_exec_name1(state, error_callback, data) NULL +#define sysctl_exec_name2(state, error_callback, data) NULL + +#else /* defined (HAVE_KERN_PROC_ARGS) || |defined (HAVE_KERN_PROC) */ + +static char * +sysctl_exec_name (struct backtrace_state *state, + int mib0, int mib1, int mib2, int mib3, + backtrace_error_callback error_callback, void *data) +{ + int mib[4]; + size_t len; + char *name; + size_t rlen; + + mib[0] = mib0; + mib[1] = mib1; + mib[2] = mib2; + mib[3] = mib3; + + if (sysctl (mib, 4, NULL, &len, NULL, 0) < 0) + return NULL; + name = (char *) backtrace_alloc (state, len, error_callback, data); + if (name == NULL) + return NULL; + rlen = len; + if (sysctl (mib, 4, name, &rlen, NULL, 0) < 0) + { + backtrace_free (state, name, len, error_callback, data); + return NULL; + } + return name; +} + +#ifdef HAVE_KERN_PROC_ARGS + +static char * +sysctl_exec_name1 (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + /* This variant is used on NetBSD. */ + return sysctl_exec_name (state, CTL_KERN, KERN_PROC_ARGS, -1, + KERN_PROC_PATHNAME, error_callback, data); +} + +#else + +#define sysctl_exec_name1(state, error_callback, data) NULL + +#endif + +#ifdef HAVE_KERN_PROC + +static char * +sysctl_exec_name2 (struct backtrace_state *state, + backtrace_error_callback error_callback, void *data) +{ + /* This variant is used on FreeBSD. */ + return sysctl_exec_name (state, CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1, + error_callback, data); +} + +#else + +#define sysctl_exec_name2(state, error_callback, data) NULL + +#endif + +#endif /* defined (HAVE_KERN_PROC_ARGS) || |defined (HAVE_KERN_PROC) */ + /* Initialize the fileline information from the executable. Returns 1 on success, 0 on failure. */ @@ -115,7 +140,10 @@ fileline_initialize (struct backtrace_state *state, const char *filename; char buf[64]; - failed = state->fileline_initialization_failed; + if (!state->threaded) + failed = state->fileline_initialization_failed; + else + failed = backtrace_atomic_load_int (&state->fileline_initialization_failed); if (failed) { @@ -123,7 +151,10 @@ fileline_initialize (struct backtrace_state *state, return 0; } - fileline_fn = state->fileline_fn; + if (!state->threaded) + fileline_fn = state->fileline_fn; + else + fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); if (fileline_fn != NULL) return 1; @@ -131,7 +162,7 @@ fileline_initialize (struct backtrace_state *state, descriptor = -1; called_error_callback = 0; - for (pass = 0; pass < 5; ++pass) + for (pass = 0; pass < 7; ++pass) { int does_not_exist; @@ -154,6 +185,12 @@ fileline_initialize (struct backtrace_state *state, (long) getpid ()); filename = buf; break; + case 5: + filename = sysctl_exec_name1 (state, error_callback, data); + break; + case 6: + filename = sysctl_exec_name2 (state, error_callback, data); + break; default: abort (); } @@ -194,12 +231,23 @@ fileline_initialize (struct backtrace_state *state, } if (failed) - { - state->fileline_initialization_failed = 1; - return 0; - } + { + if (!state->threaded) + state->fileline_initialization_failed = 1; + else + backtrace_atomic_store_int (&state->fileline_initialization_failed, 1); + return 0; + } - state->fileline_fn = fileline_fn; + if (!state->threaded) + state->fileline_fn = fileline_fn; + else + { + backtrace_atomic_store_pointer (&state->fileline_fn, fileline_fn); + + /* Note that if two threads initialize at once, one of the data + sets may be leaked. */ + } return 1; } diff --git a/libbacktrace/filenames.hpp b/libbacktrace/filenames.hpp new file mode 100644 index 00000000..aa7bd7ad --- /dev/null +++ b/libbacktrace/filenames.hpp @@ -0,0 +1,52 @@ +/* btest.c -- Filename header for libbacktrace library + Copyright (C) 2012-2018 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. + +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. */ + +#ifndef GCC_VERSION +# define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) +#endif + +#if (GCC_VERSION < 2007) +# define __attribute__(x) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#if defined(__MSDOS__) || defined(_WIN32) || defined(__OS2__) || defined (__CYGWIN__) +# define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') +# define HAS_DRIVE_SPEC(f) ((f)[0] != '\0' && (f)[1] == ':') +# define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || HAS_DRIVE_SPEC(f)) +#else +# define IS_DIR_SEPARATOR(c) ((c) == '/') +# define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0])) +#endif diff --git a/libbacktrace/internal.hpp b/libbacktrace/internal.hpp index ad7607c7..45f11831 100644 --- a/libbacktrace/internal.hpp +++ b/libbacktrace/internal.hpp @@ -1,5 +1,5 @@ /* internal.h -- Internal header file for stack backtrace library. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -56,6 +56,8 @@ POSSIBILITY OF SUCH DAMAGE. */ # endif #endif +#ifndef HAVE_SYNC_FUNCTIONS + /* Define out the sync functions. These should never be called if they are not available. */ @@ -63,6 +65,37 @@ POSSIBILITY OF SUCH DAMAGE. */ #define __sync_lock_test_and_set(A, B) (abort(), 0) #define __sync_lock_release(A) abort() +#endif /* !defined (HAVE_SYNC_FUNCTIONS) */ + +#ifdef HAVE_ATOMIC_FUNCTIONS + +/* We have the atomic builtin functions. */ + +#define backtrace_atomic_load_pointer(p) \ + __atomic_load_n ((p), __ATOMIC_ACQUIRE) +#define backtrace_atomic_load_int(p) \ + __atomic_load_n ((p), __ATOMIC_ACQUIRE) +#define backtrace_atomic_store_pointer(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) +#define backtrace_atomic_store_size_t(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) +#define backtrace_atomic_store_int(p, v) \ + __atomic_store_n ((p), (v), __ATOMIC_RELEASE) + +#else /* !defined (HAVE_ATOMIC_FUNCTIONS) */ +#ifdef HAVE_SYNC_FUNCTIONS + +/* We have the sync functions but not the atomic functions. Define + the atomic ones in terms of the sync ones. */ + +extern void *backtrace_atomic_load_pointer (void *); +extern int backtrace_atomic_load_int (int *); +extern void backtrace_atomic_store_pointer (void *, void *); +extern void backtrace_atomic_store_size_t (size_t *, size_t); +extern void backtrace_atomic_store_int (int *, int); + +#else /* !defined (HAVE_SYNC_FUNCTIONS) */ + /* We have neither the sync nor the atomic functions. These will never be called. */ @@ -72,6 +105,9 @@ POSSIBILITY OF SUCH DAMAGE. */ #define backtrace_atomic_store_size_t(p, v) abort() #define backtrace_atomic_store_int(p, v) abort() +#endif /* !defined (HAVE_SYNC_FUNCTIONS) */ +#endif /* !defined (HAVE_ATOMIC_FUNCTIONS) */ + namespace tracy { @@ -146,7 +182,7 @@ struct backtrace_view /* Create a view of SIZE bytes from DESCRIPTOR at OFFSET. Store the result in *VIEW. Returns 1 on success, 0 on error. */ extern int backtrace_get_view (struct backtrace_state *state, int descriptor, - off_t offset, size_t size, + off_t offset, uint64_t size, backtrace_error_callback error_callback, void *data, struct backtrace_view *view); @@ -224,6 +260,18 @@ extern int backtrace_vector_release (struct backtrace_state *state, backtrace_error_callback error_callback, void *data); +/* Free the space managed by VEC. This will reset VEC. */ + +static inline void +backtrace_vector_free (struct backtrace_state *state, + struct backtrace_vector *vec, + backtrace_error_callback error_callback, void *data) +{ + vec->alc += vec->size; + vec->size = 0; + backtrace_vector_release (state, vec, error_callback, data); +} + /* Read initial debug data from a descriptor, and set the fileline_data, syminfo_fn, and syminfo_data fields of STATE. Return the fileln_fn field in *FILELN_FN--this is done this way so @@ -241,23 +289,54 @@ extern int backtrace_initialize (struct backtrace_state *state, void *data, fileline *fileline_fn); +/* An enum for the DWARF sections we care about. */ + +enum dwarf_section +{ + DEBUG_INFO, + DEBUG_LINE, + DEBUG_ABBREV, + DEBUG_RANGES, + DEBUG_STR, + DEBUG_ADDR, + DEBUG_STR_OFFSETS, + DEBUG_LINE_STR, + DEBUG_RNGLISTS, + + DEBUG_MAX +}; + +/* Data for the DWARF sections we care about. */ + +struct dwarf_sections +{ + const unsigned char *data[DEBUG_MAX]; + size_t size[DEBUG_MAX]; +}; + +/* DWARF data read from a file, used for .gnu_debugaltlink. */ + +struct dwarf_data; + /* Add file/line information for a DWARF module. */ extern int backtrace_dwarf_add (struct backtrace_state *state, uintptr_t base_address, - const unsigned char* dwarf_info, - size_t dwarf_info_size, - const unsigned char *dwarf_line, - size_t dwarf_line_size, - const unsigned char *dwarf_abbrev, - size_t dwarf_abbrev_size, - const unsigned char *dwarf_ranges, - size_t dwarf_range_size, - const unsigned char *dwarf_str, - size_t dwarf_str_size, + const struct dwarf_sections *dwarf_sections, int is_bigendian, + struct dwarf_data *fileline_altlink, backtrace_error_callback error_callback, - void *data, fileline *fileline_fn); + void *data, fileline *fileline_fn, + struct dwarf_data **fileline_entry); + +/* A test-only hook for elf_uncompress_zdebug. */ + +extern int backtrace_uncompress_zdebug (struct backtrace_state *, + const unsigned char *compressed, + size_t compressed_size, + backtrace_error_callback, void *data, + unsigned char **uncompressed, + size_t *uncompressed_size); } diff --git a/libbacktrace/macho.cpp b/libbacktrace/macho.cpp index 1f84508c..6c254354 100644 --- a/libbacktrace/macho.cpp +++ b/libbacktrace/macho.cpp @@ -1,9 +1,6 @@ -/* 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 +/* elf.c -- Get debug data from a Mach-O file for backtraces. + Copyright (C) 2020 Free Software Foundation, Inc. + Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -35,43 +32,14 @@ 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 + +#ifdef HAVE_MACH_O_DYLD_H +#include +#endif #include "backtrace.hpp" #include "internal.hpp" @@ -79,1173 +47,405 @@ POSSIBILITY OF SUCH DAMAGE. */ namespace tracy { -struct macho_commands_view +/* Mach-O file header for a 32-bit executable. */ + +struct macho_header_32 { - struct backtrace_view view; - uint32_t commands_count; - uint32_t commands_total_size; - int bytes_swapped; - size_t base_offset; + uint32_t magic; /* Magic number (MACH_O_MAGIC_32) */ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t filetype; /* Type of file (object, executable) */ + uint32_t ncmds; /* Number of load commands */ + uint32_t sizeofcmds; /* Total size of load commands */ + uint32_t flags; /* Flags for special features */ }; -enum debug_section +/* Mach-O file header for a 64-bit executable. */ + +struct macho_header_64 { - DEBUG_INFO, - DEBUG_LINE, - DEBUG_ABBREV, - DEBUG_RANGES, - DEBUG_STR, - DEBUG_MAX + uint32_t magic; /* Magic number (MACH_O_MAGIC_64) */ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t filetype; /* Type of file (object, executable) */ + uint32_t ncmds; /* Number of load commands */ + uint32_t sizeofcmds; /* Total size of load commands */ + uint32_t flags; /* Flags for special features */ + uint32_t reserved; /* Reserved */ }; -static const char *const debug_section_names[DEBUG_MAX] = - { - "__debug_info", - "__debug_line", - "__debug_abbrev", - "__debug_ranges", - "__debug_str" - }; +/* Mach-O file header for a fat executable. */ -struct found_dwarf_section +struct macho_header_fat { - uint32_t file_offset; - uintptr_t file_size; - const unsigned char *data; + uint32_t magic; /* Magic number (MACH_O_MH_MAGIC_FAT) */ + uint32_t nfat_arch; /* Number of components */ }; -/* 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. */ +/* Values for the header magic field. */ + +#define MACH_O_MH_MAGIC_32 0xfeedface +#define MACH_O_MH_MAGIC_64 0xfeedfacf +#define MACH_O_MH_MAGIC_FAT 0xcafebabe +#define MACH_O_MH_CIGAM_FAT 0xbebafeca + +/* Value for the header filetype field. */ + +#define MACH_O_MH_EXECUTE 0x02 +#define MACH_O_MH_DYLIB 0x06 +#define MACH_O_MH_DSYM 0x0a + +/* A component of a fat file. A fat file starts with a + macho_header_fat followed by nfat_arch instances of this + struct. */ + +struct macho_fat_arch +{ + uint32_t cputype; /* CPU type */ + uint32_t cpusubtype; /* CPU subtype */ + uint32_t offset; /* File offset of this entry */ + uint32_t size; /* Size of this entry */ + uint32_t align; /* Alignment of this entry */ +}; + +/* Values for the fat_arch cputype field (and the header cputype + field). */ + +#define MACH_O_CPU_ARCH_ABI64 0x01000000 + +#define MACH_O_CPU_TYPE_X86 7 +#define MACH_O_CPU_TYPE_ARM 12 + +#define MACH_O_CPU_TYPE_X86_64 (MACH_O_CPU_TYPE_X86 | MACH_O_CPU_ARCH_ABI64) +#define MACH_O_CPU_TYPE_ARM64 (MACH_O_CPU_TYPE_ARM | MACH_O_CPU_ARCH_ABI64) + +/* The header of a load command. */ + +struct macho_load_command +{ + uint32_t cmd; /* The type of load command */ + uint32_t cmdsize; /* Size in bytes of the entire command */ +}; + +/* Values for the load_command cmd field. */ + +#define MACH_O_LC_SEGMENT 0x01 +#define MACH_O_LC_SYMTAB 0x02 +#define MACH_O_LC_SEGMENT_64 0x19 +#define MACH_O_LC_UUID 0x1b + +/* The length of a section of segment name. */ + +#define MACH_O_NAMELEN (16) + +/* LC_SEGMENT load command. */ + +struct macho_segment_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + char segname[MACH_O_NAMELEN]; /* Segment name */ + uint32_t vmaddr; /* Virtual memory address */ + uint32_t vmsize; /* Virtual memory size */ + uint32_t fileoff; /* Offset of data to be mapped */ + uint32_t filesize; /* Size of data in file */ + uint32_t maxprot; /* Maximum permitted virtual protection */ + uint32_t initprot; /* Initial virtual memory protection */ + uint32_t nsects; /* Number of sections in this segment */ + uint32_t flags; /* Flags */ +}; + +/* LC_SEGMENT_64 load command. */ + +struct macho_segment_64_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + char segname[MACH_O_NAMELEN]; /* Segment name */ + uint64_t vmaddr; /* Virtual memory address */ + uint64_t vmsize; /* Virtual memory size */ + uint64_t fileoff; /* Offset of data to be mapped */ + uint64_t filesize; /* Size of data in file */ + uint32_t maxprot; /* Maximum permitted virtual protection */ + uint32_t initprot; /* Initial virtual memory protection */ + uint32_t nsects; /* Number of sections in this segment */ + uint32_t flags; /* Flags */ +}; + +/* LC_SYMTAB load command. */ + +struct macho_symtab_command +{ + uint32_t cmd; /* The type of load command (LC_SEGMENT) */ + uint32_t cmdsize; /* Size in bytes of the entire command */ + uint32_t symoff; /* File offset of symbol table */ + uint32_t nsyms; /* Number of symbols */ + uint32_t stroff; /* File offset of string table */ + uint32_t strsize; /* String table size */ +}; + +/* The length of a Mach-O uuid. */ + +#define MACH_O_UUID_LEN (16) + +/* LC_UUID load command. */ + +struct macho_uuid_command +{ + uint32_t cmd; /* Type of load command (LC_UUID) */ + uint32_t cmdsize; /* Size in bytes of command */ + unsigned char uuid[MACH_O_UUID_LEN]; /* UUID */ +}; + +/* 32-bit section header within a LC_SEGMENT segment. */ + +struct macho_section +{ + char sectname[MACH_O_NAMELEN]; /* Section name */ + char segment[MACH_O_NAMELEN]; /* Segment of this section */ + uint32_t addr; /* Address in memory */ + uint32_t size; /* Section size */ + uint32_t offset; /* File offset */ + uint32_t align; /* Log2 of section alignment */ + uint32_t reloff; /* File offset of relocations */ + uint32_t nreloc; /* Number of relocs for this section */ + uint32_t flags; /* Flags */ + uint32_t reserved1; + uint32_t reserved2; +}; + +/* 64-bit section header within a LC_SEGMENT_64 segment. */ + +struct macho_section_64 +{ + char sectname[MACH_O_NAMELEN]; /* Section name */ + char segment[MACH_O_NAMELEN]; /* Segment of this section */ + uint64_t addr; /* Address in memory */ + uint64_t size; /* Section size */ + uint32_t offset; /* File offset */ + uint32_t align; /* Log2 of section alignment */ + uint32_t reloff; /* File offset of section relocations */ + uint32_t nreloc; /* Number of relocs for this section */ + uint32_t flags; /* Flags */ + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; +}; + +/* 32-bit symbol data. */ + +struct macho_nlist +{ + uint32_t n_strx; /* Index of name in string table */ + uint8_t n_type; /* Type flag */ + uint8_t n_sect; /* Section number */ + uint16_t n_desc; /* Stabs description field */ + uint32_t n_value; /* Value */ +}; + +/* 64-bit symbol data. */ + +struct macho_nlist_64 +{ + uint32_t n_strx; /* Index of name in string table */ + uint8_t n_type; /* Type flag */ + uint8_t n_sect; /* Section number */ + uint16_t n_desc; /* Stabs description field */ + uint64_t n_value; /* Value */ +}; + +/* Value found in nlist n_type field. */ + +#define MACH_O_N_EXT 0x01 /* Extern symbol */ +#define MACH_O_N_ABS 0x02 /* Absolute symbol */ +#define MACH_O_N_SECT 0x0e /* Defined in section */ + +#define MACH_O_N_TYPE 0x0e /* Mask for type bits */ +#define MACH_O_N_STAB 0xe0 /* Stabs debugging symbol */ + +/* Information we keep for a Mach-O symbol. */ + struct macho_symbol { - uintptr_t addr; - size_t size; - const char *name; + const char *name; /* Symbol name */ + uintptr_t address; /* Symbol address */ }; +/* Information to pass to macho_syminfo. */ + 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; + struct macho_syminfo_data *next; /* Next module */ + struct macho_symbol *symbols; /* Symbols sorted by address */ + size_t count; /* Number of symbols */ }; -uint16_t -macho_file_to_host_u16 (int file_bytes_swapped, uint16_t input) +/* Names of sections, indexed by enum dwarf_section in internal.h. */ + +static const char * const dwarf_section_names[DEBUG_MAX] = { - if (file_bytes_swapped) - return (input >> 8) | (input << 8); - else - return input; -} + "__debug_info", + "__debug_line", + "__debug_abbrev", + "__debug_ranges", + "__debug_str", + "", /* DEBUG_ADDR */ + "__debug_str_offs", + "", /* DEBUG_LINE_STR */ + "__debug_rnglists" +}; -uint32_t -macho_file_to_host_u32 (int file_bytes_swapped, uint32_t input) +/* Forward declaration. */ + +static int macho_add (struct backtrace_state *, const char *, int, off_t, + const unsigned char *, uintptr_t, int, + backtrace_error_callback, void *, fileline *, int *); + +/* A dummy callback function used when we can't find any debug info. */ + +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) { - 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); + error_callback (data, "no debug info in Mach-O executable", -1); 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) +/* A dummy callback function used when we can't find a symbol + table. */ + +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) { - 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; - } + error_callback (data, "no symbol table in Mach-O executable", -1); } -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; +/* Add a single DWARF section to DWARF_SECTIONS, if we need the + section. Returns 1 on success, 0 on failure. */ - if (left->addr > right->addr) - return 1; - else if (left->addr < right->addr) +static int +macho_add_dwarf_section (struct backtrace_state *state, int descriptor, + const char *sectname, uint32_t offset, uint64_t size, + backtrace_error_callback error_callback, void *data, + struct dwarf_sections *dwarf_sections) +{ + int i; + + for (i = 0; i < (int) DEBUG_MAX; ++i) + { + if (dwarf_section_names[i][0] != '\0' + && strncmp (sectname, dwarf_section_names[i], MACH_O_NAMELEN) == 0) + { + struct backtrace_view section_view; + + /* FIXME: Perhaps it would be better to try to use a single + view to read all the DWARF data, as we try to do for + ELF. */ + + if (!backtrace_get_view (state, descriptor, offset, size, + error_callback, data, §ion_view)) + return 0; + dwarf_sections->data[i] = (const unsigned char *) section_view.data; + dwarf_sections->size[i] = size; + break; + } + } + return 1; +} + +/* Collect DWARF sections from a DWARF segment. Returns 1 on success, + 0 on failure. */ + +static int +macho_add_dwarf_segment (struct backtrace_state *state, int descriptor, + off_t offset, unsigned int cmd, const char *psecs, + size_t sizesecs, unsigned int nsects, + backtrace_error_callback error_callback, void *data, + struct dwarf_sections *dwarf_sections) +{ + size_t sec_header_size; + size_t secoffset; + unsigned int i; + + switch (cmd) + { + case MACH_O_LC_SEGMENT: + sec_header_size = sizeof (struct macho_section); + break; + case MACH_O_LC_SEGMENT_64: + sec_header_size = sizeof (struct macho_section_64); + break; + default: + abort (); + } + + secoffset = 0; + for (i = 0; i < nsects; ++i) + { + if (secoffset + sec_header_size > sizesecs) + { + error_callback (data, "section overflow withing segment", 0); + return 0; + } + + switch (cmd) + { + case MACH_O_LC_SEGMENT: + { + struct macho_section section; + + memcpy (§ion, psecs + secoffset, sizeof section); + macho_add_dwarf_section (state, descriptor, section.sectname, + offset + section.offset, section.size, + error_callback, data, dwarf_sections); + } + break; + + case MACH_O_LC_SEGMENT_64: + { + struct macho_section_64 section; + + memcpy (§ion, psecs + secoffset, sizeof section); + macho_add_dwarf_section (state, descriptor, section.sectname, + offset + section.offset, section.size, + error_callback, data, dwarf_sections); + } + break; + + default: + abort (); + } + + secoffset += sec_header_size; + } + + return 1; +} + +/* Compare struct macho_symbol for qsort. */ + +static int +macho_symbol_compare (const void *v1, const void *v2) +{ + const struct macho_symbol *m1 = (const struct macho_symbol *) v1; + const struct macho_symbol *m2 = (const struct macho_symbol *) v2; + + if (m1->address < m2->address) return -1; + else if (m1->address > m2->address) + 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; -} +/* Compare an address against a macho_symbol for bsearch. We allocate + one extra entry in the array so that this can safely look at the + next entry. */ static int macho_symbol_search (const void *vkey, const void *ventry) @@ -1255,39 +455,259 @@ macho_symbol_search (const void *vkey, const void *ventry) uintptr_t addr; addr = *key; - if (addr < entry->addr) + if (addr < entry->address) return -1; - else if (addr >= entry->addr + entry->size) + else if (entry->name[0] == '\0' + && entry->address == ~(uintptr_t) 0) + return -1; + else if ((entry + 1)->name[0] == '\0' + && (entry + 1)->address == ~(uintptr_t) 0) + return -1; + else if (addr >= (entry + 1)->address) 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) +/* Return whether the symbol type field indicates a symbol table entry + that we care about: a function or data symbol. */ + +static int +macho_defined_symbol (uint8_t type) { - struct macho_syminfo_data *edata; - struct macho_symbol *sym = NULL; + if ((type & MACH_O_N_STAB) != 0) + return 0; + if ((type & MACH_O_N_EXT) != 0) + return 0; + switch (type & MACH_O_N_TYPE) + { + case MACH_O_N_ABS: + return 1; + case MACH_O_N_SECT: + return 1; + default: + return 0; + } +} + +/* Add symbol table information for a Mach-O file. */ + +static int +macho_add_symtab (struct backtrace_state *state, int descriptor, + uintptr_t base_address, int is_64, + off_t symoff, unsigned int nsyms, off_t stroff, + unsigned int strsize, + backtrace_error_callback error_callback, void *data) +{ + size_t symsize; + struct backtrace_view sym_view; + int sym_view_valid; + struct backtrace_view str_view; + int str_view_valid; + size_t ndefs; + size_t symtaboff; + unsigned int i; + size_t macho_symbol_size; + struct macho_symbol *macho_symbols; + unsigned int j; + struct macho_syminfo_data *sdata; + + sym_view_valid = 0; + str_view_valid = 0; + macho_symbol_size = 0; + macho_symbols = NULL; + + if (is_64) + symsize = sizeof (struct macho_nlist_64); + else + symsize = sizeof (struct macho_nlist); + + if (!backtrace_get_view (state, descriptor, symoff, nsyms * symsize, + error_callback, data, &sym_view)) + goto fail; + sym_view_valid = 1; + + if (!backtrace_get_view (state, descriptor, stroff, strsize, + error_callback, data, &str_view)) + return 0; + str_view_valid = 1; + + ndefs = 0; + symtaboff = 0; + for (i = 0; i < nsyms; ++i, symtaboff += symsize) + { + if (is_64) + { + struct macho_nlist_64 nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (macho_defined_symbol (nlist.n_type)) + ++ndefs; + } + else + { + struct macho_nlist nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (macho_defined_symbol (nlist.n_type)) + ++ndefs; + } + } + + /* Add 1 to ndefs to make room for a sentinel. */ + macho_symbol_size = (ndefs + 1) * sizeof (struct macho_symbol); + macho_symbols = ((struct macho_symbol *) + backtrace_alloc (state, macho_symbol_size, error_callback, + data)); + if (macho_symbols == NULL) + goto fail; + + j = 0; + symtaboff = 0; + for (i = 0; i < nsyms; ++i, symtaboff += symsize) + { + uint32_t strx; + uint64_t value; + const char *name; + + strx = 0; + value = 0; + if (is_64) + { + struct macho_nlist_64 nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (!macho_defined_symbol (nlist.n_type)) + continue; + + strx = nlist.n_strx; + value = nlist.n_value; + } + else + { + struct macho_nlist nlist; + + memcpy (&nlist, (const char *) sym_view.data + symtaboff, + sizeof nlist); + if (!macho_defined_symbol (nlist.n_type)) + continue; + + strx = nlist.n_strx; + value = nlist.n_value; + } + + if (strx >= strsize) + { + error_callback (data, "symbol string index out of range", 0); + goto fail; + } + + name = (const char *) str_view.data + strx; + if (name[0] == '_') + ++name; + macho_symbols[j].name = name; + macho_symbols[j].address = value + base_address; + ++j; + } + + sdata = ((struct macho_syminfo_data *) + backtrace_alloc (state, sizeof *sdata, error_callback, data)); + if (sdata == NULL) + goto fail; + + /* We need to keep the string table since it holds the names, but we + can release the symbol table. */ + + backtrace_release_view (state, &sym_view, error_callback, data); + sym_view_valid = 0; + str_view_valid = 0; + + /* Add a trailing sentinel symbol. */ + macho_symbols[j].name = ""; + macho_symbols[j].address = ~(uintptr_t) 0; + + backtrace_qsort (macho_symbols, ndefs + 1, sizeof (struct macho_symbol), + macho_symbol_compare); + + sdata->next = NULL; + sdata->symbols = macho_symbols; + sdata->count = ndefs; 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; - } - } + struct macho_syminfo_data **pp; + + for (pp = (struct macho_syminfo_data **) (void *) &state->syminfo_data; + *pp != NULL; + pp = &(*pp)->next) + ; + *pp = sdata; + } + 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, sdata)) + break; + } + } + + return 1; + + fail: + if (macho_symbols != NULL) + backtrace_free (state, macho_symbols, macho_symbol_size, + error_callback, data); + if (sym_view_valid) + backtrace_release_view (state, &sym_view, error_callback, data); + if (str_view_valid) + backtrace_release_view (state, &str_view, error_callback, data); + return 0; +} + +/* Return the symbol name and value for an ADDR. */ + +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 *sdata; + struct macho_symbol *sym; + + sym = NULL; + if (!state->threaded) + { + for (sdata = (struct macho_syminfo_data *) state->syminfo_data; + sdata != NULL; + sdata = sdata->next) + { + sym = ((struct macho_symbol *) + bsearch (&addr, sdata->symbols, sdata->count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; + } } else { @@ -1295,129 +715,600 @@ macho_syminfo (struct backtrace_state *state, 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; + { + sdata = backtrace_atomic_load_pointer (pp); + if (sdata == 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; - } + sym = ((struct macho_symbol *) + bsearch (&addr, sdata->symbols, sdata->count, + sizeof (struct macho_symbol), macho_symbol_search)); + if (sym != NULL) + break; - pp = &edata->next; - } + pp = &sdata->next; + } } if (sym == NULL) callback (data, addr, NULL, 0, 0); else - callback (data, addr, sym->name, sym->addr, sym->size); + callback (data, addr, sym->name, sym->address, 0); } +/* Look through a fat file to find the relevant executable. Returns 1 + on success, 0 on failure (in both cases descriptor is closed). */ 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) +macho_add_fat (struct backtrace_state *state, const char *filename, + int descriptor, int swapped, off_t offset, + const unsigned char *match_uuid, uintptr_t base_address, + int skip_symtab, uint32_t nfat_arch, + backtrace_error_callback error_callback, void *data, + fileline *fileline_fn, int *found_sym) { - error_callback (data, "no debug info in Mach-O executable", -1); + int arch_view_valid; + unsigned int cputype; + struct backtrace_view arch_view; + size_t archoffset; + unsigned int i; + + arch_view_valid = 0; + +#if defined (__x86_64__) + cputype = MACH_O_CPU_TYPE_X86_64; +#elif defined (__i386__) + cputype = MACH_O_CPU_TYPE_X86; +#elif defined (__aarch64__) + cputype = MACH_O_CPU_TYPE_ARM64; +#elif defined (__arm__) + cputype = MACH_O_CPU_TYPE_ARM; +#else + error_callback (data, "unknown Mach-O architecture", 0); + goto fail; +#endif + + if (!backtrace_get_view (state, descriptor, offset, + nfat_arch * sizeof (struct macho_fat_arch), + error_callback, data, &arch_view)) + goto fail; + + archoffset = 0; + for (i = 0; i < nfat_arch; ++i) + { + struct macho_fat_arch fat_arch; + uint32_t fcputype; + + memcpy (&fat_arch, + ((const char *) arch_view.data + + i * sizeof (struct macho_fat_arch)), + sizeof fat_arch); + + fcputype = fat_arch.cputype; + if (swapped) + fcputype = __builtin_bswap32 (fcputype); + + if (fcputype == cputype) + { + uint32_t foffset; + + /* FIXME: What about cpusubtype? */ + foffset = fat_arch.offset; + if (swapped) + foffset = __builtin_bswap32 (foffset); + backtrace_release_view (state, &arch_view, error_callback, data); + return macho_add (state, filename, descriptor, foffset, match_uuid, + base_address, skip_symtab, error_callback, data, + fileline_fn, found_sym); + } + + archoffset += sizeof (struct macho_fat_arch); + } + + error_callback (data, "could not find executable in fat file", 0); + + fail: + if (arch_view_valid) + backtrace_release_view (state, &arch_view, error_callback, data); + if (descriptor != -1) + backtrace_close (descriptor, error_callback, data); 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) +/* Look for the dsym file for FILENAME. This is called if FILENAME + does not have debug info or a symbol table. Returns 1 on success, + 0 on failure. */ + +static int +macho_add_dsym (struct backtrace_state *state, const char *filename, + uintptr_t base_address, const unsigned char *uuid, + backtrace_error_callback error_callback, void *data, + fileline* fileline_fn) { - error_callback (data, "no symbol table in Mach-O executable", -1); + const char *p; + const char *dirname; + char *diralc; + size_t dirnamelen; + const char *basename; + size_t basenamelen; + const char *dsymsuffixdir; + size_t dsymsuffixdirlen; + size_t dsymlen; + char *dsym; + char *ps; + int d; + int does_not_exist; + int dummy_found_sym; + + diralc = NULL; + dirnamelen = 0; + dsym = NULL; + dsymlen = 0; + + p = strrchr (filename, '/'); + if (p == NULL) + { + dirname = "."; + dirnamelen = 1; + basename = filename; + basenamelen = strlen (basename); + diralc = NULL; + } + else + { + dirnamelen = p - filename; + diralc = backtrace_alloc (state, dirnamelen + 1, error_callback, data); + if (diralc == NULL) + goto fail; + memcpy (diralc, filename, dirnamelen); + diralc[dirnamelen] = '\0'; + dirname = diralc; + basename = p + 1; + basenamelen = strlen (basename); + } + + dsymsuffixdir = ".dSYM/Contents/Resources/DWARF/"; + dsymsuffixdirlen = strlen (dsymsuffixdir); + + dsymlen = (dirnamelen + + basenamelen + + dsymsuffixdirlen + + basenamelen + + 1); + dsym = backtrace_alloc (state, dsymlen, error_callback, data); + if (dsym == NULL) + goto fail; + + ps = dsym; + memcpy (ps, dirname, dirnamelen); + ps += dirnamelen; + *ps++ = '/'; + memcpy (ps, basename, basenamelen); + ps += basenamelen; + memcpy (ps, dsymsuffixdir, dsymsuffixdirlen); + ps += dsymsuffixdirlen; + memcpy (ps, basename, basenamelen); + ps += basenamelen; + *ps = '\0'; + + if (diralc != NULL) + { + backtrace_free (state, diralc, dirnamelen, error_callback, data); + diralc = NULL; + } + + d = backtrace_open (dsym, error_callback, data, &does_not_exist); + if (d < 0) + { + /* The file does not exist, so we can't read the debug info. + Just return success. */ + backtrace_free (state, dsym, dsymlen, error_callback, data); + return 1; + } + + if (!macho_add (state, dsym, d, 0, uuid, base_address, 1, + error_callback, data, fileline_fn, &dummy_found_sym)) + goto fail; + + backtrace_free (state, dsym, dsymlen, error_callback, data); + + return 1; + + fail: + if (dsym != NULL) + backtrace_free (state, dsym, dsymlen, error_callback, data); + if (diralc != NULL) + backtrace_free (state, diralc, dirnamelen, error_callback, data); + return 0; } +/* Add the backtrace data for a Macho-O file. Returns 1 on success, 0 + on failure (in both cases descriptor is closed). + + FILENAME: the name of the executable. + DESCRIPTOR: an open descriptor for the executable, closed here. + OFFSET: the offset within the file of this executable, for fat files. + MATCH_UUID: if not NULL, UUID that must match. + BASE_ADDRESS: the load address of the executable. + SKIP_SYMTAB: if non-zero, ignore the symbol table; used for dSYM files. + FILELINE_FN: set to the fileline function, by backtrace_dwarf_add. + FOUND_SYM: set to non-zero if we found the symbol table. +*/ + +static int +macho_add (struct backtrace_state *state, const char *filename, int descriptor, + off_t offset, const unsigned char *match_uuid, + uintptr_t base_address, int skip_symtab, + backtrace_error_callback error_callback, void *data, + fileline *fileline_fn, int *found_sym) +{ + struct backtrace_view header_view; + struct macho_header_32 header; + off_t hdroffset; + int is_64; + struct backtrace_view cmds_view; + int cmds_view_valid; + struct dwarf_sections dwarf_sections; + int have_dwarf; + unsigned char uuid[MACH_O_UUID_LEN]; + int have_uuid; + size_t cmdoffset; + unsigned int i; + + *found_sym = 0; + + cmds_view_valid = 0; + + /* The 32-bit and 64-bit file headers start out the same, so we can + just always read the 32-bit version. A fat header is shorter but + it will always be followed by data, so it's OK to read extra. */ + + if (!backtrace_get_view (state, descriptor, offset, + sizeof (struct macho_header_32), + error_callback, data, &header_view)) + goto fail; + + memcpy (&header, header_view.data, sizeof header); + + backtrace_release_view (state, &header_view, error_callback, data); + + switch (header.magic) + { + case MACH_O_MH_MAGIC_32: + is_64 = 0; + hdroffset = offset + sizeof (struct macho_header_32); + break; + case MACH_O_MH_MAGIC_64: + is_64 = 1; + hdroffset = offset + sizeof (struct macho_header_64); + break; + case MACH_O_MH_MAGIC_FAT: + { + struct macho_header_fat fat_header; + + hdroffset = offset + sizeof (struct macho_header_fat); + memcpy (&fat_header, &header, sizeof fat_header); + return macho_add_fat (state, filename, descriptor, 0, hdroffset, + match_uuid, base_address, skip_symtab, + fat_header.nfat_arch, error_callback, data, + fileline_fn, found_sym); + } + case MACH_O_MH_CIGAM_FAT: + { + struct macho_header_fat fat_header; + uint32_t nfat_arch; + + hdroffset = offset + sizeof (struct macho_header_fat); + memcpy (&fat_header, &header, sizeof fat_header); + nfat_arch = __builtin_bswap32 (fat_header.nfat_arch); + return macho_add_fat (state, filename, descriptor, 1, hdroffset, + match_uuid, base_address, skip_symtab, + nfat_arch, error_callback, data, + fileline_fn, found_sym); + } + default: + error_callback (data, "executable file is not in Mach-O format", 0); + goto fail; + } + + switch (header.filetype) + { + case MACH_O_MH_EXECUTE: + case MACH_O_MH_DYLIB: + case MACH_O_MH_DSYM: + break; + default: + error_callback (data, "executable file is not an executable", 0); + goto fail; + } + + if (!backtrace_get_view (state, descriptor, hdroffset, header.sizeofcmds, + error_callback, data, &cmds_view)) + goto fail; + cmds_view_valid = 1; + + memset (&dwarf_sections, 0, sizeof dwarf_sections); + have_dwarf = 0; + memset (&uuid, 0, sizeof uuid); + have_uuid = 0; + + cmdoffset = 0; + for (i = 0; i < header.ncmds; ++i) + { + const char *pcmd; + struct macho_load_command load_command; + + if (cmdoffset + sizeof load_command > header.sizeofcmds) + break; + + pcmd = (const char *) cmds_view.data + cmdoffset; + memcpy (&load_command, pcmd, sizeof load_command); + + switch (load_command.cmd) + { + case MACH_O_LC_SEGMENT: + { + struct macho_segment_command segcmd; + + memcpy (&segcmd, pcmd, sizeof segcmd); + if (memcmp (segcmd.segname, + "__DWARF\0\0\0\0\0\0\0\0\0", + MACH_O_NAMELEN) == 0) + { + if (!macho_add_dwarf_segment (state, descriptor, offset, + load_command.cmd, + pcmd + sizeof segcmd, + (load_command.cmdsize + - sizeof segcmd), + segcmd.nsects, error_callback, + data, &dwarf_sections)) + goto fail; + have_dwarf = 1; + } + } + break; + + case MACH_O_LC_SEGMENT_64: + { + struct macho_segment_64_command segcmd; + + memcpy (&segcmd, pcmd, sizeof segcmd); + if (memcmp (segcmd.segname, + "__DWARF\0\0\0\0\0\0\0\0\0", + MACH_O_NAMELEN) == 0) + { + if (!macho_add_dwarf_segment (state, descriptor, offset, + load_command.cmd, + pcmd + sizeof segcmd, + (load_command.cmdsize + - sizeof segcmd), + segcmd.nsects, error_callback, + data, &dwarf_sections)) + goto fail; + have_dwarf = 1; + } + } + break; + + case MACH_O_LC_SYMTAB: + if (!skip_symtab) + { + struct macho_symtab_command symcmd; + + memcpy (&symcmd, pcmd, sizeof symcmd); + if (!macho_add_symtab (state, descriptor, base_address, is_64, + offset + symcmd.symoff, symcmd.nsyms, + offset + symcmd.stroff, symcmd.strsize, + error_callback, data)) + goto fail; + + *found_sym = 1; + } + break; + + case MACH_O_LC_UUID: + { + struct macho_uuid_command uuidcmd; + + memcpy (&uuidcmd, pcmd, sizeof uuidcmd); + memcpy (&uuid[0], &uuidcmd.uuid[0], MACH_O_UUID_LEN); + have_uuid = 1; + } + break; + + default: + break; + } + + cmdoffset += load_command.cmdsize; + } + + if (!backtrace_close (descriptor, error_callback, data)) + goto fail; + descriptor = -1; + + backtrace_release_view (state, &cmds_view, error_callback, data); + cmds_view_valid = 0; + + if (match_uuid != NULL) + { + /* If we don't have a UUID, or it doesn't match, just ignore + this file. */ + if (!have_uuid + || memcmp (match_uuid, &uuid[0], MACH_O_UUID_LEN) != 0) + return 1; + } + + if (have_dwarf) + { + int is_big_endian; + + is_big_endian = 0; +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + is_big_endian = 1; +#endif +#endif + + if (!backtrace_dwarf_add (state, base_address, &dwarf_sections, + is_big_endian, NULL, error_callback, data, + fileline_fn, NULL)) + goto fail; + } + + if (!have_dwarf && have_uuid) + { + if (!macho_add_dsym (state, filename, base_address, &uuid[0], + error_callback, data, fileline_fn)) + goto fail; + } + + return 1; + + fail: + if (cmds_view_valid) + backtrace_release_view (state, &cmds_view, error_callback, data); + if (descriptor != -1) + backtrace_close (descriptor, error_callback, data); + return 0; +} + +#ifdef HAVE_MACH_O_DYLD_H + +/* Initialize the backtrace data we need from a Mach-O executable + using the dyld support functions. This closes descriptor. */ + int backtrace_initialize (struct backtrace_state *state, const char *filename, - int descriptor, - backtrace_error_callback error_callback, - void *data, fileline *fileline_fn) + 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; + uint32_t c; + uint32_t i; + int closed_descriptor; + int found_sym; + fileline macho_fileline_fn; - // Add all loaded images - loaded_image_count = _dyld_image_count (); - for (i = 0; i < loaded_image_count; i++) + closed_descriptor = 0; + found_sym = 0; + macho_fileline_fn = macho_nodebug; + + c = _dyld_image_count (); + for (i = 0; i < c; ++i) { - int current_found_sym; - int current_found_dwarf; - int current_descriptor; - intptr_t current_vmslide; - const char *current_name; + uintptr_t base_address; + const char *name; + int d; + fileline mff; + int mfs; - current_vmslide = _dyld_get_image_vmaddr_slide (i); - current_name = _dyld_get_image_name (i); + name = _dyld_get_image_name (i); + if (name == NULL) + continue; - 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); + if (strcmp (name, filename) == 0 && !closed_descriptor) + { + d = descriptor; + closed_descriptor = 1; + } else - (void) __sync_bool_compare_and_swap (&state->syminfo_fn, NULL, - macho_nosyms); + { + int does_not_exist; + + d = backtrace_open (name, error_callback, data, &does_not_exist); + if (d < 0) + continue; + } + + base_address = _dyld_get_image_vmaddr_slide (i); + + mff = macho_nodebug; + if (!macho_add (state, name, d, 0, NULL, base_address, 0, + error_callback, data, &mff, &mfs)) + return 0; + + if (mff != macho_nodebug) + macho_fileline_fn = mff; + if (mfs) + found_sym = 1; } + if (!closed_descriptor) + backtrace_close (descriptor, error_callback, data); + if (!state->threaded) { - if (state->fileline_fn == NULL || state->fileline_fn == macho_nodebug) - *fileline_fn = macho_fileline_fn; + if (found_sym) + state->syminfo_fn = macho_syminfo; + else if (state->syminfo_fn == NULL) + state->syminfo_fn = macho_nosyms; } 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; + 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) + *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + + if (*fileline_fn == NULL || *fileline_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + return 1; } -} \ No newline at end of file +#else /* !defined (HAVE_MACH_O_DYLD_H) */ + +/* Initialize the backtrace data we need from a Mach-O executable + without using the dyld support functions. This closes + descriptor. */ + +int +backtrace_initialize (struct backtrace_state *state, const char *filename, + int descriptor, backtrace_error_callback error_callback, + void *data, fileline *fileline_fn) +{ + fileline macho_fileline_fn; + int found_sym; + + macho_fileline_fn = macho_nodebug; + if (!macho_add (state, filename, descriptor, 0, NULL, 0, 0, + error_callback, data, &macho_fileline_fn, &found_sym)) + return 0; + + 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) + *fileline_fn = state->fileline_fn; + else + *fileline_fn = backtrace_atomic_load_pointer (&state->fileline_fn); + + if (*fileline_fn == NULL || *fileline_fn == macho_nodebug) + *fileline_fn = macho_fileline_fn; + + return 1; +} + +#endif /* !defined (HAVE_MACH_O_DYLD_H) */ + +} diff --git a/libbacktrace/mmapio.cpp b/libbacktrace/mmapio.cpp index 655785f4..b452d359 100644 --- a/libbacktrace/mmapio.cpp +++ b/libbacktrace/mmapio.cpp @@ -1,5 +1,5 @@ /* mmapio.c -- File views using mmap. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ namespace tracy int backtrace_get_view (struct backtrace_state *state ATTRIBUTE_UNUSED, - int descriptor, off_t offset, size_t size, + int descriptor, off_t offset, uint64_t size, backtrace_error_callback error_callback, void *data, struct backtrace_view *view) { @@ -63,6 +63,12 @@ backtrace_get_view (struct backtrace_state *state ATTRIBUTE_UNUSED, off_t pageoff; void *map; + if ((uint64_t) (size_t) size != size) + { + error_callback (data, "file size too large", 0); + return 0; + } + pagesize = getpagesize (); inpage = offset % pagesize; pageoff = offset - inpage; @@ -92,7 +98,13 @@ backtrace_release_view (struct backtrace_state *state ATTRIBUTE_UNUSED, backtrace_error_callback error_callback, void *data) { - if (munmap (view->base, view->len) < 0) + union { + const void *cv; + void *v; + }; + + cv = view->base; + if (munmap (v, view->len) < 0) error_callback (data, "munmap", errno); } diff --git a/libbacktrace/posix.cpp b/libbacktrace/posix.cpp index c863c188..fd364b89 100644 --- a/libbacktrace/posix.cpp +++ b/libbacktrace/posix.cpp @@ -1,5 +1,5 @@ /* posix.c -- POSIX file I/O routines for the backtrace library. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without diff --git a/libbacktrace/sort.cpp b/libbacktrace/sort.cpp index 410326e4..5f1bfab8 100644 --- a/libbacktrace/sort.cpp +++ b/libbacktrace/sort.cpp @@ -1,5 +1,5 @@ /* sort.c -- Sort without allocating memory - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without diff --git a/libbacktrace/state.cpp b/libbacktrace/state.cpp index dcf1d8c2..edc31e07 100644 --- a/libbacktrace/state.cpp +++ b/libbacktrace/state.cpp @@ -1,5 +1,5 @@ /* state.c -- Create the backtrace state. - Copyright (C) 2012-2018 Free Software Foundation, Inc. + Copyright (C) 2012-2020 Free Software Foundation, Inc. Written by Ian Lance Taylor, Google. Redistribution and use in source and binary forms, with or without