diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/infrun.c | 2 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/next-bp-other-thread.c | 45 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/next-bp-other-thread.exp | 54 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/schedlock.c | 24 | ||||
-rw-r--r-- | gdb/testsuite/gdb.threads/schedlock.exp | 250 |
5 files changed, 254 insertions, 121 deletions
diff --git a/gdb/infrun.c b/gdb/infrun.c index e25f392..b950b74 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -5462,7 +5462,7 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs) stepping, then scheduler locking can't be in effect, otherwise we wouldn't have resumed the current event thread in the first place. */ - gdb_assert (!schedlock_applies (1)); + gdb_assert (!schedlock_applies (currently_stepping (tp))); stepping_thread = tp; } diff --git a/gdb/testsuite/gdb.threads/next-bp-other-thread.c b/gdb/testsuite/gdb.threads/next-bp-other-thread.c new file mode 100644 index 0000000..e931305 --- /dev/null +++ b/gdb/testsuite/gdb.threads/next-bp-other-thread.c @@ -0,0 +1,45 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2014 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 <http://www.gnu.org/licenses/>. */ + +#include <pthread.h> +#include <unistd.h> +#include <stdlib.h> + +/* Always zero, used in breakpoint condition. */ +volatile int global_zero; + +void * +child_function (void *arg) +{ + while (1) + { + usleep (1); /* set breakpoint child here */ + } + + pthread_exit (NULL); +} + +int +main (void) +{ + pthread_t child_thread; + int res; + + res = pthread_create (&child_thread, NULL, child_function, NULL); + sleep (2); /* set wait-thread breakpoint here */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.threads/next-bp-other-thread.exp b/gdb/testsuite/gdb.threads/next-bp-other-thread.exp new file mode 100644 index 0000000..c2361fa --- /dev/null +++ b/gdb/testsuite/gdb.threads/next-bp-other-thread.exp @@ -0,0 +1,54 @@ +# Copyright (C) 2014 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 <http://www.gnu.org/licenses/>. + +# Test that GDB behaves correctly when a "next" steps over a call, and +# another thread hits a breakpoint that doesn't cause a user visible +# stop (and so needs to be stepped over). GDB used to trip on an +# invalid assertion - PR17408. + +standard_testfile + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] == -1} { + return -1 +} + +# Test all "set scheduler-locking" variants. +foreach schedlock {"off" "step" "on" } { + with_test_prefix "schedlock=$schedlock" { + clean_restart $binfile + + if ![runto_main] { + continue + } + + gdb_breakpoint [gdb_get_line_number "set wait-thread breakpoint here"] + gdb_continue_to_breakpoint "run to wait-thread breakpoint" + gdb_test "info threads" "2 .*\\\* 1.*" "info threads shows all threads" + + delete_breakpoints + + gdb_breakpoint [gdb_get_line_number "set breakpoint child here"] + # Give it a condition that always fails. + gdb_test "condition \$bpnum global_zero == 1" ".*" + + gdb_test_no_output "set scheduler-locking $schedlock" + + # While stepping over the sleep call, the other thread hits a + # breakpoint that doesn't cause a user visible stop (and so + # needs to be stepped over). The next should complete as if + # that breakpoint never triggered. + gdb_test "next" "EXIT_SUCCESS.*" "next over function call" + } +} diff --git a/gdb/testsuite/gdb.threads/schedlock.c b/gdb/testsuite/gdb.threads/schedlock.c index 2d8c7ab..11e4bbc 100644 --- a/gdb/testsuite/gdb.threads/schedlock.c +++ b/gdb/testsuite/gdb.threads/schedlock.c @@ -48,6 +48,28 @@ int main() { exit(EXIT_SUCCESS); } +void some_function (void) { + /* Sleep a bit to give the other threads a chance to run, if not + locked. This also ensure that even if the compiler optimizes out + or inlines some_function, there's still be some function that + needs to be stepped over. */ + usleep (1); +} + +/* When testing "next", this is set to have the loop call + some_function, which GDB should step over. When testing "step", + that would step into the function, which is not what we want. */ +volatile int call_function = 0; + +/* Call some_function if CALL_FUNCTION is set. This is wrapped in a + macro so that it's a single source line in the main loop. */ +#define MAYBE_CALL_SOME_FUNCTION() \ + do \ + { \ + if (call_function) \ + some_function (); \ + } while (0) + void *thread_function(void *arg) { int my_number = (long) arg; int *myp = (int *) &args[my_number]; @@ -56,7 +78,7 @@ void *thread_function(void *arg) { while (*myp > 0) { /* schedlock.exp: main loop. */ - (*myp) ++; + MAYBE_CALL_SOME_FUNCTION(); (*myp) ++; } pthread_exit(NULL); diff --git a/gdb/testsuite/gdb.threads/schedlock.exp b/gdb/testsuite/gdb.threads/schedlock.exp index c32ed0c..ff20763 100644 --- a/gdb/testsuite/gdb.threads/schedlock.exp +++ b/gdb/testsuite/gdb.threads/schedlock.exp @@ -30,8 +30,10 @@ if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executab # Now we can proceed with the real testing. -proc get_args { } { - global list_count +# Get the current contents of the `args` array in the test program. +# Description is appended to the test message. + +proc get_args { description } { global gdb_prompt global NUM @@ -40,10 +42,10 @@ proc get_args { } { append pattern ", (\[0-9\]+)" } - gdb_test_multiple "print args" "listed args ($list_count)" { + set test "listed args ($description)" + gdb_test_multiple "print args" $test { -re "\\\$\[0-9\]+ = {$pattern}.*$gdb_prompt $" { - set list_count [expr $list_count + 1] - pass "listed args ($list_count)" + pass $test set result "" for {set i 1} {[expr $i <= $NUM]} {incr i} { @@ -75,46 +77,62 @@ proc stop_process { description } { proc get_current_thread { description } { global gdb_prompt - gdb_test_multiple "bt" "$description" { + set test "find current thread ($description)" + + gdb_test_multiple "bt" $test { -re "thread_function \\(arg=0x(\[0-9\])\\).*$gdb_prompt $" { - pass $description + pass $test return $expect_out(1,string) } } return "" } +# Make sure we're stopped in the loop, in one of the non-main threads. + +proc goto_loop { msg } { + gdb_breakpoint [concat [gdb_get_line_number "schedlock.exp: main loop"] " if arg != 0"] + + set test "return to loop" + if {$msg != ""} { + set test "$test ($msg)" + } + gdb_continue_to_breakpoint $test + delete_breakpoints +} + proc my_continue { msg } { - gdb_test_multiple "continue" "continuing ($msg)" { + set test "continue ($msg)" + gdb_test_multiple "continue" $test { -re "Continuing" { - pass "continue ($msg)" + pass $test } } stop_process "stop all threads ($msg)" - # Make sure we're in one of the non-main looping threads. - gdb_breakpoint [concat [gdb_get_line_number "schedlock.exp: main loop"] " if arg != 0"] - gdb_continue_to_breakpoint "return to loop ($msg)" - delete_breakpoints + goto_loop $msg } -proc step_ten_loops { msg } { +# Use CMD to step the loop 10 times. CMD may be "step" or "next". + +proc step_ten_loops { cmd } { global gdb_prompt for {set i 0} {[expr $i < 10]} {set i [expr $i + 1]} { set other_step 0 - gdb_test_multiple "step" "step to increment ($msg $i)" { + set test "$cmd to increment ($i)" + gdb_test_multiple $cmd $test { -re ".*myp\\) \\+\\+;\[\r\n\]+$gdb_prompt $" { - pass "step to increment ($msg $i)" + pass $test } -re "$gdb_prompt $" { if {$other_step == 0} { set other_step 1 - send_gdb "step\n" + send_gdb "$cmd\n" exp_continue } else { - fail "step to increment ($msg $i)" + fail $test # FIXME cascade? } } @@ -158,15 +176,12 @@ gdb_test_multiple "set scheduler-locking off" "scheduler locking set to none" { gdb_breakpoint [gdb_get_line_number "schedlock.exp: last thread start"] gdb_continue_to_breakpoint "all threads started" -global list_count -set list_count 0 - -set start_args [get_args] +set start_args [get_args "before initial"] # First make sure that all threads are alive. my_continue "initial" -set cont_args [get_args] +set cont_args [get_args "after initial"] set bad 0 for {set i 0} {[expr $i < $NUM]} {set i [expr $i + 1]} { @@ -180,125 +195,122 @@ if { $bad == 0 } { fail "all threads alive ($bad/$NUM did not run)" } -# We can't change threads, unfortunately, in current GDB. Use -# whichever we stopped in. -set curthread [get_current_thread "find current thread (1)"] - - - +# Compare the previous thread and args with the current thread and +# args. Check that we didn't switch threads, and that the threads +# incremented their args counter the amounts expected. CMD is the +# command being tested. BEFORE_THREAD is the thread that was selected +# before the command was run. BEFORE_ARGS is the value of the +# thread's args before the command was run. LOCKED indicates whether +# we expect threads other than the selected thread remained locked. -# Test stepping without scheduler locking. -gdb_test_no_output "set scheduler-locking off" +proc check_result { cmd before_thread before_args locked } { + global NUM -step_ten_loops "unlocked" + # Make sure we're still in the same thread. + set newthread [get_current_thread "after"] -# Make sure we're still in the same thread. -set newthread [get_current_thread "find current thread (2)"] -if {$curthread == $newthread} { - pass "step without lock does not change thread" -} else { - fail "step without lock does not change thread (switched to thread $newthread)" -} + set test "$cmd does not change thread" + if {$before_thread == $newthread} { + pass "$test" + } else { + fail "$test (switched to thread $newthread)" + } -set start_args $cont_args -set cont_args [get_args] + set after_args [get_args "after"] -set num_other_threads 0 -for {set i 0} {[expr $i < $NUM]} {set i [expr $i + 1]} { - if {[lindex $start_args $i] == [lindex $cont_args $i]} { - if {$i == $curthread} { - fail "current thread stepped (didn't run)" + set test "current thread advanced" + if { $locked } { + set test "$test - locked" + } else { + set test "$test - unlocked" } - } else { - if {$i == $curthread} { - if {[lindex $start_args $i] == [expr [lindex $cont_args $i] - 10]} { - pass "current thread stepped" + + set num_other_threads 0 + for {set i 0} {$i < $NUM} {incr i} { + if {[lindex $before_args $i] == [lindex $after_args $i]} { + if {$i == $before_thread} { + fail "$test (didn't run)" + } } else { - fail "current thread stepped (wrong amount)" + if {$i == $before_thread} { + if {$cmd == "continue" + || [lindex $before_args $i] == [expr [lindex $after_args $i] - 10]} { + pass "$test" + } else { + fail "$test (wrong amount)" + } + } else { + incr num_other_threads + } } + } + + if { $locked } { + gdb_assert {$num_other_threads == 0} "other threads didn't run - locked" } else { - set num_other_threads [expr $num_other_threads + 1] + gdb_assert {$num_other_threads > 0} "other threads ran - unlocked" } - } -} -if {$num_other_threads > 0} { - pass "other threads ran - unlocked" -} else { - fail "other threads ran - unlocked" } -# Test continue with scheduler locking -gdb_test "set scheduler-locking on" "" +with_test_prefix "schedlock=on: cmd=continue" { + # Use whichever we stopped in. + set curthread [get_current_thread "before"] -my_continue "with lock" + # Test continue with scheduler locking. + gdb_test "set scheduler-locking on" "" -# Make sure we're still in the same thread. -set newthread [get_current_thread "find current thread (3)"] -if {$curthread == $newthread} { - pass "continue with lock does not change thread" -} else { - fail "continue with lock does not change thread (switched to thread $newthread)" + my_continue "with lock" + + check_result "continue" $curthread $cont_args 1 } -set start_args $cont_args -set cont_args [get_args] +# Test stepping/nexting with different modes of scheduler locking. +proc test_step { schedlock cmd call_function } { + global NUM -set num_other_threads 0 -for {set i 0} {[expr $i < $NUM]} {set i [expr $i + 1]} { - if {[lindex $start_args $i] == [lindex $cont_args $i]} { - if {$i == $curthread} { - fail "current thread ran (didn't run)" - } - } else { - if {$i == $curthread} { - pass "current thread ran" - } else { - incr num_other_threads + gdb_test_no_output "set scheduler-locking off" + goto_loop "" + + set curthread [get_current_thread "before"] + + # No need to set to off again. This avoids a duplicate message. + if {$schedlock != "off"} { + gdb_test_no_output "set scheduler-locking $schedlock" } - } -} -if {$num_other_threads > 0} { - fail "other threads didn't run - locked" -} else { - pass "other threads didn't run - locked" -} -# Test stepping with scheduler locking -step_ten_loops "locked" + gdb_test "print call_function = $call_function" \ + " = $call_function" -# Make sure we're still in the same thread. -set newthread [get_current_thread "find current thread (2)"] -if {$curthread == $newthread} { - pass "step with lock does not change thread" -} else { - fail "step with lock does not change thread (switched to thread $newthread)" -} + set before_args [get_args "before"] -set start_args $cont_args -set cont_args [get_args] + step_ten_loops $cmd -set num_other_threads 0 -for {set i 0} {[expr $i < $NUM]} {set i [expr $i + 1]} { - if {[lindex $start_args $i] == [lindex $cont_args $i]} { - if {$i == $curthread} { - fail "current thread stepped locked (didn't run)" - } - } else { - if {$i == $curthread} { - if {[lindex $start_args $i] == [expr [lindex $cont_args $i] - 10]} { - pass "current thread stepped locked" - } else { - fail "current thread stepped locked (wrong amount)" - } + # "next" lets other threads run while stepping over functions. + if { $schedlock == "on" || ($schedlock == "step" && !$call_function) } { + set locked 1 } else { - incr num_other_threads + set locked 0 } - } -} -if {$num_other_threads > 0} { - fail "other threads didn't run - step locked" -} else { - pass "other threads didn't run - step locked" + + check_result $cmd $curthread $before_args $locked } -return 0 +# Test stepping/nexting with different modes of scheduler locking. +foreach schedlock {"off" "step" "on"} { + with_test_prefix "schedlock=$schedlock" { + with_test_prefix "cmd=step" { + test_step $schedlock "step" 0 + } + with_test_prefix "cmd=next" { + # With "next", and schedlock "step", threads run unlocked + # when stepping over a function call. This exercises both + # with and without a function call. Without a function + # call "next" should behave just like "step". + foreach call_function {0 1} { + with_test_prefix "call_function=$call_function" { + test_step $schedlock "next" $call_function + } + } + } + } +} |