| /* Get macro information. |
| Copyright (C) 2002-2009, 2014, 2017, 2018 Red Hat, Inc. |
| This file is part of elfutils. |
| |
| This file is free software; you can redistribute it and/or modify |
| it under the terms of either |
| |
| * the GNU Lesser General Public License as published by the Free |
| Software Foundation; either version 3 of the License, or (at |
| your option) any later version |
| |
| or |
| |
| * the GNU General Public License as published by the Free |
| Software Foundation; either version 2 of the License, or (at |
| your option) any later version |
| |
| or both in parallel, as here. |
| |
| elfutils is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received copies of the GNU General Public License and |
| the GNU Lesser General Public License along with this program. If |
| not, see <http://www.gnu.org/licenses/>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <assert.h> |
| #include <dwarf.h> |
| #include <search.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <libdwP.h> |
| |
| static int |
| get_offset_from (Dwarf_Die *die, int name, Dwarf_Word *retp) |
| { |
| /* Get the appropriate attribute. */ |
| Dwarf_Attribute attr; |
| if (INTUSE(dwarf_attr) (die, name, &attr) == NULL) |
| return -1; |
| |
| /* Offset into the corresponding section. */ |
| return INTUSE(dwarf_formudata) (&attr, retp); |
| } |
| |
| static int |
| macro_op_compare (const void *p1, const void *p2) |
| { |
| const Dwarf_Macro_Op_Table *t1 = (const Dwarf_Macro_Op_Table *) p1; |
| const Dwarf_Macro_Op_Table *t2 = (const Dwarf_Macro_Op_Table *) p2; |
| |
| if (t1->offset < t2->offset) |
| return -1; |
| if (t1->offset > t2->offset) |
| return 1; |
| |
| if (t1->sec_index < t2->sec_index) |
| return -1; |
| if (t1->sec_index > t2->sec_index) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void |
| build_table (Dwarf_Macro_Op_Table *table, |
| Dwarf_Macro_Op_Proto op_protos[static 255]) |
| { |
| unsigned ct = 0; |
| for (unsigned i = 1; i < 256; ++i) |
| if (op_protos[i - 1].forms != NULL) |
| table->table[table->opcodes[i - 1] = ct++] = op_protos[i - 1]; |
| else |
| table->opcodes[i - 1] = 0xff; |
| } |
| |
| #define MACRO_PROTO(NAME, ...) \ |
| Dwarf_Macro_Op_Proto NAME = ({ \ |
| static const uint8_t proto[] = {__VA_ARGS__}; \ |
| (Dwarf_Macro_Op_Proto) {sizeof proto, proto}; \ |
| }) |
| |
| enum { macinfo_data_size = offsetof (Dwarf_Macro_Op_Table, table[5]) }; |
| static unsigned char macinfo_data[macinfo_data_size] |
| __attribute__ ((aligned (__alignof (Dwarf_Macro_Op_Table)))); |
| |
| static __attribute__ ((constructor)) void |
| init_macinfo_table (void) |
| { |
| MACRO_PROTO (p_udata_str, DW_FORM_udata, DW_FORM_string); |
| MACRO_PROTO (p_udata_udata, DW_FORM_udata, DW_FORM_udata); |
| MACRO_PROTO (p_none); |
| |
| Dwarf_Macro_Op_Proto op_protos[255] = |
| { |
| [DW_MACINFO_define - 1] = p_udata_str, |
| [DW_MACINFO_undef - 1] = p_udata_str, |
| [DW_MACINFO_vendor_ext - 1] = p_udata_str, |
| [DW_MACINFO_start_file - 1] = p_udata_udata, |
| [DW_MACINFO_end_file - 1] = p_none, |
| /* If you are adding more elements to this array, increase |
| MACINFO_DATA_SIZE above. */ |
| }; |
| |
| Dwarf_Macro_Op_Table *macinfo_table = (void *) macinfo_data; |
| memset (macinfo_table, 0, sizeof macinfo_data); |
| build_table (macinfo_table, op_protos); |
| macinfo_table->sec_index = IDX_debug_macinfo; |
| } |
| |
| static Dwarf_Macro_Op_Table * |
| get_macinfo_table (Dwarf *dbg, Dwarf_Word macoff, Dwarf_Die *cudie) |
| { |
| assert (cudie != NULL); |
| |
| Dwarf_Attribute attr_mem, *attr |
| = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &attr_mem); |
| Dwarf_Off line_offset = (Dwarf_Off) -1; |
| if (attr != NULL) |
| if (unlikely (INTUSE(dwarf_formudata) (attr, &line_offset) != 0)) |
| return NULL; |
| |
| Dwarf_Macro_Op_Table *table = libdw_alloc (dbg, Dwarf_Macro_Op_Table, |
| macinfo_data_size, 1); |
| memcpy (table, macinfo_data, macinfo_data_size); |
| |
| table->offset = macoff; |
| table->sec_index = IDX_debug_macinfo; |
| table->line_offset = line_offset; |
| table->is_64bit = cudie->cu->address_size == 8; |
| table->comp_dir = __libdw_getcompdir (cudie); |
| |
| return table; |
| } |
| |
| static Dwarf_Macro_Op_Table * |
| get_table_for_offset (Dwarf *dbg, Dwarf_Word macoff, |
| const unsigned char *readp, |
| const unsigned char *const endp, |
| Dwarf_Die *cudie) |
| { |
| const unsigned char *startp = readp; |
| |
| /* Request at least 3 bytes for header. */ |
| if (readp + 3 > endp) |
| { |
| invalid_dwarf: |
| __libdw_seterrno (DWARF_E_INVALID_DWARF); |
| return NULL; |
| } |
| |
| uint16_t version = read_2ubyte_unaligned_inc (dbg, readp); |
| if (version != 4 && version != 5) |
| { |
| __libdw_seterrno (DWARF_E_INVALID_VERSION); |
| return NULL; |
| } |
| |
| uint8_t flags = *readp++; |
| bool is_64bit = (flags & 0x1) != 0; |
| |
| Dwarf_Off line_offset = (Dwarf_Off) -1; |
| if ((flags & 0x2) != 0) |
| { |
| line_offset = read_addr_unaligned_inc (is_64bit ? 8 : 4, dbg, readp); |
| if (readp > endp) |
| goto invalid_dwarf; |
| } |
| else if (cudie != NULL) |
| { |
| Dwarf_Attribute attr_mem, *attr |
| = INTUSE(dwarf_attr) (cudie, DW_AT_stmt_list, &attr_mem); |
| if (attr != NULL) |
| if (unlikely (INTUSE(dwarf_formudata) (attr, &line_offset) != 0)) |
| return NULL; |
| } |
| |
| /* """The macinfo entry types defined in this standard may, but |
| might not, be described in the table""". |
| |
| I.e. these may be present. It's tempting to simply skip them, |
| but it's probably more correct to tolerate that a producer tweaks |
| the way certain opcodes are encoded, for whatever reasons. */ |
| |
| MACRO_PROTO (p_udata_str, DW_FORM_udata, DW_FORM_string); |
| MACRO_PROTO (p_udata_strp, DW_FORM_udata, DW_FORM_strp); |
| MACRO_PROTO (p_udata_strsup, DW_FORM_udata, DW_FORM_strp_sup); |
| MACRO_PROTO (p_udata_strx, DW_FORM_udata, DW_FORM_strx); |
| MACRO_PROTO (p_udata_udata, DW_FORM_udata, DW_FORM_udata); |
| MACRO_PROTO (p_secoffset, DW_FORM_sec_offset); |
| MACRO_PROTO (p_none); |
| |
| Dwarf_Macro_Op_Proto op_protos[255] = |
| { |
| [DW_MACRO_define - 1] = p_udata_str, |
| [DW_MACRO_undef - 1] = p_udata_str, |
| [DW_MACRO_define_strp - 1] = p_udata_strp, |
| [DW_MACRO_undef_strp - 1] = p_udata_strp, |
| [DW_MACRO_start_file - 1] = p_udata_udata, |
| [DW_MACRO_end_file - 1] = p_none, |
| [DW_MACRO_import - 1] = p_secoffset, |
| [DW_MACRO_define_sup - 1] = p_udata_strsup, |
| [DW_MACRO_undef_sup - 1] = p_udata_strsup, |
| [DW_MACRO_import_sup - 1] = p_secoffset, /* XXX - but in sup!. */ |
| [DW_MACRO_define_strx - 1] = p_udata_strx, |
| [DW_MACRO_undef_strx - 1] = p_udata_strx, |
| }; |
| |
| if ((flags & 0x4) != 0) |
| { |
| unsigned count = *readp++; |
| for (unsigned i = 0; i < count; ++i) |
| { |
| unsigned opcode = *readp++; |
| |
| Dwarf_Macro_Op_Proto e; |
| if (readp >= endp) |
| goto invalid; |
| get_uleb128 (e.nforms, readp, endp); |
| e.forms = readp; |
| op_protos[opcode - 1] = e; |
| |
| readp += e.nforms; |
| if (readp > endp) |
| { |
| invalid: |
| __libdw_seterrno (DWARF_E_INVALID_DWARF); |
| return NULL; |
| } |
| } |
| } |
| |
| size_t ct = 0; |
| for (unsigned i = 1; i < 256; ++i) |
| if (op_protos[i - 1].forms != NULL) |
| ++ct; |
| |
| /* We support at most 0xfe opcodes defined in the table, as 0xff is |
| a value that means that given opcode is not stored at all. But |
| that should be fine, as opcode 0 is not allocated. */ |
| assert (ct < 0xff); |
| |
| size_t macop_table_size = offsetof (Dwarf_Macro_Op_Table, table[ct]); |
| |
| Dwarf_Macro_Op_Table *table = libdw_alloc (dbg, Dwarf_Macro_Op_Table, |
| macop_table_size, 1); |
| |
| *table = (Dwarf_Macro_Op_Table) { |
| .offset = macoff, |
| .sec_index = IDX_debug_macro, |
| .line_offset = line_offset, |
| .header_len = readp - startp, |
| .version = version, |
| .is_64bit = is_64bit, |
| |
| /* NULL if CUDIE is NULL or DW_AT_comp_dir is absent. */ |
| .comp_dir = __libdw_getcompdir (cudie), |
| }; |
| build_table (table, op_protos); |
| |
| return table; |
| } |
| |
| static Dwarf_Macro_Op_Table * |
| cache_op_table (Dwarf *dbg, int sec_index, Dwarf_Off macoff, |
| const unsigned char *startp, |
| const unsigned char *const endp, |
| Dwarf_Die *cudie) |
| { |
| Dwarf_Macro_Op_Table fake = { .offset = macoff, .sec_index = sec_index }; |
| Dwarf_Macro_Op_Table **found = tfind (&fake, &dbg->macro_ops, |
| macro_op_compare); |
| if (found != NULL) |
| return *found; |
| |
| Dwarf_Macro_Op_Table *table = sec_index == IDX_debug_macro |
| ? get_table_for_offset (dbg, macoff, startp, endp, cudie) |
| : get_macinfo_table (dbg, macoff, cudie); |
| |
| if (table == NULL) |
| return NULL; |
| |
| Dwarf_Macro_Op_Table **ret = tsearch (table, &dbg->macro_ops, |
| macro_op_compare); |
| if (unlikely (ret == NULL)) |
| { |
| __libdw_seterrno (DWARF_E_NOMEM); |
| return NULL; |
| } |
| |
| return *ret; |
| } |
| |
| static ptrdiff_t |
| read_macros (Dwarf *dbg, int sec_index, |
| Dwarf_Off macoff, int (*callback) (Dwarf_Macro *, void *), |
| void *arg, ptrdiff_t offset, bool accept_0xff, |
| Dwarf_Die *cudie) |
| { |
| Elf_Data *d = dbg->sectiondata[sec_index]; |
| if (unlikely (d == NULL || d->d_buf == NULL)) |
| { |
| __libdw_seterrno (DWARF_E_NO_ENTRY); |
| return -1; |
| } |
| |
| if (unlikely (macoff >= d->d_size)) |
| { |
| __libdw_seterrno (DWARF_E_INVALID_DWARF); |
| return -1; |
| } |
| |
| const unsigned char *const startp = d->d_buf + macoff; |
| const unsigned char *const endp = d->d_buf + d->d_size; |
| |
| Dwarf_Macro_Op_Table *table = cache_op_table (dbg, sec_index, macoff, |
| startp, endp, cudie); |
| if (table == NULL) |
| return -1; |
| |
| if (offset == 0) |
| offset = table->header_len; |
| |
| assert (offset >= 0); |
| assert (offset < endp - startp); |
| const unsigned char *readp = startp + offset; |
| |
| while (readp < endp) |
| { |
| unsigned int opcode = *readp++; |
| if (opcode == 0) |
| /* Nothing more to do. */ |
| return 0; |
| |
| if (unlikely (opcode == 0xff && ! accept_0xff)) |
| { |
| /* See comment below at dwarf_getmacros for explanation of |
| why we are doing this. */ |
| __libdw_seterrno (DWARF_E_INVALID_OPCODE); |
| return -1; |
| } |
| |
| unsigned int idx = table->opcodes[opcode - 1]; |
| if (idx == 0xff) |
| { |
| __libdw_seterrno (DWARF_E_INVALID_OPCODE); |
| return -1; |
| } |
| |
| Dwarf_Macro_Op_Proto *proto = &table->table[idx]; |
| |
| /* A fake CU with bare minimum data to fool dwarf_formX into |
| doing the right thing with the attributes that we put out. |
| We pretend it is the same version as the actual table. |
| Version 4 for the old GNU extension, version 5 for DWARF5. |
| To handle DW_FORM_strx[1234] we set the .str_offsets_base |
| from the given CU. |
| XXX We will need to deal with DW_MACRO_import_sup and change |
| out the dbg somehow for the DW_FORM_sec_offset to make sense. */ |
| Dwarf_CU fake_cu = { |
| .dbg = dbg, |
| .sec_idx = sec_index, |
| .version = table->version, |
| .offset_size = table->is_64bit ? 8 : 4, |
| .str_off_base = str_offsets_base_off (dbg, (cudie != NULL |
| ? cudie->cu: NULL)), |
| .startp = (void *) startp + offset, |
| .endp = (void *) endp, |
| }; |
| |
| Dwarf_Attribute *attributes; |
| Dwarf_Attribute *attributesp = NULL; |
| Dwarf_Attribute nattributes[8]; |
| if (unlikely (proto->nforms > 8)) |
| { |
| attributesp = malloc (sizeof (Dwarf_Attribute) * proto->nforms); |
| if (attributesp == NULL) |
| { |
| __libdw_seterrno (DWARF_E_NOMEM); |
| return -1; |
| } |
| attributes = attributesp; |
| } |
| else |
| attributes = &nattributes[0]; |
| |
| for (Dwarf_Word i = 0; i < proto->nforms; ++i) |
| { |
| /* We pretend this is a DW_AT[_GNU]_macros attribute so that |
| DW_FORM_sec_offset forms get correctly interpreted as |
| offset into .debug_macro. XXX Deal with DW_MACRO_import_sup |
| (swap .dbg) for DW_FORM_sec_offset? */ |
| attributes[i].code = (fake_cu.version == 4 ? DW_AT_GNU_macros |
| : DW_AT_macros); |
| attributes[i].form = proto->forms[i]; |
| attributes[i].valp = (void *) readp; |
| attributes[i].cu = &fake_cu; |
| |
| /* We don't want forms that aren't allowed because they could |
| read from the "abbrev" like DW_FORM_implicit_const. */ |
| if (! libdw_valid_user_form (attributes[i].form)) |
| { |
| __libdw_seterrno (DWARF_E_INVALID_DWARF); |
| free (attributesp); |
| return -1; |
| } |
| |
| size_t len = __libdw_form_val_len (&fake_cu, proto->forms[i], readp); |
| if (unlikely (len == (size_t) -1)) |
| { |
| free (attributesp); |
| return -1; |
| } |
| |
| readp += len; |
| } |
| |
| Dwarf_Macro macro = { |
| .table = table, |
| .opcode = opcode, |
| .attributes = attributes, |
| }; |
| |
| int res = callback (¯o, arg); |
| if (unlikely (attributesp != NULL)) |
| free (attributesp); |
| |
| if (res != DWARF_CB_OK) |
| return readp - startp; |
| } |
| |
| return 0; |
| } |
| |
| /* Token layout: |
| |
| - The highest bit is used for distinguishing between callers that |
| know that opcode 0xff may have one of two incompatible meanings. |
| The mask that we use for selecting this bit is |
| DWARF_GETMACROS_START. |
| |
| - The rest of the token (31 or 63 bits) encodes address inside the |
| macro unit. |
| |
| Besides, token value of 0 signals end of iteration and -1 is |
| reserved for signaling errors. That means it's impossible to |
| represent maximum offset of a .debug_macro unit to new-style |
| callers (which in practice decreases the permissible macro unit |
| size by another 1 byte). */ |
| |
| static ptrdiff_t |
| token_from_offset (ptrdiff_t offset, bool accept_0xff) |
| { |
| if (offset == -1 || offset == 0) |
| return offset; |
| |
| /* Make sure the offset didn't overflow into the flag bit. */ |
| if ((offset & DWARF_GETMACROS_START) != 0) |
| { |
| __libdw_seterrno (DWARF_E_TOO_BIG); |
| return -1; |
| } |
| |
| if (accept_0xff) |
| offset |= DWARF_GETMACROS_START; |
| |
| return offset; |
| } |
| |
| static ptrdiff_t |
| offset_from_token (ptrdiff_t token, bool *accept_0xffp) |
| { |
| *accept_0xffp = (token & DWARF_GETMACROS_START) != 0; |
| token &= ~DWARF_GETMACROS_START; |
| |
| return token; |
| } |
| |
| static ptrdiff_t |
| gnu_macros_getmacros_off (Dwarf *dbg, Dwarf_Off macoff, |
| int (*callback) (Dwarf_Macro *, void *), |
| void *arg, ptrdiff_t offset, bool accept_0xff, |
| Dwarf_Die *cudie) |
| { |
| assert (offset >= 0); |
| |
| if (macoff >= dbg->sectiondata[IDX_debug_macro]->d_size) |
| { |
| __libdw_seterrno (DWARF_E_INVALID_OFFSET); |
| return -1; |
| } |
| |
| return read_macros (dbg, IDX_debug_macro, macoff, |
| callback, arg, offset, accept_0xff, cudie); |
| } |
| |
| static ptrdiff_t |
| macro_info_getmacros_off (Dwarf *dbg, Dwarf_Off macoff, |
| int (*callback) (Dwarf_Macro *, void *), |
| void *arg, ptrdiff_t offset, Dwarf_Die *cudie) |
| { |
| assert (offset >= 0); |
| |
| return read_macros (dbg, IDX_debug_macinfo, macoff, |
| callback, arg, offset, true, cudie); |
| } |
| |
| ptrdiff_t |
| dwarf_getmacros_off (Dwarf *dbg, Dwarf_Off macoff, |
| int (*callback) (Dwarf_Macro *, void *), |
| void *arg, ptrdiff_t token) |
| { |
| if (dbg == NULL) |
| { |
| __libdw_seterrno (DWARF_E_NO_DWARF); |
| return -1; |
| } |
| |
| bool accept_0xff; |
| ptrdiff_t offset = offset_from_token (token, &accept_0xff); |
| assert (accept_0xff); |
| |
| offset = gnu_macros_getmacros_off (dbg, macoff, callback, arg, offset, |
| accept_0xff, NULL); |
| |
| return token_from_offset (offset, accept_0xff); |
| } |
| |
| ptrdiff_t |
| dwarf_getmacros (Dwarf_Die *cudie, int (*callback) (Dwarf_Macro *, void *), |
| void *arg, ptrdiff_t token) |
| { |
| if (cudie == NULL) |
| { |
| __libdw_seterrno (DWARF_E_NO_DWARF); |
| return -1; |
| } |
| |
| /* This function might be called from a code that expects to see |
| DW_MACINFO_* opcodes, not DW_MACRO_{GNU_,}* ones. It is fine to |
| serve most DW_MACRO_{GNU_,}* opcodes to such code, because those |
| whose values are the same as DW_MACINFO_* ones also have the same |
| behavior. It is not very likely that a .debug_macro section |
| would only use the part of opcode space that it shares with |
| .debug_macinfo, but it is possible. Serving the opcodes that are |
| only valid in DW_MACRO_{GNU_,}* domain is OK as well, because |
| clients in general need to be ready that newer standards define |
| more opcodes, and have coping mechanisms for unfamiliar opcodes. |
| |
| The one exception to the above rule is opcode 0xff, which has |
| concrete semantics in .debug_macinfo, but falls into vendor block |
| in .debug_macro, and can be assigned to do whatever. There is |
| some small probability that the two opcodes would look |
| superficially similar enough that a client would be confused and |
| misbehave as a result. For this reason, we refuse to serve |
| through this interface 0xff's originating from .debug_macro |
| unless the TOKEN that we obtained indicates the call originates |
| from a new-style caller. See above for details on what |
| information is encoded into tokens. */ |
| |
| bool accept_0xff; |
| ptrdiff_t offset = offset_from_token (token, &accept_0xff); |
| |
| /* DW_AT_macro_info */ |
| if (dwarf_hasattr (cudie, DW_AT_macro_info)) |
| { |
| Dwarf_Word macoff; |
| if (get_offset_from (cudie, DW_AT_macro_info, &macoff) != 0) |
| return -1; |
| offset = macro_info_getmacros_off (cudie->cu->dbg, macoff, |
| callback, arg, offset, cudie); |
| } |
| else |
| { |
| /* DW_AT_GNU_macros, DW_AT_macros */ |
| Dwarf_Word macoff; |
| if (get_offset_from (cudie, DW_AT_GNU_macros, &macoff) != 0 |
| && get_offset_from (cudie, DW_AT_macros, &macoff) != 0) |
| return -1; |
| offset = gnu_macros_getmacros_off (cudie->cu->dbg, macoff, |
| callback, arg, offset, accept_0xff, |
| cudie); |
| } |
| |
| return token_from_offset (offset, accept_0xff); |
| } |