Browse Source
In the previous commits I have been working on improving inferior function call support. One thing that worries me about using inferior function calls from a conditional breakpoint is: what happens if the inferior function call fails? If the failure is obvious, e.g. the thread performing the call crashes, or hits a breakpoint, then this case is already well handled, and the error is reported to the user. But what if the thread performing the inferior call just deadlocks? If the user made the call from a 'print' or 'call' command, then the user might have some expectation of when the function call should complete, and, when this time limit is exceeded, the user will (hopefully) interrupt GDB and regain control of the debug session. But, when the inferior function call is from a breakpoint condition it is much harder to understand that GDB is deadlocked within an inferior call. Maybe the breakpoint hasn't been hit yet? Or maybe the condition was always false? Or maybe GDB is deadlocked in an inferior call? The only way to know for sure is for the user to periodically interrupt the inferior, check on the state of all the threads, and then continue. Additionally, the focus of the previous commit was inferior function calls, from a conditional breakpoint, in a multi-threaded inferior. This opens up a whole new set of potential failure conditions. For example, what if the function called relies on interaction with some other thread, and the other thread crashes? Or hits a breakpoint? Given how inferior function calls work (in a synchronous manner), a stop event in some other thread is going to be ignored while the inferior function call is being executed as part of a breakpoint condition, and this means that GDB could get stuck waiting for the original condition thread, which will now never complete. In this commit I propose a solution to this problem. A timeout. For targets that support async-mode we can install an event-loop timer before starting the inferior function call. When the timer expires we will stop the thread performing the inferior function call. With this mechanism in place a user can be sure that any inferior call they make will either complete, or timeout eventually. Adding a timer like this is obviously a change in behaviour for the more common 'call' and 'print' uses of inferior function calls, so, in this patch, I propose having two different timers. One I call the 'direct-call-timeout', which is used for 'call' and 'print' commands. This timeout is by default set to unlimited, which, not surprisingly, means there is no timeout in place. A second timer, which I've called 'indirect-call-timeout', is used for inferior function calls from breakpoint conditions. This timeout has a default value of 30 seconds. This is a reasonably long time to wait, and hopefully should be enough in most cases to allow the inferior call to complete. An inferior call that takes more than 30 seconds, which is installed on a breakpoint condition is really going to slow down the debug session, so hopefully this is not a common use case. The user is, of course, free to reduce, or increase the timeout value, and can always use Ctrl-c to interrupt an inferior function call, but this timeout will ensure that GDB will stop at some point. The new commands added by this commit are: set direct-call-timeout SECONDS show direct-call-timeout set indirect-call-timeout SECONDS show indirect-call-timeout These new timeouts do depend on async-mode, so, if async-mode is disabled (maint set target-async off), or not supported (e.g. target sim), then the timeout is treated as unlimited (that is, no timeout is set). For targets that "fake" non-async mode, e.g. Linux native, where non-async mode is really just async mode, but then we park the target in a sissuspend, we could easily fix things so that the timeouts still work, however, for targets that really are not async aware, like the simulator, fixing things so that timeouts work correctly would be a much bigger task - that effort would be better spent just making the target async-aware. And so, I'm happy for now that this feature will only work on async targets. The two new show commands will display slightly different text if the current target is a non-async target, which should allow users to understand what's going on. There's a somewhat random test adjustment needed in gdb.base/help.exp, the test uses a regexp with the apropos command, and expects to find a single result. Turns out the new settings I added also matched the regexp, which broke the test. I've updated the regexp a little to exclude my new settings. Reviewed-By: Tankut Baris Aktemur <tankut.baris.aktemur@intel.com> Reviewed-By: Eli Zaretskii <eliz@gnu.org> Tested-By: Luis Machado <luis.machado@arm.com> Tested-By: Keith Seitz <keiths@redhat.com>master
8 changed files with 756 additions and 5 deletions
@ -0,0 +1,36 @@ |
|||
/* Copyright 2022-2024 Free Software Foundation, Inc.
|
|||
|
|||
This file is part of GDB. |
|||
|
|||
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 <unistd.h> |
|||
|
|||
/* This function is called from GDB. */ |
|||
int |
|||
function_that_never_returns () |
|||
{ |
|||
while (1) |
|||
sleep (1); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
int |
|||
main () |
|||
{ |
|||
alarm (300); |
|||
|
|||
return 0; |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
# Copyright 2022-2024 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 GDB's direct-call-timeout setting, that is, ensure that if an |
|||
# inferior function call, invoked from e.g. a 'print' command, takes |
|||
# too long, then GDB can interrupt it, and return control to the user. |
|||
|
|||
standard_testfile |
|||
|
|||
if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ |
|||
{debug}] == -1 } { |
|||
return |
|||
} |
|||
|
|||
# Start GDB according to TARGET_ASYNC, TARGET_NON_STOP, and NON_STOP, |
|||
# then adjust the direct-call-timeout, and make an inferior function |
|||
# call that will never return. GDB should eventually timeout and stop |
|||
# the inferior. |
|||
proc run_test { target_async target_non_stop non_stop } { |
|||
save_vars { ::GDBFLAGS } { |
|||
append ::GDBFLAGS \ |
|||
" -ex \"maint set target-non-stop $target_non_stop\"" |
|||
append ::GDBFLAGS \ |
|||
" -ex \"set non-stop $non_stop\"" |
|||
append ::GDBFLAGS \ |
|||
" -ex \"maintenance set target-async ${target_async}\"" |
|||
|
|||
clean_restart ${::binfile} |
|||
} |
|||
|
|||
if {![runto_main]} { |
|||
return |
|||
} |
|||
|
|||
gdb_test_no_output "set direct-call-timeout 5" |
|||
|
|||
# When non-stop mode is off we get slightly different output from GDB. |
|||
if { ([target_info gdb_protocol] == "remote" |
|||
|| [target_info gdb_protocol] == "extended-remote") |
|||
&& !$target_non_stop } { |
|||
set stopped_line_pattern "Program received signal SIGINT, Interrupt\\." |
|||
} else { |
|||
set stopped_line_pattern "Program stopped\\." |
|||
} |
|||
|
|||
gdb_test "print function_that_never_returns ()" \ |
|||
[multi_line \ |
|||
$stopped_line_pattern \ |
|||
".*" \ |
|||
"The program being debugged timed out while in a function called from GDB\\." \ |
|||
"GDB remains in the frame where the timeout occurred\\." \ |
|||
"To change this behavior use \"set unwind-on-timeout on\"\\." \ |
|||
"Evaluation of the expression containing the function" \ |
|||
"\\(function_that_never_returns\\) will be abandoned\\." \ |
|||
"When the function is done executing, GDB will silently stop\\."] |
|||
|
|||
gdb_test "bt" ".* function_that_never_returns .*<function called from gdb>.*" |
|||
} |
|||
|
|||
foreach_with_prefix target_async { "on" "off" } { |
|||
|
|||
if { !$target_async } { |
|||
# GDB can't timeout while waiting for a thread if the target |
|||
# runs with async-mode turned off; once the target is running |
|||
# GDB is effectively blocked until the target stops for some |
|||
# reason. |
|||
continue |
|||
} |
|||
|
|||
foreach_with_prefix target_non_stop { "on" "off" } { |
|||
foreach_with_prefix non_stop { "on" "off" } { |
|||
if { $non_stop && !$target_non_stop } { |
|||
# It doesn't make sense to operate GDB in non-stop |
|||
# mode when the target has (in theory) non-stop mode |
|||
# disabled. |
|||
continue |
|||
} |
|||
|
|||
run_test $target_async $target_non_stop $non_stop |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
/* This testcase is part of GDB, the GNU debugger.
|
|||
|
|||
Copyright 2022-2024 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 <stdio.h> |
|||
#include <pthread.h> |
|||
#include <unistd.h> |
|||
#include <stdlib.h> |
|||
#include <errno.h> |
|||
#include <semaphore.h> |
|||
|
|||
#define NUM_THREADS 5 |
|||
|
|||
/* Semaphores, used to track when threads have started, and to control
|
|||
when the threads finish. */ |
|||
sem_t startup_semaphore; |
|||
sem_t finish_semaphore; |
|||
sem_t thread_1_semaphore; |
|||
sem_t thread_2_semaphore; |
|||
|
|||
/* Mutex to control when the first worker thread hit a breakpoint
|
|||
location. */ |
|||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
|||
|
|||
/* Global variable to poke, just so threads have something to do. */ |
|||
volatile int global_var = 0; |
|||
|
|||
int |
|||
condition_func () |
|||
{ |
|||
/* Let thread 2 run. */ |
|||
if (sem_post (&thread_2_semaphore) != 0) |
|||
abort (); |
|||
|
|||
/* Wait for thread 2 to complete its actions. */ |
|||
if (sem_wait (&thread_1_semaphore) != 0) |
|||
abort (); |
|||
|
|||
return 1; |
|||
} |
|||
|
|||
void |
|||
do_segfault () |
|||
{ |
|||
volatile int *p = 0; |
|||
*p = 0; /* Segfault here. */ |
|||
} |
|||
|
|||
void * |
|||
worker_func (void *arg) |
|||
{ |
|||
int tid = *((int *) arg); |
|||
|
|||
/* Let the main thread know that this worker has started. */ |
|||
if (sem_post (&startup_semaphore) != 0) |
|||
abort (); |
|||
|
|||
switch (tid) |
|||
{ |
|||
case 0: |
|||
/* Wait for MUTEX to become available, then pass through the
|
|||
conditional breakpoint location. */ |
|||
if (pthread_mutex_lock (&mutex) != 0) |
|||
abort (); |
|||
global_var = 99; /* Conditional breakpoint here. */ |
|||
if (pthread_mutex_unlock (&mutex) != 0) |
|||
abort (); |
|||
break; |
|||
|
|||
case 1: |
|||
if (sem_wait (&thread_2_semaphore) != 0) |
|||
abort (); |
|||
do_segfault (); |
|||
if (sem_post (&thread_1_semaphore) != 0) |
|||
abort (); |
|||
|
|||
/* Fall through. */ |
|||
default: |
|||
/* Wait until we are allowed to finish. */ |
|||
if (sem_wait (&finish_semaphore) != 0) |
|||
abort (); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void |
|||
stop_marker () |
|||
{ |
|||
global_var = 99; /* Stop marker. */ |
|||
} |
|||
|
|||
/* The main program entry point. */ |
|||
|
|||
int |
|||
main () |
|||
{ |
|||
pthread_t threads[NUM_THREADS]; |
|||
int args[NUM_THREADS]; |
|||
void *retval; |
|||
|
|||
/* An alarm, just in case the thread deadlocks. */ |
|||
alarm (300); |
|||
|
|||
/* Semaphore initialization. */ |
|||
if (sem_init (&startup_semaphore, 0, 0) != 0) |
|||
abort (); |
|||
if (sem_init (&finish_semaphore, 0, 0) != 0) |
|||
abort (); |
|||
if (sem_init (&thread_1_semaphore, 0, 0) != 0) |
|||
abort (); |
|||
if (sem_init (&thread_2_semaphore, 0, 0) != 0) |
|||
abort (); |
|||
|
|||
/* Lock MUTEX, this prevents the first worker thread from rushing ahead. */ |
|||
if (pthread_mutex_lock (&mutex) != 0) |
|||
abort (); |
|||
|
|||
/* Worker thread creation. */ |
|||
for (int i = 0; i < NUM_THREADS; i++) |
|||
{ |
|||
args[i] = i; |
|||
pthread_create (&threads[i], NULL, worker_func, &args[i]); |
|||
} |
|||
|
|||
/* Wait for every thread to start. */ |
|||
for (int i = 0; i < NUM_THREADS; i++) |
|||
{ |
|||
if (sem_wait (&startup_semaphore) != 0) |
|||
abort (); |
|||
} |
|||
|
|||
/* Unlock the first thread so it can proceed. */ |
|||
if (pthread_mutex_unlock (&mutex) != 0) |
|||
abort (); |
|||
|
|||
/* Wait for the first thread only. */ |
|||
pthread_join (threads[0], &retval); |
|||
|
|||
/* Now post FINISH_SEMAPHORE to allow all the other threads to finish. */ |
|||
for (int i = 1; i < NUM_THREADS; i++) |
|||
sem_post (&finish_semaphore); |
|||
|
|||
/* Now wait for the remaining threads to complete. */ |
|||
for (int i = 1; i < NUM_THREADS; i++) |
|||
pthread_join (threads[i], &retval); |
|||
|
|||
/* Semaphore cleanup. */ |
|||
sem_destroy (&finish_semaphore); |
|||
sem_destroy (&startup_semaphore); |
|||
sem_destroy (&thread_1_semaphore); |
|||
sem_destroy (&thread_2_semaphore); |
|||
|
|||
stop_marker (); |
|||
|
|||
return 0; |
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
# Copyright 2022-2024 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/>. |
|||
|
|||
# Tests inferior calls executed from a breakpoint condition in |
|||
# a multi-threaded program. |
|||
# |
|||
# This test has the inferior function call timeout, and checks how GDB |
|||
# handles this situation. |
|||
|
|||
standard_testfile |
|||
|
|||
if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \ |
|||
{debug pthreads}] } { |
|||
return |
|||
} |
|||
|
|||
set cond_bp_line [gdb_get_line_number "Conditional breakpoint here"] |
|||
set final_bp_line [gdb_get_line_number "Stop marker"] |
|||
set segfault_line [gdb_get_line_number "Segfault here"] |
|||
|
|||
# Setup GDB based on TARGET_ASYNC, TARGET_NON_STOP, and NON_STOP. |
|||
# Setup some breakpoints in the inferior, one of which has an inferior |
|||
# call within its condition. |
|||
# |
|||
# Continue GDB, the breakpoint with inferior call will be hit, but the |
|||
# inferior call will never return. We expect GDB to timeout. |
|||
# |
|||
# The reason that the inferior call never completes is that a second |
|||
# thread, on which the inferior call relies, either hits a breakpoint |
|||
# (when OTHER_THREAD_BP is true), or crashes (when OTHER_THREAD_BP is |
|||
# false). |
|||
proc run_test { target_async target_non_stop non_stop other_thread_bp } { |
|||
save_vars { ::GDBFLAGS } { |
|||
append ::GDBFLAGS " -ex \"maint set target-non-stop $target_non_stop\"" |
|||
append ::GDBFLAGS " -ex \"maint non-stop $non_stop\"" |
|||
append ::GDBFLAGS " -ex \"maintenance set target-async ${target_async}\"" |
|||
|
|||
clean_restart ${::binfile} |
|||
} |
|||
|
|||
if {![runto_main]} { |
|||
return |
|||
} |
|||
|
|||
# The default timeout for indirect inferior calls (e.g. inferior |
|||
# calls for conditional breakpoint expressions) is pretty high. |
|||
# We don't want the test to take too long, so reduce this. |
|||
# |
|||
# However, the test relies on a second thread hitting some event |
|||
# (either a breakpoint or signal) before this timeout expires. |
|||
# |
|||
# There is a chance that on a really slow system this might not |
|||
# happen, in which case the test might fail. |
|||
# |
|||
# However, we still allocate 5 seconds, which feels like it should |
|||
# be enough time in most cases, but maybe we need to do something |
|||
# smarter here? Possibly we could have some initial run where the |
|||
# inferior doesn't timeout, but does perform the same interaction |
|||
# between threads, we could time that, and use that as the basis |
|||
# for this timeout. For now though, we just hope 5 seconds is |
|||
# enough. |
|||
gdb_test_no_output "set indirect-call-timeout 5" |
|||
|
|||
gdb_breakpoint \ |
|||
"${::srcfile}:${::cond_bp_line} if (condition_func ())" |
|||
set bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ |
|||
"get number for conditional breakpoint"] |
|||
|
|||
gdb_breakpoint "${::srcfile}:${::final_bp_line}" |
|||
set final_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ |
|||
"get number for final breakpoint"] |
|||
|
|||
# The thread performing an inferior call relies on a second |
|||
# thread. The second thread will segfault unless it hits a |
|||
# breakpoint first. In either case the initial thread will not |
|||
# complete its inferior call. |
|||
if { $other_thread_bp } { |
|||
gdb_breakpoint "${::srcfile}:${::segfault_line}" |
|||
set segfault_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \ |
|||
"get number for segfault breakpoint"] |
|||
} |
|||
|
|||
# When non-stop mode is off we get slightly different output from GDB. |
|||
if { ([target_info gdb_protocol] == "remote" |
|||
|| [target_info gdb_protocol] == "extended-remote") |
|||
&& !$target_non_stop} { |
|||
set stopped_line_pattern "Thread ${::decimal} \"\[^\r\n\"\]+\" received signal SIGINT, Interrupt\\." |
|||
} else { |
|||
set stopped_line_pattern "Thread ${::decimal} \"\[^\r\n\"\]+\" stopped\\." |
|||
} |
|||
|
|||
gdb_test "continue" \ |
|||
[multi_line \ |
|||
$stopped_line_pattern \ |
|||
".*" \ |
|||
"Error in testing condition for breakpoint ${bp_num}:" \ |
|||
"The program being debugged timed out while in a function called from GDB\\." \ |
|||
"GDB remains in the frame where the timeout occurred\\." \ |
|||
"To change this behavior use \"set unwind-on-timeout on\"\\." \ |
|||
"Evaluation of the expression containing the function" \ |
|||
"\\(condition_func\\) will be abandoned\\." \ |
|||
"When the function is done executing, GDB will silently stop\\."] \ |
|||
"expected timeout waiting for inferior call to complete" |
|||
|
|||
# Remember that other thread that either crashed (with a segfault) |
|||
# or hit a breakpoint? Now that the inferior call has timed out, |
|||
# if we try to resume then we should see the pending event from |
|||
# that other thread. |
|||
if { $other_thread_bp } { |
|||
gdb_test "continue" \ |
|||
[multi_line \ |
|||
"Continuing\\." \ |
|||
".*" \ |
|||
"" \ |
|||
"Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${segfault_bp_num}, do_segfault \[^\r\n\]+:${::segfault_line}" \ |
|||
"${::decimal}\\s+\[^\r\n\]+Segfault here\[^\r\n\]+"] \ |
|||
"hit the segfault breakpoint" |
|||
} else { |
|||
gdb_test "continue" \ |
|||
[multi_line \ |
|||
"Continuing\\." \ |
|||
".*" \ |
|||
"Thread ${::decimal} \"infcall-from-bp\" received signal SIGSEGV, Segmentation fault\\." \ |
|||
"\\\[Switching to Thread \[^\r\n\]+\\\]" \ |
|||
"${::hex} in do_segfault \\(\\) at \[^\r\n\]+:${::segfault_line}" \ |
|||
"${::decimal}\\s+\[^\r\n\]+Segfault here\[^\r\n\]+"] \ |
|||
"hit the segfault" |
|||
} |
|||
} |
|||
|
|||
foreach_with_prefix target_async {"on" "off" } { |
|||
|
|||
if { !$target_async } { |
|||
# GDB can't timeout while waiting for a thread if the target |
|||
# runs with async-mode turned off; once the target is running |
|||
# GDB is effectively blocked until the target stops for some |
|||
# reason. |
|||
continue |
|||
} |
|||
|
|||
foreach_with_prefix target_non_stop {"off" "on"} { |
|||
foreach_with_prefix non_stop {"off" "on"} { |
|||
if { $non_stop && !$target_non_stop } { |
|||
# It doesn't make sense to operate GDB in non-stop |
|||
# mode when the target has (in theory) non-stop mode |
|||
# disabled. |
|||
continue |
|||
} |
|||
foreach_with_prefix other_thread_bp { true false } { |
|||
run_test $target_async $target_non_stop $non_stop $other_thread_bp |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue