diff options
Diffstat (limited to 'tests/varlocs.c')
-rw-r--r-- | tests/varlocs.c | 426 |
1 files changed, 382 insertions, 44 deletions
diff --git a/tests/varlocs.c b/tests/varlocs.c index c3fba89e..25124399 100644 --- a/tests/varlocs.c +++ b/tests/varlocs.c @@ -1,5 +1,5 @@ /* Test program for dwarf location functions. - Copyright (C) 2013, 2015 Red Hat, Inc. + Copyright (C) 2013, 2015, 2017, 2018 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -39,9 +39,13 @@ // Needed for DW_OP_call_frame_cfa. static Dwarf *dw; Dwarf_CFI *cfi_debug; +Dwarf_Addr cfi_debug_bias; Dwarf_CFI *cfi_eh; Dwarf_Addr cfi_eh_bias; +bool is_ET_REL; +bool is_debug; + // Whether the current function has a DW_AT_frame_base defined. // Needed for DW_OP_fbreg. bool has_frame_base; @@ -75,11 +79,51 @@ dwarf_encoding_string (unsigned int code) return NULL; } +static const char * +dwarf_tag_string (unsigned int tag) +{ + switch (tag) + { +#define DWARF_ONE_KNOWN_DW_TAG(NAME, CODE) case CODE: return #NAME; + DWARF_ALL_KNOWN_DW_TAG +#undef DWARF_ONE_KNOWN_DW_TAG + default: + return NULL; + } +} + +static const char * +dwarf_attr_string (unsigned int attrnum) +{ + switch (attrnum) + { +#define DWARF_ONE_KNOWN_DW_AT(NAME, CODE) case CODE: return #NAME; + DWARF_ALL_KNOWN_DW_AT +#undef DWARF_ONE_KNOWN_DW_AT + default: + return NULL; + } +} + +static const char * +dwarf_form_string (unsigned int form) +{ + switch (form) + { +#define DWARF_ONE_KNOWN_DW_FORM(NAME, CODE) case CODE: return #NAME; + DWARF_ALL_KNOWN_DW_FORM +#undef DWARF_ONE_KNOWN_DW_FORM + default: + return NULL; + } +} + /* BASE must be a base type DIE referenced by a typed DWARF expression op. */ static void print_base_type (Dwarf_Die *base) { - assert (dwarf_tag (base) == DW_TAG_base_type); + if (dwarf_tag (base) != DW_TAG_base_type) + error (EXIT_FAILURE, 0, "not a base type"); Dwarf_Attribute encoding; Dwarf_Word enctype = 0; @@ -120,16 +164,16 @@ dwarf_opcode_string (unsigned int code) } // Forward reference for print_expr_block. -static void print_expr (Dwarf_Attribute *, Dwarf_Op *, Dwarf_Addr); +static void print_expr (Dwarf_Attribute *, Dwarf_Op *, Dwarf_Addr, int); static void print_expr_block (Dwarf_Attribute *attr, Dwarf_Op *exprs, int len, - Dwarf_Addr addr) + Dwarf_Addr addr, int depth) { printf ("{"); for (int i = 0; i < len; i++) { - print_expr (attr, &exprs[i], addr); + print_expr (attr, &exprs[i], addr, depth); printf ("%s", (i + 1 < len ? ", " : "")); } printf ("}"); @@ -141,13 +185,17 @@ print_expr_block_addrs (Dwarf_Attribute *attr, Dwarf_Op *exprs, int len) { printf (" [%" PRIx64 ",%" PRIx64 ") ", begin, end); - print_expr_block (attr, exprs, len, begin); + print_expr_block (attr, exprs, len, begin, 0); printf ("\n"); } static void -print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) +print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr, int depth) { +#define MAX_DEPTH 64 + if (depth++ > MAX_DEPTH) + error (EXIT_FAILURE, 0, "print_expr recursion depth exceeded"); + uint8_t atom = expr->atom; const char *opname = dwarf_opcode_string (atom); assert (opname != NULL); @@ -215,24 +263,33 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) error (EXIT_FAILURE, 0, "%s used in CFI", opname); printf ("%s ", opname); - if (cfi_eh == NULL && cfi_debug == NULL) + if (cfi_eh == NULL && cfi_debug == NULL && !is_debug) error (EXIT_FAILURE, 0, "DW_OP_call_frame_cfa used but no cfi found."); Dwarf_Frame *frame; - if (dwarf_cfi_addrframe (cfi_eh, addr + cfi_eh_bias, &frame) != 0 - && dwarf_cfi_addrframe (cfi_debug, addr, &frame) != 0) + if (dwarf_cfi_addrframe (cfi_eh, addr + cfi_eh_bias, &frame) == 0 + || dwarf_cfi_addrframe (cfi_debug, addr + cfi_debug_bias, + &frame) == 0) + { + Dwarf_Op *cfa_ops; + size_t cfa_nops; + if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0) + error (EXIT_FAILURE, 0, "dwarf_frame_cfa 0x%" PRIx64 ": %s", + addr, dwarf_errmsg (-1)); + if (cfa_nops < 1) + error (EXIT_FAILURE, 0, "dwarf_frame_cfa no ops"); + print_expr_block (NULL, cfa_ops, cfa_nops, 0, depth); + free (frame); + } + else if (is_ET_REL || is_debug) + { + /* XXX In ET_REL files there might be an .eh_frame with relocations + we don't handle (e.g. X86_64_PC32). Maybe we should? */ + printf ("{...}"); + } + else error (EXIT_FAILURE, 0, "dwarf_cfi_addrframe 0x%" PRIx64 ": %s", addr, dwarf_errmsg (-1)); - - Dwarf_Op *cfa_ops; - size_t cfa_nops; - if (dwarf_frame_cfa (frame, &cfa_ops, &cfa_nops) != 0) - error (EXIT_FAILURE, 0, "dwarf_frame_cfa 0x%" PRIx64 ": %s", - addr, dwarf_errmsg (-1)); - if (cfa_nops < 1) - error (EXIT_FAILURE, 0, "dwarf_frame_cfa no ops"); - print_expr_block (NULL, cfa_ops, cfa_nops, 0); - free (frame); break; case DW_OP_push_object_address: @@ -290,7 +347,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) dwarf_errmsg (-1)); printf ("%s([%" PRIx64 "]) ", opname, dwarf_dieoffset (&call_die)); - print_expr_block (&call_attr, call_ops, call_len, addr); + print_expr_block (&call_attr, call_ops, call_len, addr, depth); } break; @@ -360,6 +417,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_implicit_pointer: case DW_OP_GNU_implicit_pointer: /* Special, DIE offset, signed offset. Referenced DIE has a location or const_value attribute. */ @@ -386,7 +444,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) Dwarf_Die impl_die; if (dwarf_getlocation_die (attr, expr, &impl_die) != 0) - error (EXIT_FAILURE, 0, "dwarf_getlocation_due: %s", + error (EXIT_FAILURE, 0, "dwarf_getlocation_die: %s", dwarf_errmsg (-1)); printf ("%s([%" PRIx64 "],%" PRId64 ") ", opname, @@ -404,7 +462,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) if (locs == 0) printf ("<no location>"); // This means "optimized out". else if (locs == 1) - print_expr_block (&attrval, exprval, exprval_len, addr); + print_expr_block (&attrval, exprval, exprval_len, addr, depth); else error (EXIT_FAILURE, 0, "dwarf_getlocation_addr attrval at addr 0x%" PRIx64 @@ -413,6 +471,47 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_GNU_variable_value: + /* Special, DIE offset. Referenced DIE has a location or const_value + attribute. */ + { + if (attr == NULL) + error (EXIT_FAILURE, 0, "%s used in CFI", opname); + + Dwarf_Attribute attrval; + if (dwarf_getlocation_attr (attr, expr, &attrval) != 0) + error (EXIT_FAILURE, 0, "dwarf_getlocation_attr: %s", + dwarf_errmsg (-1)); + + Dwarf_Die impl_die; + if (dwarf_getlocation_die (attr, expr, &impl_die) != 0) + error (EXIT_FAILURE, 0, "dwarf_getlocation_die: %s", + dwarf_errmsg (-1)); + + printf ("%s([%" PRIx64 "]) ", opname, dwarf_dieoffset (&impl_die)); + + if (dwarf_whatattr (&attrval) == DW_AT_const_value) + printf ("<constant value>"); // Lookup type... + else + { + // Lookup the location description at the current address. + Dwarf_Op *exprval; + size_t exprval_len; + int locs = dwarf_getlocation_addr (&attrval, addr, + &exprval, &exprval_len, 1); + if (locs == 0) + printf ("<no location>"); // This means "optimized out". + else if (locs == 1) + print_expr_block (&attrval, exprval, exprval_len, addr, depth); + else + error (EXIT_FAILURE, 0, + "dwarf_getlocation_addr attrval at addr 0x%" PRIx64 + ", locs (%d): %s", addr, locs, dwarf_errmsg (-1)); + } + } + break; + + case DW_OP_entry_value: case DW_OP_GNU_entry_value: /* Special, unsigned size plus expression block. All registers inside the block should be interpreted as they had on @@ -432,7 +531,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) dwarf_errmsg (-1)); printf ("%s(%zd) ", opname, entry_len); - print_expr_block (attr, entry_ops, entry_len, addr); + print_expr_block (attr, entry_ops, entry_len, addr, depth); } break; @@ -451,11 +550,14 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) // XXX actually lookup DW_TAG_GNU_call_site_parameter printf ("%s[%" PRIx64 "]", opname, dwarf_dieoffset (¶m)); assert (expr->number == dwarf_cuoffset (¶m)); - assert (dwarf_tag (¶m) == DW_TAG_formal_parameter); + if (dwarf_tag (¶m) != DW_TAG_formal_parameter) + error (EXIT_FAILURE, 0, "Not a formal parameter"); } break; + case DW_OP_convert: case DW_OP_GNU_convert: + case DW_OP_reinterpret: case DW_OP_GNU_reinterpret: /* Special, unsigned CU relative DIE offset pointing to a DW_TAG_base_type. Pops a value, converts or reinterprets the @@ -480,6 +582,7 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_regval_type: case DW_OP_GNU_regval_type: /* Special, unsigned register number plus unsigned CU relative DIE offset pointing to a DW_TAG_base_type. */ @@ -495,9 +598,10 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_deref_type: case DW_OP_GNU_deref_type: /* Special, unsigned size plus unsigned CU relative DIE offset - pointing to a DW_TAG_base_type. */ + pointing to a DW_TAG_base_type. */ { Dwarf_Die type; if (dwarf_getlocation_die (attr, expr, &type) != 0) @@ -510,6 +614,21 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_xderef_type: + /* Special, unsigned size plus unsigned DIE offset + pointing to a DW_TAG_base_type. */ + { + Dwarf_Die type; + if (dwarf_getlocation_die (attr, expr, &type) != 0) + error (EXIT_FAILURE, 0, "dwarf_getlocation_die: %s", + dwarf_errmsg (-1)); + // XXX check size against base_type size? + printf ("%s(%" PRIu64 ")", opname, expr->number); + print_base_type (&type); + } + break; + + case DW_OP_const_type: case DW_OP_GNU_const_type: /* Special, unsigned CU relative DIE offset pointing to a DW_TAG_base_type, an unsigned size length plus a block with @@ -540,6 +659,42 @@ print_expr (Dwarf_Attribute *attr, Dwarf_Op *expr, Dwarf_Addr addr) } break; + case DW_OP_GNU_addr_index: + case DW_OP_addrx: + /* Address from the .debug_addr section (indexed based on CU). */ + { + Dwarf_Attribute addr_attr; + if (dwarf_getlocation_attr (attr, expr, &addr_attr) != 0) + error (EXIT_FAILURE, 0, "dwarf_getlocation_attr for addr: %s", + dwarf_errmsg (-1)); + + Dwarf_Addr address; + if (dwarf_formaddr (&addr_attr, &address) != 0) + error (EXIT_FAILURE, 0, "dwarf_formaddr address failed: %s", + dwarf_errmsg (-1)); + + printf ("addr: 0x%" PRIx64, address); + } + break; + + case DW_OP_GNU_const_index: + case DW_OP_constx: + /* Constant from the .debug_addr section (indexed based on CU). */ + { + Dwarf_Attribute addr_attr; + if (dwarf_getlocation_attr (attr, expr, &addr_attr) != 0) + error (EXIT_FAILURE, 0, "dwarf_getlocation_attr for addr: %s", + dwarf_errmsg (-1)); + + Dwarf_Word constant; + if (dwarf_formudata (&addr_attr, &constant) != 0) + error (EXIT_FAILURE, 0, "dwarf_formudata constant failed: %s", + dwarf_errmsg (-1)); + + printf ("const: 0x%" PRIx64, constant); + } + break; + default: error (EXIT_FAILURE, 0, "unhandled opcode: DW_OP_%s (0x%x)", opname, atom); @@ -572,7 +727,7 @@ print_varlocs (Dwarf_Die *funcdie) if (entrypc == 0) printf ("XXX zero address"); // XXX bad DWARF? else - print_expr_block (&fb_attr, fb_expr, fb_exprlen, entrypc); + print_expr_block (&fb_attr, fb_expr, fb_exprlen, entrypc, 0); printf ("\n"); } else @@ -585,7 +740,7 @@ print_varlocs (Dwarf_Die *funcdie) &fb_expr, &fb_exprlen)) > 0) { printf (" (%" PRIx64 ",%" PRIx64 ") ", start, end); - print_expr_block (&fb_attr, fb_expr, fb_exprlen, start); + print_expr_block (&fb_attr, fb_expr, fb_exprlen, start, 0); printf ("\n"); } @@ -759,9 +914,162 @@ handle_function (Dwarf_Die *funcdie, void *arg __attribute__((unused))) return DWARF_CB_OK; } +struct attr_arg +{ + int depth; + Dwarf_Addr entrypc; +}; + +static int +handle_attr (Dwarf_Attribute *attr, void *arg) +{ + int depth = ((struct attr_arg *) arg)->depth; + Dwarf_Addr entrypc = ((struct attr_arg *) arg)->entrypc; + + unsigned int code = dwarf_whatattr (attr); + unsigned int form = dwarf_whatform (attr); + + printf ("%*s%s (%s)", depth * 2, "", + dwarf_attr_string (code), dwarf_form_string (form)); + + /* If we can get an DWARF expression (or location lists) from this + attribute we'll print it, otherwise we'll ignore it. But if + there is an error while the attribute has the "correct" form then + we'll report an error (we can only really check DW_FORM_exprloc + other forms can be ambiguous). */ + Dwarf_Op *expr; + size_t exprlen; + bool printed = false; + int res = dwarf_getlocation (attr, &expr, &exprlen); + if (res == 0) + { + printf (" "); + print_expr_block (attr, expr, exprlen, entrypc, 0); + printf ("\n"); + printed = true; + } + else if (form == DW_FORM_exprloc) + { + error (0, 0, "%s dwarf_getlocation failed: %s", + dwarf_attr_string (code), dwarf_errmsg (-1)); + return DWARF_CB_ABORT; + } + else + { + Dwarf_Addr base, begin, end; + ptrdiff_t offset = 0; + while ((offset = dwarf_getlocations (attr, offset, + &base, &begin, &end, + &expr, &exprlen)) > 0) + { + if (! printed) + printf ("\n"); + printf ("%*s", depth * 2, ""); + print_expr_block_addrs (attr, begin, end, expr, exprlen); + printed = true; + } + } + + if (! printed) + printf ("\n"); + + return DWARF_CB_OK; +} + +static void +handle_die (Dwarf_Die *die, int depth, bool outer_has_frame_base, + Dwarf_Addr outer_entrypc) +{ + /* CU DIE already printed. */ + if (depth > 0) + { + const char *name = dwarf_diename (die); + if (name != NULL) + printf ("%*s[%" PRIx64 "] %s \"%s\"\n", depth * 2, "", + dwarf_dieoffset (die), dwarf_tag_string (dwarf_tag (die)), + name); + else + printf ("%*s[%" PRIx64 "] %s\n", depth * 2, "", + dwarf_dieoffset (die), dwarf_tag_string (dwarf_tag (die))); + } + + struct attr_arg arg; + arg.depth = depth + 1; + + /* The (lowest) address to use for (looking up) operands that depend + on address. */ + Dwarf_Addr die_entrypc; + if (dwarf_entrypc (die, &die_entrypc) != 0 || die_entrypc == 0) + { + /* Try to get the lowest address of the first range covered. */ + Dwarf_Addr base, start, end; + if (dwarf_ranges (die, 0, &base, &start, &end) <= 0 || start == 0) + die_entrypc = outer_entrypc; + else + die_entrypc = start; + } + arg.entrypc = die_entrypc; + + /* Whether this or the any outer DIE has a frame base. Used as + sanity check when printing experssions that use DW_OP_fbreg. */ + bool die_has_frame_base = dwarf_hasattr (die, DW_AT_frame_base); + die_has_frame_base |= outer_has_frame_base; + has_frame_base = die_has_frame_base; + + /* Look through all attributes to find those that contain DWARF + expressions and print those. We expect to handle all attributes, + anything else is an error. */ + if (dwarf_getattrs (die, handle_attr, &arg, 0) != 1) + error (EXIT_FAILURE, 0, "Couldn't get all attributes: %s", + dwarf_errmsg (-1)); + + /* Handle children and siblings recursively depth first. */ + Dwarf_Die child; + if (dwarf_haschildren (die) != 0 && dwarf_child (die, &child) == 0) + handle_die (&child, depth + 1, die_has_frame_base, die_entrypc); + + Dwarf_Die sibling; + if (dwarf_siblingof (die, &sibling) == 0) + handle_die (&sibling, depth, outer_has_frame_base, outer_entrypc); +} + int main (int argc, char *argv[]) { + /* With --exprlocs we process all DIEs looking for any attribute + which contains an DWARF expression (but not location lists) and + print those. Otherwise we process all function DIEs and print + all DWARF expressions and location lists associated with + parameters and variables). It must be the first argument, + or the second, after --debug. */ + bool exprlocs = false; + + /* With --debug we ignore not being able to find .eh_frame. + It must come as first argument. */ + is_debug = false; + if (argc > 1) + { + if (strcmp ("--exprlocs", argv[1]) == 0) + { + exprlocs = true; + argv[1] = ""; + } + else if (strcmp ("--debug", argv[1]) == 0) + { + is_debug = true; + argv[1] = ""; + } + } + + if (argc > 2) + { + if (strcmp ("--exprlocs", argv[2]) == 0) + { + exprlocs = true; + argv[2] = ""; + } + } + int remaining; Dwfl *dwfl; (void) argp_parse (dwfl_standard_argp (), argc, argv, 0, &remaining, @@ -770,14 +1078,27 @@ main (int argc, char *argv[]) Dwarf_Die *cu = NULL; Dwarf_Addr dwbias; + bool found_cu = false; while ((cu = dwfl_nextcu (dwfl, cu, &dwbias)) != NULL) { /* Only walk actual compile units (not partial units) that - contain code. */ + contain code if we are only interested in the function variable + locations. */ + Dwarf_Die cudie; + Dwarf_Die subdie; + uint8_t unit_type; + if (dwarf_cu_info (cu->cu, NULL, &unit_type, &cudie, &subdie, + NULL, NULL, NULL) != 0) + error (EXIT_FAILURE, 0, "dwarf_cu_info: %s", dwarf_errmsg (-1)); + if (unit_type == DW_UT_skeleton) + cudie = subdie; + Dwarf_Addr cubase; - if (dwarf_tag (cu) == DW_TAG_compile_unit - && dwarf_lowpc (cu, &cubase) == 0) + if (dwarf_tag (&cudie) == DW_TAG_compile_unit + && (exprlocs || dwarf_lowpc (&cudie, &cubase) == 0)) { + found_cu = true; + Dwfl_Module *mod = dwfl_cumodule (cu); Dwarf_Addr modbias; dw = dwfl_module_getdwarf (mod, &modbias); @@ -796,29 +1117,46 @@ main (int argc, char *argv[]) ? modname : basename (mainfile)); printf ("module '%s'\n", name); - print_die (cu, "CU", 0); + print_die (&cudie, "CU", 0); Dwarf_Addr elfbias; Elf *elf = dwfl_module_getelf (mod, &elfbias); // CFI. We need both since sometimes neither is complete. - cfi_debug = dwarf_getcfi (dw); // No bias needed, same file. - cfi_eh = dwarf_getcfi_elf (elf); - cfi_eh_bias = dwbias - elfbias; - - // Get the actual CU DIE and walk all functions inside it. - Dwarf_Die cudie; - uint8_t offsize; - uint8_t addrsize; - if (dwarf_diecu (cu, &cudie, &addrsize, &offsize) == NULL) - error (EXIT_FAILURE, 0, "dwarf_diecu %s", dwarf_errmsg (-1)); - - if (dwarf_getfuncs (cu, handle_function, NULL, 0) != 0) + cfi_debug = dwfl_module_dwarf_cfi (mod, &cfi_debug_bias); + cfi_eh = dwfl_module_eh_cfi (mod, &cfi_eh_bias); + + // No bias needed, same file. + assert (cfi_debug == NULL || cfi_debug_bias == 0); + + // We are a bit forgiving for object files. There might be + // relocations we don't handle that are needed in some + // places... + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + is_ET_REL = ehdr->e_type == ET_REL; + + if (exprlocs) + { + Dwarf_Addr entrypc; + if (dwarf_entrypc (&cudie, &entrypc) != 0) + entrypc = 0; + + /* XXX - Passing true for has_frame_base is not really true. + We do it because we want to resolve all DIEs and all + attributes. Technically we should check that the DIE + (types) are referenced from variables that are defined in + a context (function) that has a frame base. */ + handle_die (&cudie, 0, true /* Should be false */, entrypc); + } + else if (dwarf_getfuncs (&cudie, handle_function, NULL, 0) != 0) error (EXIT_FAILURE, 0, "dwarf_getfuncs %s", dwarf_errmsg (-1)); } } + if (! found_cu) + error (EXIT_FAILURE, 0, "No DWARF CU found?"); + dwfl_end (dwfl); return 0; } |