diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h @@ -379,6 +379,8 @@ lldb::VariableSP ParseVariableDIE(const lldb_private::SymbolContext &sc, const DWARFDIE &die, const lldb::addr_t func_low_pc); + lldb::VariableSP ParseVariableDIECached(const lldb_private::SymbolContext &sc, + const DWARFDIE &die); void ParseAndAppendGlobalVariable(const lldb_private::SymbolContext &sc, @@ -391,8 +393,15 @@ size_t ParseVariablesInFunctionContextRecursive( const lldb_private::SymbolContext &sc, const DWARFDIE &die, - const lldb::addr_t func_low_pc, - lldb_private::VariableList &variable_list); + lldb::addr_t func_low_pc, DIEArray &accumulator); + + size_t PopulateBlockVariableList(lldb_private::VariableList &variable_list, + const lldb_private::SymbolContext &sc, + llvm::ArrayRef variable_dies, + lldb::addr_t func_low_pc); + + DIEArray MergeBlockAbstractParameters(const DWARFDIE &block_die, + DIEArray &&variable_dies); bool ClassOrStructIsVirtual(const DWARFDIE &die); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -3092,7 +3092,7 @@ sc.comp_unit->SetVariableList(variables); m_index->GetGlobalVariables(*dwarf_cu, [&](DWARFDIE die) { - VariableSP var_sp(ParseVariableDIE(sc, die, LLDB_INVALID_ADDRESS)); + VariableSP var_sp(ParseVariableDIECached(sc, die)); if (var_sp) { variables->AddVariableIfUnique(var_sp); ++vars_added; @@ -3106,6 +3106,26 @@ return 0; } +VariableSP SymbolFileDWARF::ParseVariableDIECached(const SymbolContext &sc, + const DWARFDIE &die) { + if (!die) + return nullptr; + + DIEToVariableSP &die_to_variable = die.GetDWARF()->GetDIEToVariable(); + + VariableSP var_sp = die_to_variable[die.GetDIE()]; + if (var_sp) + return var_sp; + + var_sp = ParseVariableDIE(sc, die, LLDB_INVALID_ADDRESS); + if (var_sp) { + die_to_variable[die.GetDIE()] = var_sp; + if (DWARFDIE spec_die = die.GetReferencedDIE(DW_AT_specification)) + die_to_variable[spec_die.GetDIE()] = var_sp; + } + return var_sp; +} + VariableSP SymbolFileDWARF::ParseVariableDIE(const SymbolContext &sc, const DWARFDIE &die, const lldb::addr_t func_low_pc) { @@ -3115,9 +3135,6 @@ if (!die) return nullptr; - if (VariableSP var_sp = GetDIEToVariable()[die.GetDIE()]) - return var_sp; // Already been parsed! - const dw_tag_t tag = die.Tag(); ModuleSP module = GetObjectFile()->GetModule(); @@ -3127,8 +3144,6 @@ DWARFAttributes attributes; const size_t num_attributes = die.GetAttributes(attributes); - DWARFDIE spec_die; - VariableSP var_sp; const char *name = nullptr; const char *mangled = nullptr; Declaration decl; @@ -3175,9 +3190,6 @@ case DW_AT_location: location_form = form_value; break; - case DW_AT_specification: - spec_die = form_value.Reference(); - break; case DW_AT_start_scope: // TODO: Implement this. break; @@ -3188,6 +3200,7 @@ case DW_AT_description: case DW_AT_endianity: case DW_AT_segment: + case DW_AT_specification: case DW_AT_visibility: default: case DW_AT_abstract_origin: @@ -3380,7 +3393,7 @@ location.Update_DW_OP_addr(exe_file_addr); } else { // Variable didn't make it into the final executable - return var_sp; + return nullptr; } } } @@ -3426,35 +3439,25 @@ } } - if (symbol_context_scope) { - auto type_sp = std::make_shared( - *this, GetUID(type_die_form.Reference())); - - if (use_type_size_for_value && type_sp->GetType()) - location.UpdateValue( - const_value_form.Unsigned(), - type_sp->GetType()->GetByteSize(nullptr).getValueOr(0), - die.GetCU()->GetAddressByteSize()); - - var_sp = std::make_shared( - die.GetID(), name, mangled, type_sp, scope, symbol_context_scope, - scope_ranges, &decl, location, is_external, is_artificial, - location_is_const_value_data, is_static_member); - } else { + if (!symbol_context_scope) { // Not ready to parse this variable yet. It might be a global or static // variable that is in a function scope and the function in the symbol // context wasn't filled in yet - return var_sp; + return nullptr; } - // Cache var_sp even if NULL (the variable was just a specification or was - // missing vital information to be able to be displayed in the debugger - // (missing location due to optimization, etc)) so we don't re-parse this - // DIE over and over later... - GetDIEToVariable()[die.GetDIE()] = var_sp; - if (spec_die) - GetDIEToVariable()[spec_die.GetDIE()] = var_sp; - return var_sp; + auto type_sp = std::make_shared( + *this, GetUID(type_die_form.Reference())); + + if (use_type_size_for_value && type_sp->GetType()) + location.UpdateValue(const_value_form.Unsigned(), + type_sp->GetType()->GetByteSize(nullptr).getValueOr(0), + die.GetCU()->GetAddressByteSize()); + + return std::make_shared( + die.GetID(), name, mangled, type_sp, scope, symbol_context_scope, + scope_ranges, &decl, location, is_external, is_artificial, + location_is_const_value_data, is_static_member); } DWARFDIE @@ -3518,7 +3521,9 @@ return; } - // We haven't already parsed it, lets do that now. + // We haven't parsed the variable yet, lets do that now. Also, let us include + // the variable in the relevant compilation unit's variable list, if it + // exists. VariableListSP variable_list_sp; DWARFDIE sc_parent_die = GetParentSymbolContextDIE(die); dw_tag_t parent_tag = sc_parent_die.Tag(); @@ -3545,7 +3550,7 @@ return; } - var_sp = ParseVariableDIE(sc, die, LLDB_INVALID_ADDRESS); + var_sp = ParseVariableDIECached(sc, die); if (!var_sp) return; @@ -3554,32 +3559,109 @@ variable_list_sp->AddVariableIfUnique(var_sp); } +DIEArray +SymbolFileDWARF::MergeBlockAbstractParameters(const DWARFDIE &block_die, + DIEArray &&variable_dies) { + // DW_TAG_inline_subroutine objects may omit DW_TAG_formal_parameter in + // instances of the function when they are unused (i.e., the parameter's + // location list would be empty). The current DW_TAG_inline_subroutine may + // refer to another DW_TAG_subprogram that might actually have the definitions + // of the parameters and we need to include these so they show up in the + // variables for this function (for example, in a stack trace). Let us try to + // find the abstract subprogram that might contain the parameter definitions + // and merge with the concrete parameters. + + // Nothing to merge if the block is not an inlined function. + if (block_die.Tag() != DW_TAG_inlined_subroutine) { + return std::move(variable_dies); + } + + // Nothing to merge if the block does not have abstract parameters. + DWARFDIE abs_die = block_die.GetReferencedDIE(DW_AT_abstract_origin); + if (!abs_die || abs_die.Tag() != DW_TAG_subprogram || + !abs_die.HasChildren()) { + return std::move(variable_dies); + } + + // For each abstract parameter, if we have its concrete counterpart, insert + // it. Otherwise, insert the abstract parameter. + DIEArray::iterator concrete_it = variable_dies.begin(); + DWARFDIE abstract_child = abs_die.GetFirstChild(); + DIEArray merged; + bool did_merge_abstract = false; + for (; abstract_child; abstract_child = abstract_child.GetSibling()) { + if (abstract_child.Tag() == DW_TAG_formal_parameter) { + if (concrete_it == variable_dies.end() || + GetDIE(*concrete_it).Tag() != DW_TAG_formal_parameter) { + // We arrived at the end of the concrete parameter list, so all + // the remaining abstract parameters must have been omitted. + // Let us insert them to the merged list here. + merged.push_back(*abstract_child.GetDIERef()); + did_merge_abstract = true; + continue; + } + + DWARFDIE origin_of_concrete = + GetDIE(*concrete_it).GetReferencedDIE(DW_AT_abstract_origin); + if (origin_of_concrete == abstract_child) { + // The current abstract paramater is the origin of the current + // concrete parameter, just push the concrete parameter. + merged.push_back(*concrete_it); + ++concrete_it; + } else { + // Otherwise, the parameter must have been omitted from the concrete + // function, so insert the abstract one. + merged.push_back(*abstract_child.GetDIERef()); + did_merge_abstract = true; + } + } + } + + // Shortcut if no merging happened. + if (!did_merge_abstract) + return std::move(variable_dies); + + // We inserted all the abstract parameters (or their concrete counterparts). + // Let us insert all the remaining concrete variables to the merged list. + // During the insertion, let us check there are no remaining concrete + // formal parameters. If that's the case, then just bailout from the merge - + // the variable list is malformed. + for (; concrete_it != variable_dies.end(); ++concrete_it) { + if (GetDIE(*concrete_it).Tag() == DW_TAG_formal_parameter) { + return std::move(variable_dies); + } + merged.push_back(*concrete_it); + } + return std::move(merged); +} + size_t SymbolFileDWARF::ParseVariablesInFunctionContext( const SymbolContext &sc, const DWARFDIE &die, const lldb::addr_t func_low_pc) { if (!die || !sc.function) return 0; - VariableList empty_variable_list; - // Since |die| corresponds to a Block instance, the recursive call will get - // a variable list from the block. |empty_variable_list| should remain empty. + DIEArray dummy_block_variables; // The recursive call should not add anything + // to this vector because |die| should be a + // subprogram, so all variables will be added + // to the subprogram's list. return ParseVariablesInFunctionContextRecursive(sc, die, func_low_pc, - empty_variable_list); + dummy_block_variables); } +// This method parses all the variables in the blocks in the subtree of |die|, +// and inserts them to the variable list for all the nested blocks. +// The uninserted variables for the current block are accumulated in +// |accumulator|. size_t SymbolFileDWARF::ParseVariablesInFunctionContextRecursive( const lldb_private::SymbolContext &sc, const DWARFDIE &die, - const lldb::addr_t func_low_pc, VariableList &variable_list) { + lldb::addr_t func_low_pc, DIEArray &accumulator) { size_t vars_added = 0; dw_tag_t tag = die.Tag(); if ((tag == DW_TAG_variable) || (tag == DW_TAG_constant) || (tag == DW_TAG_formal_parameter)) { - VariableSP var_sp(ParseVariableDIE(sc, die, func_low_pc)); - if (var_sp) { - variable_list.AddVariableIfUnique(var_sp); - ++vars_added; - } + accumulator.push_back(*die.GetDIERef()); } switch (tag) { @@ -3611,29 +3693,45 @@ block_variable_list_sp = std::make_shared(); block->SetVariableList(block_variable_list_sp); } + + DIEArray block_variables; for (DWARFDIE child = die.GetFirstChild(); child; child = child.GetSibling()) { vars_added += ParseVariablesInFunctionContextRecursive( - sc, child, func_low_pc, *block_variable_list_sp); + sc, child, func_low_pc, block_variables); } - + block_variables = + MergeBlockAbstractParameters(die, std::move(block_variables)); + vars_added += PopulateBlockVariableList(*block_variable_list_sp, sc, + block_variables, func_low_pc); break; } default: - // Recurse to children with the same variable list. + // Recurse to children with the same variable accumulator. for (DWARFDIE child = die.GetFirstChild(); child; child = child.GetSibling()) { vars_added += ParseVariablesInFunctionContextRecursive( - sc, child, func_low_pc, variable_list); + sc, child, func_low_pc, accumulator); } - break; } return vars_added; } +size_t SymbolFileDWARF::PopulateBlockVariableList( + VariableList &variable_list, const lldb_private::SymbolContext &sc, + llvm::ArrayRef variable_dies, lldb::addr_t func_low_pc) { + // Parse the variable DIEs and insert them to the list. + for (auto &die : variable_dies) { + if (VariableSP var_sp = ParseVariableDIE(sc, GetDIE(die), func_low_pc)) { + variable_list.AddVariableIfUnique(var_sp); + } + } + return variable_dies.size(); +} + /// Collect call site parameters in a DW_TAG_call_site DIE. static CallSiteParameterArray CollectCallSiteParameters(ModuleSP module, DWARFDIE call_site_die) { diff --git a/lldb/test/API/functionalities/unused-inlined-parameters/Makefile b/lldb/test/API/functionalities/unused-inlined-parameters/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/unused-inlined-parameters/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -O1 + +include Makefile.rules diff --git a/lldb/test/API/functionalities/unused-inlined-parameters/TestUnusedInlinedParameters.py b/lldb/test/API/functionalities/unused-inlined-parameters/TestUnusedInlinedParameters.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/unused-inlined-parameters/TestUnusedInlinedParameters.py @@ -0,0 +1,22 @@ +""" +Test that unused inlined parameters are displayed. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestUnusedInlinedParameters(TestBase): + mydir = TestBase.compute_mydir(__file__) + + def test_unused_inlined_parameters(self): + self.build() + lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c")) + + # For the unused parameters, only check the types. + self.assertIn("(void *) unused1 = ", + lldbutil.get_description(self.frame().FindVariable("unused1"))) + self.assertEqual(42, self.frame().FindVariable("used").GetValueAsUnsigned()) + self.assertIn("(int) unused2 = ", + lldbutil.get_description(self.frame().FindVariable("unused2"))) diff --git a/lldb/test/API/functionalities/unused-inlined-parameters/main.c b/lldb/test/API/functionalities/unused-inlined-parameters/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/unused-inlined-parameters/main.c @@ -0,0 +1,12 @@ +#include + +__attribute__((optnone)) __attribute__((nodebug)) void use(int used) {} + +__attribute__((always_inline)) void f(void *unused1, int used, int unused2) { + use(used); // break here +} + +int main(int argc, char **argv) { + f(argv, 42, 1); + return 0; +} \ No newline at end of file diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/Inputs/unused-inlined-params.s b/lldb/test/Shell/SymbolFile/DWARF/x86/Inputs/unused-inlined-params.s new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/x86/Inputs/unused-inlined-params.s @@ -0,0 +1,458 @@ +# The below program is roughly derived from the following C program. +# To see the annotated debug info, look for the section +# '.section .debug_info' below. +# +# __attribute__((always_inline)) +# void f(void* unused1, int used, int unused2, int partial, int unused3) { +# used += partial; +# printf("f %i", partial); +# printf("f %i", used); // |partial| is not live at this line. +# } +# +# void g(int unused) { +# printf("Hello"); +# } +# +# __attribute__((noinline)) +# void other() { +# f(nullptr, 1, 0, 2, 0); +# } +# +# int main(int argc, char** argv) { +# f(argv, 42, 1, argc, 2); +# g(1); +# other(); +# return 0; +# } + + .text + .file "unused-inlined-params.c" + +.Lcu_begin: + + .globl other +other: + nop +.Linlined_f_in_other: +break_at_inlined_f_in_other: + callq printf # Omitted the setup of arguments. +.Linlined_f_in_other_between_printfs: + callq printf # Omitted the setup of arguments. +.Linlined_f_in_other_end: + retq +.Lother_end: + .size other, .Lother_end-other + + .globl main +main: + .file 1 "/example" "unused-inlined-params.c" + movl $1, %esi +.Linlined_f: +break_at_inlined_f_in_main: + leal 42(%rsi), %ebx +.Linlined_f_before_printf: + callq printf # Omitted the setup of arguments. +.Linlined_f_between_printfs: +break_at_inlined_f_in_main_between_printfs: + callq printf # Omitted the setup of arguments. +.Linlined_f_end: +.Linlined_g: +break_at_inlined_g_in_main: + callq printf # Omitted the setup of arguments. +.Linlined_g_end: + callq other + retq +.Lmain_end: + .size main, .Lmain_end-main + +# Dummy printf implementation. +printf: + retq + +# Simple entry point to make the linker happy. + .globl _start +_start: + jmp main + +.Lcu_end: + + + .section .debug_loc,"",@progbits +.Ldebug_loc_partial: + .quad .Linlined_f-.Lcu_begin + .quad .Linlined_f_between_printfs-.Lcu_begin + .short 1 # Loc expr size + .byte 84 # super-register DW_OP_reg4 + .quad 0 + .quad 0 +.Ldebug_loc_used: + .quad .Linlined_f-.Lcu_begin + .quad .Linlined_f_before_printf-.Lcu_begin + .short 3 # Loc expr size + .byte 17 # DW_OP_consts + .byte 42 # value + .byte 159 # DW_OP_stack_value + .quad .Linlined_f_before_printf-.Lcu_begin + .quad .Linlined_f_end-.Lcu_begin + .short 1 # Loc expr size + .byte 83 # super-register DW_OP_reg3 + .quad 0 + .quad 0 +.Ldebug_loc_partial_in_other: + .quad .Linlined_f_in_other-.Lcu_begin + .quad .Linlined_f_in_other_between_printfs-.Lcu_begin + .short 3 # Loc expr size + .byte 17 # DW_OP_consts + .byte 2 # value + .byte 159 # DW_OP_stack_value + .quad 0 + .quad 0 +.Ldebug_loc_used_in_other: + .quad .Linlined_f_in_other-.Lcu_begin + .quad .Linlined_f_in_other_end-.Lcu_begin + .short 3 # Loc expr size + .byte 17 # DW_OP_consts + .byte 1 # value + .byte 159 # DW_OP_stack_value + .quad 0 + .quad 0 + + .section .debug_abbrev,"",@progbits + .byte 1 # Abbreviation Code + .byte 17 # DW_TAG_compile_unit + .byte 1 # DW_CHILDREN_yes + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 16 # DW_AT_stmt_list + .byte 23 # DW_FORM_sec_offset + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 4 # Abbreviation Code + .byte 5 # DW_TAG_formal_parameter + .byte 0 # DW_CHILDREN_no + .byte 2 # DW_AT_location + .byte 23 # DW_FORM_sec_offset + .byte 49 # DW_AT_abstract_origin + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 5 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 1 # DW_CHILDREN_yes + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 39 # DW_AT_prototyped + .byte 25 # DW_FORM_flag_present + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 32 # DW_AT_inline + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 6 # Abbreviation Code + .byte 5 # DW_TAG_formal_parameter + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 7 # Abbreviation Code + .byte 15 # DW_TAG_pointer_type + .byte 0 # DW_CHILDREN_no + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 8 # Abbreviation Code + .byte 36 # DW_TAG_base_type + .byte 0 # DW_CHILDREN_no + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 62 # DW_AT_encoding + .byte 11 # DW_FORM_data1 + .byte 11 # DW_AT_byte_size + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 9 # Abbreviation Code + .byte 46 # DW_TAG_subprogram + .byte 1 # DW_CHILDREN_yes + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 64 # DW_AT_frame_base + .byte 24 # DW_FORM_exprloc + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 39 # DW_AT_prototyped + .byte 25 # DW_FORM_flag_present + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 63 # DW_AT_external + .byte 25 # DW_FORM_flag_present + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 10 # Abbreviation Code + .byte 5 # DW_TAG_formal_parameter + .byte 0 # DW_CHILDREN_no + .byte 2 # DW_AT_location + .byte 23 # DW_FORM_sec_offset + .byte 3 # DW_AT_name + .byte 14 # DW_FORM_strp + .byte 58 # DW_AT_decl_file + .byte 11 # DW_FORM_data1 + .byte 59 # DW_AT_decl_line + .byte 11 # DW_FORM_data1 + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 11 # Abbreviation Code + .byte 29 # DW_TAG_inlined_subroutine + .byte 1 # DW_CHILDREN_yes + .byte 49 # DW_AT_abstract_origin + .byte 19 # DW_FORM_ref4 + .byte 17 # DW_AT_low_pc + .byte 1 # DW_FORM_addr + .byte 18 # DW_AT_high_pc + .byte 6 # DW_FORM_data4 + .byte 88 # DW_AT_call_file + .byte 11 # DW_FORM_data1 + .byte 89 # DW_AT_call_line + .byte 11 # DW_FORM_data1 + .byte 87 # DW_AT_call_column + .byte 11 # DW_FORM_data1 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + + .byte 12 # Abbreviation Code + .byte 15 # DW_TAG_pointer_type + .byte 0 # DW_CHILDREN_no + .byte 73 # DW_AT_type + .byte 19 # DW_FORM_ref4 + .byte 0 # EOM(1) + .byte 0 # EOM(2) + .byte 0 # EOM(3) + + .section .debug_info,"",@progbits +.Ldi_cu_begin: + .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit +.Ldebug_info_start0: + .short 4 # DWARF version number + .long .debug_abbrev # Offset Into Abbrev. Section + .byte 8 # Address Size (in bytes) + .byte 1 # Abbrev [1] DW_TAG_compile_unit + .long .Linfo_string_fname # DW_AT_name + .long .Lline_table_start0 # DW_AT_stmt_list + .quad .Lcu_begin # DW_AT_low_pc + .long .Lcu_end-.Lcu_begin # DW_AT_high_pc + +# Debug info for |f| (abstract version with all parameters). + +.Ldebug_info_f: + .byte 5 # Abbrev [5] DW_TAG_subprogram + .long .Linfo_string_f # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + # DW_AT_prototyped + # DW_AT_external + .byte 1 # DW_AT_inline +.Ldebug_info_param1: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_unused1 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + .long .Ldebug_info_void_ptr-.Ldi_cu_begin + # DW_AT_type +.Ldebug_info_param2: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_used # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type +.Ldebug_info_param3: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_unused2 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type +.Ldebug_info_param4: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_partial # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type +.Ldebug_info_param5: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_unused3 # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type + .byte 0 # End Of Children Mark (DW_TAG_subprogram) + +# Debug info for |g| (abstract version with all parameters). + +.Ldebug_info_g: + .byte 5 # Abbrev [5] DW_TAG_subprogram + .long .Linfo_string_g # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 4 # DW_AT_decl_line + # DW_AT_prototyped + # DW_AT_external + .byte 1 # DW_AT_inline +.Ldebug_info_g_param1: + .byte 6 # Abbrev [6] DW_TAG_formal_parameter + .long .Linfo_string_unused # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 10 # DW_AT_decl_line + .long .Ldebug_info_int-.Ldi_cu_begin + .byte 0 # End Of Children Mark (DW_TAG_subprogram) + +# Debug info for |main|. + + .byte 9 # Abbrev [9] DW_TAG_subprogram + .quad main # DW_AT_low_pc + .long .Lmain_end-main # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + .long .Linfo_string_main # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 18 # DW_AT_decl_line + # DW_AT_prototyped + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type + # DW_AT_external + +# Debug info for concrete |f| inlined into |main|. + + .byte 11 # Abbrev [11] DW_TAG_inlined_subroutine + .long .Ldebug_info_f-.Ldi_cu_begin + # DW_AT_abstract_origin + .quad .Linlined_f # DW_AT_low_pc + .long .Linlined_f_end-.Linlined_f # DW_AT_high_pc + .byte 1 # DW_AT_call_file + .byte 20 # DW_AT_call_line + .byte 3 # DW_AT_call_column + .byte 4 # Abbrev [4] DW_TAG_formal_parameter + .long .Ldebug_loc_used # DW_AT_location + .long .Ldebug_info_param2-.Ldi_cu_begin + # DW_AT_abstract_origin + .byte 4 # Abbrev [4] DW_TAG_formal_parameter + .long .Ldebug_loc_partial # DW_AT_location + .long .Ldebug_info_param4-.Ldi_cu_begin + # DW_AT_abstract_origin + .byte 0 # End Of Children Mark (DW_TAG_inlined_subroutine) + +# Debug info for concrete |g| inlined into |main|. + + .byte 11 # Abbrev [11] DW_TAG_inlined_subroutine + .long .Ldebug_info_g-.Ldi_cu_begin + # DW_AT_abstract_origin + .quad .Linlined_g # DW_AT_low_pc + .long .Linlined_g_end-.Linlined_g # DW_AT_high_pc + .byte 1 # DW_AT_call_file + .byte 21 # DW_AT_call_line + .byte 3 # DW_AT_call_column + .byte 0 # End Of Children Mark (DW_TAG_inlined_subroutine) + + .byte 0 # End Of Children Mark (DW_TAG_subprogram) + +# Debug info for |other|. + + .byte 9 # Abbrev [9] DW_TAG_subprogram + .quad other # DW_AT_low_pc + .long .Lother_end-other # DW_AT_high_pc + .byte 1 # DW_AT_frame_base + .byte 87 + .long .Linfo_string_other # DW_AT_name + .byte 1 # DW_AT_decl_file + .byte 15 # DW_AT_decl_line + # DW_AT_prototyped + .long .Ldebug_info_int-.Ldi_cu_begin # DW_AT_type + # DW_AT_external + +# Debug info for concrete |f| inlined into |other|. + + .byte 11 # Abbrev [11] DW_TAG_inlined_subroutine + .long .Ldebug_info_f-.Ldi_cu_begin + # DW_AT_abstract_origin + .quad .Linlined_f_in_other # DW_AT_low_pc + .long .Linlined_f_in_other_end-.Linlined_f_in_other + # DW_AT_high_pc + .byte 1 # DW_AT_call_file + .byte 16 # DW_AT_call_line + .byte 3 # DW_AT_call_column + .byte 4 # Abbrev [4] DW_TAG_formal_parameter + .long .Ldebug_loc_used_in_other # DW_AT_location + .long .Ldebug_info_param2-.Ldi_cu_begin + # DW_AT_abstract_origin + .byte 4 # Abbrev [4] DW_TAG_formal_parameter + .long .Ldebug_loc_partial_in_other # DW_AT_location + .long .Ldebug_info_param4-.Ldi_cu_begin + # DW_AT_abstract_origin + .byte 0 # End Of Children Mark (DW_TAG_inlined_subroutine) + .byte 0 # End Of Children Mark (DW_TAG_subprogram) + +.Ldebug_info_void_ptr: + .byte 7 # Abbrev [7] DW_TAG_pointer_type +.Ldebug_info_int: + .byte 8 # Abbrev [8] DW_TAG_base_type + .long .Linfo_string_int # DW_AT_name + .byte 5 # DW_AT_encoding + .byte 4 # DW_AT_byte_size + + .byte 0 # End Of Children Mark (DW_TAG_compile_unit) +.Ldebug_info_end0: + .section .debug_str,"MS",@progbits,1 +.Linfo_string_fname: + .asciz "unused-inlined-params.c" +.Linfo_string_f: + .asciz "f" +.Linfo_string_unused1: + .asciz "unused1" +.Linfo_string_used: + .asciz "used" +.Linfo_string_int: + .asciz "int" +.Linfo_string_unused2: + .asciz "unused2" +.Linfo_string_partial: + .asciz "partial" +.Linfo_string_unused3: + .asciz "unused3" +.Linfo_string_main: + .asciz "main" +.Linfo_string_g: + .asciz "g" +.Linfo_string_unused: + .asciz "unused" +.Linfo_string_other: + .asciz "other" + .section ".note.GNU-stack","",@progbits + .addrsig + .section .debug_line,"",@progbits +.Lline_table_start0: diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/unused-inlined-params.test b/lldb/test/Shell/SymbolFile/DWARF/x86/unused-inlined-params.test new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/x86/unused-inlined-params.test @@ -0,0 +1,48 @@ +# RUN: llvm-mc -filetype=obj %S/Inputs/unused-inlined-params.s \ +# RUN: -triple x86_64-pc-linux -o %t.o +# RUN: %lldb %t.o -s %s -o exit | FileCheck %s + + +# In this test we verify that inlined functions still mention +# all their parameters in `frame variable`, even when those +# parameters were completely optimized away from the concrete +# instance of the inlined function in the debug info. +# The debugger should look up the parameters in the abstract +# origin of the concrete instance. + +# Let us check that unused parameters of an inlined function are listed +# at the inlined function entry. +image lookup -v -s break_at_inlined_f_in_main +# CHECK-LABEL: image lookup -v -s break_at_inlined_f_in_main +# CHECK: name = "unused1", type = "void *", location = +# CHECK: name = "used", type = "int", location = DW_OP_consts +42 +# CHECK: name = "unused2", type = "int", location = +# CHECK: name = "partial", type = "int", location = DW_OP_reg4 RSI +# CHECK: name = "unused3", type = "int", location = + +# Show variables outsid of the live range of the 'partial' parameter +# and verify that the output is as expected. +image lookup -v -s break_at_inlined_f_in_main_between_printfs +# CHECK-LABEL: image lookup -v -s break_at_inlined_f_in_main_between_printfs +# CHECK: name = "unused1", type = "void *", location = +# CHECK: name = "used", type = "int", location = DW_OP_reg3 RBX +# CHECK: name = "unused2", type = "int", location = +# Note: image lookup does not show variables outside of their +# location, so |partial| is missing here. +# CHECK-NOT: partial +# CHECK: name = "unused3", type = "int", location = + +# Check that we show parameters even if all of them are compiled away. +image lookup -v -s break_at_inlined_g_in_main +# CHECK-LABEL: image lookup -v -s break_at_inlined_g_in_main +# CHECK: name = "unused", type = "int", location = + +# Check that even the other inlined instance of f displays the correct +# parameters. +image lookup -v -s break_at_inlined_f_in_other +# CHECK-LABEL: image lookup -v -s break_at_inlined_f_in_other +# CHECK: name = "unused1", type = "void *", location = +# CHECK: name = "used", type = "int", location = DW_OP_consts +1 +# CHECK: name = "unused2", type = "int", location = +# CHECK: name = "partial", type = "int", location = DW_OP_consts +2 +# CHECK: name = "unused3", type = "int", location =