From 6518e6562b56b258c4933fcb8bce057215544a8f Mon Sep 17 00:00:00 2001 From: Kevin Buettner Date: Fri, 24 Aug 2018 22:22:46 -0700 Subject: [PATCH] Test case for functions with non-contiguous ranges See comments in the new files for what this is about - I tried to explain it all there. gdb/testsuite/ChangeLog: * gdb.dwarf2/dw2-ranges-func.c: New file. * gdb.dwarf2/dw2-ranges-func.exp: New file. --- gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.dwarf2/dw2-ranges-func.c | 78 ++++++ gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp | 405 +++++++++++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-ranges-func.c create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 4d0c88e..d4510bc 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2018-08-24 Kevin Buettner + + * gdb.dwarf2/dw2-ranges-func.c: New file. + * gdb.dwarf2/dw2-ranges-func.exp: New file. + 2018-07-11 Sergio Durigan Junior PR c++/23373 diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.c b/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.c new file mode 100644 index 0000000..864803c --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.c @@ -0,0 +1,78 @@ +/* Copyright 2018 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* The idea here is to, via use of the dwarf assembler, create a function + which occupies two non-contiguous address ranges. + + foo_low and foo will be combined into a single function foo with a + function bar in between these two ranges. + + This test case was motivated by a bug in which a function which + occupied two non-contiguous address ranges was calling another + function which resides in between these ranges. So we end up with + a situation in which the low/start address of our constructed foo + (in this case) will be less than any of the addresses in bar, but + the high/end address of foo will be greater than any of bar's + addresses. + + This situation was causing a problem in the caching code of + find_pc_partial_function: When the low and high addresses of foo + are placed in the cache, the simple check that was used to see if + the cache was applicable would (incorrectly) succeed when presented + with an address in bar. I.e. an address in bar presented as an + input to find_pc_partial_function could produce the answer "this + address belongs to foo". */ + +volatile int e = 0; + +void +baz (void) +{ + asm ("baz_label: .globl baz_label"); +} /* baz end */ + +void +foo_low (void) +{ /* foo_low prologue */ + asm ("foo_low_label: .globl foo_low_label"); + baz (); /* foo_low baz call */ + asm ("foo_low_label2: .globl foo_low_label2"); +} /* foo_low end */ + +void +bar (void) +{ + asm ("bar_label: .globl bar_label"); +} /* bar end */ + +void +foo (void) +{ /* foo prologue */ + asm ("foo_label: .globl foo_label"); + bar (); /* foo bar call */ + asm ("foo_label2: .globl foo_label2"); + if (e) foo_low (); /* foo foo_low call */ + asm ("foo_label3: .globl foo_label3"); +} /* foo end */ + +int +main (void) +{ /* main prologue */ + asm ("main_label: .globl main_label"); + foo (); /* main foo call */ + asm ("main_label2: .globl main_label2"); + return 0; /* main return */ +} /* main end */ + diff --git a/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp b/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp new file mode 100644 index 0000000..6f1f2d2 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp @@ -0,0 +1,405 @@ +# Copyright 2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 a copy of the GNU General Public License +# along with this program. If not, see . +load_lib dwarf.exp + +# Test DW_AT_ranges in the context of a subprogram scope. + +# This test can only be run on targets which support DWARF-2 and use gas. +if {![dwarf2_support]} { + unsupported "dwarf2 support required for this test" + return 0 +} + +if [get_compiler_info] { + return -1 +} +if !$gcc_compiled { + unsupported "gcc required for this test" + return 0 +} + +standard_testfile dw2-ranges-func.c dw2-ranges-func-dw.S + +# We need to know the size of integer and address types in order to +# write some of the debugging info we'd like to generate. +# +# For that, we ask GDB by debugging our test program. Any program +# would do, but since we already have it specifically for this +# testcase, might as well use that. + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return -1 +} + +set asm_file [standard_output_file $srcfile2] +Dwarf::assemble $asm_file { + global srcdir subdir srcfile srcfile2 + declare_labels integer_label volatile_label func_ranges_label cu_ranges_label L + set int_size [get_sizeof "int" 4] + + # Find start address and length for our functions. + lassign [function_range main [list ${srcdir}/${subdir}/$srcfile]] \ + main_start main_len + set main_end "$main_start + $main_len" + lassign [function_range foo [list ${srcdir}/${subdir}/$srcfile]] \ + foo_start foo_len + set foo_end "$foo_start + $foo_len" + lassign [function_range foo_low [list ${srcdir}/${subdir}/$srcfile]] \ + foo_low_start foo_low_len + set foo_low_end "$foo_low_start + $foo_low_len" + lassign [function_range bar [list ${srcdir}/${subdir}/$srcfile]] \ + bar_start bar_len + set bar_end "$bar_start + $bar_len" + lassign [function_range baz [list ${srcdir}/${subdir}/$srcfile]] \ + baz_start baz_len + set baz_end "$baz_start + $baz_len" + + set e_var [gdb_target_symbol e] + + cu {} { + compile_unit { + {language @DW_LANG_C} + {name dw-ranges-func.c} + {stmt_list $L DW_FORM_sec_offset} + {low_pc 0 addr} + {ranges ${cu_ranges_label} DW_FORM_sec_offset} + } { + integer_label: DW_TAG_base_type { + {DW_AT_byte_size $int_size DW_FORM_sdata} + {DW_AT_encoding @DW_ATE_signed} + {DW_AT_name integer} + } + volatile_label: DW_TAG_volatile_type { + {type :$integer_label} + } + DW_TAG_variable { + {name e} + {external 1 flag} + {type :$volatile_label} + {location {addr $e_var} SPECIAL_expr} + } + subprogram { + {external 1 flag} + {name main} + {DW_AT_type :$integer_label} + {low_pc $main_start addr} + {high_pc $main_len DW_FORM_data4} + } + subprogram { + {external 1 flag} + {name foo} + {ranges ${func_ranges_label} DW_FORM_sec_offset} + } + subprogram { + {external 1 flag} + {name bar} + {low_pc $bar_start addr} + {high_pc $bar_len DW_FORM_data4} + } + subprogram { + {external 1 flag} + {name baz} + {low_pc $baz_start addr} + {high_pc $baz_len DW_FORM_data4} + } + } + } + + lines {version 2} L { + include_dir "${srcdir}/${subdir}" + file_name "$srcfile" 1 + + # Generate a line table program. An attempt was made to make it + # reasonably accurate as it made debugging the test case easier. + program { + {DW_LNE_set_address $main_start} + {DW_LNS_advance_line [expr [gdb_get_line_number "main prologue"] - 1]} + {DW_LNS_copy} + {DW_LNE_set_address main_label} + {DW_LNS_advance_line [expr [gdb_get_line_number "main foo call"] - [gdb_get_line_number "main prologue"]]} + {DW_LNS_copy} + {DW_LNE_set_address main_label2} + {DW_LNS_advance_line [expr [gdb_get_line_number "main return"] - [gdb_get_line_number "main foo call"]]} + {DW_LNS_copy} + {DW_LNE_set_address $main_end} + {DW_LNS_advance_line [expr [gdb_get_line_number "main end"] - [gdb_get_line_number "main return"] + 1]} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address $foo_start} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo prologue"] - 1] } + {DW_LNS_copy} + {DW_LNE_set_address foo_label} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo bar call"] - [gdb_get_line_number "foo prologue"]]} + {DW_LNS_copy} + {DW_LNE_set_address foo_label2} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo foo_low call"] - [gdb_get_line_number "foo bar call"]]} + {DW_LNS_copy} + {DW_LNE_set_address foo_label3} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo end"] - [gdb_get_line_number "foo foo_low call"]]} + {DW_LNS_copy} + {DW_LNE_set_address $foo_end} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address $bar_start} + {DW_LNS_advance_line [expr [gdb_get_line_number "bar end"] - 1]} + {DW_LNS_copy} + {DW_LNS_advance_pc $bar_len} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address $baz_start} + {DW_LNS_advance_line [expr [gdb_get_line_number "baz end"] - 1]} + {DW_LNS_copy} + {DW_LNS_advance_pc $baz_len} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address $foo_low_start} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo_low prologue"] - 1]} + {DW_LNS_copy} + {DW_LNE_set_address foo_low_label} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo_low baz call"] - [gdb_get_line_number "foo_low prologue"]]} + {DW_LNS_copy} + {DW_LNE_set_address foo_low_label2} + {DW_LNS_advance_line [expr [gdb_get_line_number "foo_low end"] - [gdb_get_line_number "foo_low baz call"]]} + {DW_LNS_copy} + {DW_LNE_set_address $foo_low_end} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + } + } + + # Generate ranges data. + ranges {is_64 [is_64_target]} { + func_ranges_label: sequence { + {range {$foo_start } $foo_end} + {range {$foo_low_start} $foo_low_end} + } + cu_ranges_label: sequence { + {range {$foo_start } $foo_end} + {range {$foo_low_start} $foo_low_end} + {range {$main_start} $main_end} + {range {$bar_start} $bar_end} + {range {$baz_start} $baz_end} + } + } +} + +if { [prepare_for_testing "failed to prepare" ${testfile} \ + [list $srcfile $asm_file] {nodebug}] } { + return -1 +} + +if ![runto_main] { + return -1 +} + +set main_prologue_line_num [gdb_get_line_number "main prologue"] +# Do a sanity check to make sure that line number info is available. +gdb_test "info line main" \ + "Line ${main_prologue_line_num} of .* starts at address .* and ends at .*" + +with_test_prefix "step-test-1" { + set bp_foo_bar [gdb_get_line_number "foo bar call"] + + gdb_test "break $bp_foo_bar" \ + "Breakpoint.*at.* file .*$srcfile, line $bp_foo_bar\\." \ + "break at call to bar" + + gdb_test "continue" \ + "Continuing\\..*Breakpoint \[0-9\]+, foo \\(\\).*$bp_foo_bar\\s+bar\\s\\(\\);.*foo bar call.*" \ + "continue to call of bar" + + gdb_test "step" \ + "bar \\(\\).*bar end.*" \ + "step into bar" + + gdb_test "step" \ + "foo \\(\\).*foo foo_low call.*" \ + "step out of bar, back into foo" +} + +with_test_prefix "step-test-2" { + clean_restart ${testfile} + if ![runto_main] { + return -1 + } + + # Note that the RE used for the following test will fail when the + # breakpoint has been set on multiple locations. E.g. "(2 locations)". + # This is intentional since that behavior is one of the bugs that + # this test case tests for. + gdb_test "break foo" \ + "Breakpoint.*at.* file .*$srcfile, line \\d+\\." \ + "break foo" + + # Continue to foo. Allow execution to stop either on the prologue + # or on the call to bar since either behavior is acceptable though + # the latter is preferred. + set test "continue to foo" + gdb_test_multiple "continue" $test { + -re "Breakpoint \\d+, foo \\(\\).*foo prologue.*${gdb_prompt}" { + pass $test + gdb_test "step" \ + "foo bar call .*" \ + "step to call of bar after landing on prologue" + } + -re "Breakpoint \\d+, foo \\(\\).*foo bar call.*${gdb_prompt}" { + pass $test + } + } + + gdb_test "step" \ + "bar \\(\\).*bar end.*" \ + "step into bar" + + gdb_test "step" \ + "foo \\(\\).*foo foo_low call.*" \ + "step out of bar, back into foo" +} + +clean_restart ${testfile} +if ![runto_main] { + return -1 +} + +# Disassembly of foo should have multiple address ranges. +gdb_test_sequence "disassemble foo" "" [list \ + "Dump of assembler code for function foo:" \ + "Address range $hex to $hex:" \ + " $hex <\\+0>:" \ + "Address range $hex to $hex:" \ + " $hex <(.+?)>:" \ + "End of assembler dump\\." \ +] + +set foo_low_addr -1 +set test "x/i foo_low" +gdb_test_multiple $test $test { + -re " ($hex) .*${gdb_prompt}" { + set foo_low_addr $expect_out(1,string) + pass $test + } +} + +set foo_addr -1 +set test "x/i foo" +gdb_test_multiple $test $test { + -re " ($hex) .*${gdb_prompt}" { + set foo_addr $expect_out(1,string) + pass $test + } +} + +gdb_assert {$foo_low_addr != $foo_addr} "foo and foo_low are at different addresses" + +# This more permissive RE for "break foo" will allow a breakpoint on +# multiple locations to PASS. */ +gdb_test "break foo" \ + "Breakpoint.*at.*" \ + "break foo" + +gdb_test "break baz" \ + "Breakpoint.*at.* file .*$srcfile, line \\d+\\." + +gdb_test "continue" \ + "Breakpoint \\d+, foo \\(\\).*" \ + "continue to foo" + +gdb_test_no_output "set variable e=1" + +# If GDB incorrectly places the foo breakpoint on multiple locations, +# then GDB will (incorrectly) stop in foo_low instead of in baz. +gdb_test "continue" \ + "Breakpoint \\d+, (?:$hex in )?baz \\(\\).*" \ + "continue to baz" + +with_test_prefix "step-test-3" { + clean_restart ${testfile} + if ![runto_main] { + return -1 + } + + gdb_test "step" \ + "foo \\(\\).*bar \\(\\);.*foo bar call.*" \ + "step into foo from main" + + gdb_test "step" \ + "bar \\(\\).*\}.* bar end.*" \ + "step into bar from foo" + + gdb_test "step" \ + "foo(_label2)? \\(\\).*foo_low \\(\\);.*foo foo_low call.*" \ + "step out of bar to foo" + + # The tests in the "enable_foo_low_stepping" section, below, work + # with some versions of gcc, though it's not clear that they + # should. This test case causes foo_low, originally a separate + # function invoked via a subroutine call, to be considered as part + # of foo via use of DW_AT_ranges. Real code that I've looked at + # uses a branch instruction to cause code in the "cold" range to + # be executed. + # + # For the moment though, these tests have been left in place, but + # disabled, in case we decide that making such a subroutine call + # is a reasonable thing to do that should also be supported by + # GDB. + + set enable_foo_low_stepping false + + if { $enable_foo_low_stepping } { + gdb_test_no_output "set variable e=1" + + set test "step into foo_low from foo" + gdb_test_multiple "step" $test { + -re "foo(_low)? \\(\\).*\{.*foo_low prologue.*${gdb_prompt}" { + pass $test + gdb_test "step" \ + "foo \\(\\).*baz \\(\\);.*foo_low baz call.*" \ + "step to baz call in foo_low" + + } + -re "foo(_low)? \\(\\).*baz \\(\\);.*foo_low baz call.*${gdb_prompt}" { + pass $test + } + } + + gdb_test "step" \ + "baz \\(\\).*\}.*baz end.*" \ + "step into baz from foo_low" + + gdb_test "step" \ + "foo(?:_low(?:_label2)?)? \\(\\).*\}.*foo_low end.*" \ + "step out of baz to foo_low" + + gdb_test "step" \ + "foo(?:_label3)? \\(\\).*\}.*foo end.*" \ + "step out of foo_low to foo" + } else { + gdb_test "next" \ + ".*foo end.*" \ + "next over foo_low call" + } + + gdb_test "step" \ + "main(?:_label2)? \\(\\).*" \ + "step out of foo to main" +} -- 2.7.4