From patchwork Tue Jun 26 06:57:06 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Buettner X-Patchwork-Id: 28045 Received: (qmail 53181 invoked by alias); 26 Jun 2018 06:57:12 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 53167 invoked by uid 89); 26 Jun 2018 06:57:11 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-25.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_LAZY_DOMAIN_SECURITY, KAM_SHORT, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=bars X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Tue, 26 Jun 2018 06:57:08 +0000 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id CECCC344514 for ; Tue, 26 Jun 2018 06:57:07 +0000 (UTC) Received: from pinnacle.lan (ovpn-117-99.phx2.redhat.com [10.3.117.99]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 96FCA1001F5C for ; Tue, 26 Jun 2018 06:57:07 +0000 (UTC) Date: Mon, 25 Jun 2018 23:57:06 -0700 From: Kevin Buettner To: gdb-patches@sourceware.org Subject: [PATCH 8/8] Test case for functions with non-contiguous ranges Message-ID: <20180625235706.5efafbfd@pinnacle.lan> In-Reply-To: <20180625233239.49dc52ea@pinnacle.lan> References: <20180625233239.49dc52ea@pinnacle.lan> MIME-Version: 1.0 X-IsSubscribed: yes 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/gdb.dwarf2/dw2-ranges-func.c | 78 +++++ gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp | 410 +++++++++++++++++++++++++++ 2 files changed, 488 insertions(+) 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..6adb471 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/dw2-ranges-func.exp @@ -0,0 +1,410 @@ +# 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]} { + verbose "Skipping DW_AT_ranges test." + return 0 +} + +# The .c files use __attribute__. +if [get_compiler_info] { + return -1 +} +if !$gcc_compiled { + verbose "Skipping DW_AT_ranges 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 ranges_label ranges2_label L + set int_size [get_sizeof "int" 4] + + # Find start address and length for our functions. + set main_func \ + [function_range main [list ${srcdir}/${subdir}/$srcfile]] + set foo_func \ + [function_range foo [list ${srcdir}/${subdir}/$srcfile]] + set foo_low_func \ + [function_range foo_low [list ${srcdir}/${subdir}/$srcfile]] + set bar_func \ + [function_range bar [list ${srcdir}/${subdir}/$srcfile]] + set baz_func \ + [function_range baz [list ${srcdir}/${subdir}/$srcfile]] + + set main_start [lindex $main_func 0] + set main_len [lindex $main_func 1] + set foo_start [lindex $foo_func 0] + set foo_end {$foo_start + [lindex $foo_func 1]} + set foo_low_start [lindex $foo_low_func 0] + set foo_low_len [lindex $foo_low_func 1] + set foo_low_end {$foo_low_start + $foo_low_len} + set bar_start [lindex $bar_func 0] + set bar_len [lindex $bar_func 1] + set baz_start [lindex $baz_func 0] + set baz_len [lindex $baz_func 1] + + 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 ${ranges2_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 ${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. I attempted to make it reasonably + # accurate as it made debugging the test case easier. + program { + {DW_LNE_set_address [lindex $main_func 0]} + {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 [lindex $main_func 0]+[lindex $main_func 1]} + {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 [lindex $foo_func 0]} + {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 [lindex $foo_func 0]+[lindex $foo_func 1]} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address [lindex $bar_func 0]} + {DW_LNS_advance_line [expr [gdb_get_line_number "bar end"] - 1]} + {DW_LNS_copy} + {DW_LNS_advance_pc [lindex $bar_func 1]} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address [lindex $baz_func 0]} + {DW_LNS_advance_line [expr [gdb_get_line_number "baz end"] - 1]} + {DW_LNS_copy} + {DW_LNS_advance_pc [lindex $baz_func 1]} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address [lindex $foo_low_func 0]} + {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 [lindex $foo_low_func 0]+[lindex $foo_low_func 1]} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + } + } + + # Generate ranges data. + ranges {is_64 [is_64_target]} { + ranges_label: sequence { + {range {$foo_start } $foo_end} + {range {$foo_low_start} $foo_low_end} + } + ranges2_label: sequence { + {range {$foo_start } $foo_end} + {range {$foo_low_start} $foo_low_end} + {range {$main_start} $main_start + $main_len} + {range {$bar_start} $bar_start + $bar_len} + {range {$baz_start} $baz_start + $baz_len} + } + } +} + +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 .*" + +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" + +delete_breakpoints +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 (2nd time)" + +gdb_test "step" \ + "foo \\(\\).*foo foo_low call.*" \ + "step out of bar, back into foo (2nd time)" + +delete_breakpoints +if ![runto_main] { + return -1 +} + +# Disassembly of foo should have multiple address ranges. +# Note: I couldn't get $hex to work in the gdb_test_sequence regex. +gdb_test_sequence "disassemble foo" "" { + "Dump of assembler code for function foo:" + "Address range 0x[\\dabcdefABCDEF]+ to 0x[\\dabcdefABCDEF]+:" + " 0x[\\dabcdefABCDEF]+ <\\+0>:" + "Address range 0x[\\dabcdefABCDEF]+ to 0x[\\dabcdefABCDEF]+:" + " 0x[\\dabcdefABCDEF]+ <(.+?)>:" + "End of assembler dump\\." +} + +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 test "x/i foo" +gdb_test_multiple $test $test { + -re " ($hex) .*${gdb_prompt}" { + set foo_addr $expect_out(1,string) + pass $test + } +} + +set test "foo and foo_low are at different addresses" +if {$foo_low_addr == $foo_addr} { + fail $test +} else { + pass $test +} + +# This more permissive RE for "break foo" will allow a breakpoint on +# multiple locations to PASS. */ +gdb_test "break foo" \ + "Breakpoint.*at.*" \ + "break foo (2nd time)" + +gdb_test "break baz" \ + "Breakpoint.*at.* file .*$srcfile, line \\d+\\." + +gdb_test "continue" \ + "Breakpoint \\d+, foo \\(\\).*" \ + "Continue to foo (2nd time)" + +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" + +delete_breakpoints +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" work with some versions +# of gcc, though it's not clear to me that they should. This example, +# which causes foo_low 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. If a subroutine +# call were used, it would be considered to be a separate subprogram +# and the issues that I see wouldn't be encountered. +# +# For the moment though, I've left these tests in place, but disabled, +# in the event that 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"