summaryrefslogtreecommitdiff
path: root/gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch
diff options
context:
space:
mode:
Diffstat (limited to 'gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch')
-rw-r--r--gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch1148
1 files changed, 1148 insertions, 0 deletions
diff --git a/gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch b/gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch
new file mode 100644
index 0000000..3087f1d
--- /dev/null
+++ b/gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch
@@ -0,0 +1,1148 @@
+From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
+From: Andrew Burgess <aburgess@redhat.com>
+Date: Fri, 7 Oct 2022 12:39:07 +0100
+Subject: gdb-rhel-13298-inferior-funcall-bp-condition-3-of-5.patch
+
+;;gdb: add timeouts for inferior function calls
+;;(Andrew Burgess, RHEL-13298)
+
+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>
+
+diff --git a/gdb/NEWS b/gdb/NEWS
+--- a/gdb/NEWS
++++ b/gdb/NEWS
+@@ -1,6 +1,179 @@
+ What has changed in GDB?
+ (Organized release by release)
+
++*** Changes since GDB 14
++
++* The MPX commands "show/set mpx bound" have been deprecated, as Intel
++ listed MPX as removed in 2019.
++
++* Building GDB and GDBserver now requires a C++17 compiler.
++ For example, GCC 9 or later.
++
++* GDB index now contains information about the main function. This speeds up
++ startup when it is being used for some large binaries.
++
++* On hosts where threading is available, DWARF reading is now done in
++ the background, resulting in faster startup. This can be controlled
++ using "maint set dwarf synchronous".
++
++* Changed commands
++
++disassemble
++ Attempting to use both the 'r' and 'b' flags with the disassemble
++ command will now give an error. Previously the 'b' flag would
++ always override the 'r' flag.
++
++gcore
++generate-core-file
++ GDB now generates sparse core files, on systems that support it.
++
++maintenance info line-table
++ Add an EPILOGUE-BEGIN column to the output of the command. It indicates
++ if the line is considered the start of the epilgoue, and thus a point at
++ which the frame can be considered destroyed.
++
++* New commands
++
++info missing-debug-handler
++ List all the registered missing debug handlers.
++
++enable missing-debug-handler LOCUS HANDLER
++disable missing-debug-handler LOCUS HANDLER
++ Enable or disable a missing debug handler with a name matching the
++ regular expression HANDLER, in LOCUS.
++
++ LOCUS can be 'global' to operate on global missing debug handler,
++ 'progspace' to operate on handlers within the current program space,
++ or can be a regular expression which is matched against the filename
++ of the primary executable in each program space.
++
++maintenance info linux-lwps
++ List all LWPs under control of the linux-nat target.
++
++set remote thread-options-packet
++show remote thread-options-packet
++ Set/show the use of the thread options packet.
++
++set direct-call-timeout SECONDS
++show direct-call-timeout
++set indirect-call-timeout SECONDS
++show indirect-call-timeout
++ These new settings can be used to limit how long GDB will wait for
++ an inferior function call to complete. The direct timeout is used
++ for inferior function calls from e.g. 'call' and 'print' commands,
++ while the indirect timeout is used for inferior function calls from
++ within a conditional breakpoint expression.
++
++ The default for the direct timeout is unlimited, while the default
++ for the indirect timeout is 30 seconds.
++
++ These timeouts will only have an effect for targets that are
++ operating in async mode. For non-async targets the timeouts are
++ ignored, GDB will wait indefinitely for an inferior function to
++ complete, unless interrupted by the user using Ctrl-C.
++
++* New features in the GDB remote stub, GDBserver
++
++ ** The --remote-debug and --event-loop-debug command line options
++ have been removed.
++
++ ** The --debug command line option now takes an optional comma
++ separated list of components to emit debug for. The currently
++ supported components are: all, threads, event-loop, and remote.
++ If no components are given then threads is assumed.
++
++ ** The 'monitor set remote-debug' and 'monitor set event-loop-debug'
++ command have been removed.
++
++ ** The 'monitor set debug 0|1' command has been extended to take a
++ component name, e.g.: 'monitor set debug COMPONENT off|on'.
++ Possible component names are: all, threads, event-loop, and
++ remote.
++
++* Python API
++
++ ** New function gdb.notify_mi(NAME, DATA), that emits custom
++ GDB/MI async notification.
++
++ ** New read/write attribute gdb.Value.bytes that contains a bytes
++ object holding the contents of this value.
++
++ ** New module gdb.missing_debug that facilitates dealing with
++ objfiles that are missing any debug information.
++
++ ** New function gdb.missing_debug.register_handler that can register
++ an instance of a sub-class of gdb.missing_debug.MissingDebugInfo
++ as a handler for objfiles that are missing debug information.
++
++ ** New class gdb.missing_debug.MissingDebugInfo which can be
++ sub-classed to create handlers for objfiles with missing debug
++ information.
++
++ ** Stop events now have a "details" attribute that holds a
++ dictionary that carries the same information as an MI "*stopped"
++ event.
++
++ ** New function gdb.interrupt(), that interrupts GDB as if the user
++ typed control-c.
++
++ ** New gdb.InferiorThread.ptid_string attribute. This read-only
++ attribute contains the string that appears in the 'Target Id'
++ column of the 'info threads' command output.
++
++ ** It is no longer possible to create new gdb.Progspace object using
++ 'gdb.Progspace()', this will result in a TypeError. Progspace
++ objects can still be obtained through calling other API
++ functions, for example 'gdb.current_progspace()'.
++
++ ** User defined attributes can be added to a gdb.Inferior object,
++ these will be stored in the object's new Inferior.__dict__
++ attribute.
++
++ ** User defined attributes can be added to a gdb.InferiorThread
++ object, these will be stored in the object's new
++ InferiorThread.__dict__ attribute.
++
++ ** New constants gdb.SYMBOL_TYPE_DOMAIN, gdb.SYMBOL_FUNCTION_DOMAIN,
++ and gdb.SEARCH_*_DOMAIN corresponding to all the existing symbol
++ domains. Symbol lookup can now search in multiple domains at
++ once, and can also narrowly search for just a type or function.
++
++* Debugger Adapter Protocol changes
++
++ ** GDB now emits the "process" event.
++
++ ** GDB now supports the "cancel" request.
++
++ ** The "attach" request now supports specifying the program.
++
++ ** New command "set debug dap-log-level" controls DAP logging.
++
++ ** The "set debug dap-log-file" command is now documented. This
++ command was available in GDB 14 but not documented.
++
++* Guile API
++
++ ** New constants SYMBOL_TYPE_DOMAIN, SYMBOL_FUNCTION_DOMAIN, and
++ SEARCH_*_DOMAIN corresponding to all the existing symbol domains.
++ Symbol lookup can now search in multiple domains at once, and can
++ also narrowly search for just a type or function.
++
++* New remote packets
++
++New stop reason: clone
++ Indicates that a clone system call was executed.
++
++QThreadOptions
++ Enable/disable optional event reporting, on a per-thread basis.
++ Currently supported options are GDB_THREAD_OPTION_CLONE, to enable
++ clone event reporting, and GDB_THREAD_OPTION_EXIT to enable thread
++ exit event reporting.
++
++QThreadOptions in qSupported
++ The qSupported packet allows GDB to inform the stub it supports the
++ QThreadOptions packet, and the qSupported response can contain the
++ set of thread options the remote stub supports.
++
+ *** Changes in GDB 14
+
+ * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which
+diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
+--- a/gdb/doc/gdb.texinfo
++++ b/gdb/doc/gdb.texinfo
+@@ -20987,6 +20987,72 @@ to resume the inferior (using commands like @code{continue},
+ @code{step}, etc). In this case, when the inferior finally returns to
+ the dummy-frame, @value{GDBN} will once again halt the inferior.
+
++On targets that support asynchronous execution (@pxref{Background
++Execution}) @value{GDBN} can place a timeout on any functions called
++from @value{GDBN}. If the timeout expires and the function call is
++still ongoing, then @value{GDBN} will interrupt the program.
++
++For targets that don't support asynchronous execution
++(@pxref{Background Execution}) then timeouts for functions called from
++@value{GDBN} are not supported, the timeout settings described below
++will be treated as @code{unlimited}, meaning @value{GDBN} will wait
++indefinitely for function call to complete, unless interrupted by the
++user using @kbd{Ctrl-C}.
++
++@table @code
++@item set direct-call-timeout @var{seconds}
++@kindex set direct-call-timeout
++@cindex timeout for called functions
++Set the timeout used when calling functions in the program to
++@var{seconds}, which should be an integer greater than zero, or the
++special value @code{unlimited}, which indicates no timeout should be
++used. The default for this setting is @code{unlimited}.
++
++This setting is used when the user calls a function directly from the
++command prompt, for example with a @code{call} or @code{print}
++command.
++
++This setting only works for targets that support asynchronous
++execution (@pxref{Background Execution}), for any other target the
++setting is treated as @code{unlimited}.
++
++@item show direct-call-timeout
++@kindex show direct-call-timeout
++@cindex timeout for called functions
++Show the timeout used when calling functions in the program with a
++@code{call} or @code{print} command.
++@end table
++
++It is also possible to call functions within the program from the
++condition of a conditional breakpoint (@pxref{Conditions, ,Break
++Conditions}). A different setting controls the timeout used for
++function calls made from a breakpoint condition.
++
++@table @code
++@item set indirect-call-timeout @var{seconds}
++@kindex set indirect-call-timeout
++@cindex timeout for called functions
++Set the timeout used when calling functions in the program from a
++breakpoint or watchpoint condition to @var{seconds}, which should be
++an integer greater than zero, or the special value @code{unlimited},
++which indicates no timeout should be used. The default for this
++setting is @code{30} seconds.
++
++This setting only works for targets that support asynchronous
++execution (@pxref{Background Execution}), for any other target the
++setting is treated as @code{unlimited}.
++
++If a function called from a breakpoint or watchpoint condition times
++out, then @value{GDBN} will stop at the point where the timeout
++occurred. The breakpoint condition evaluation will be abandoned.
++
++@item show indirect-call-timeout
++@kindex show indirect-call-timeout
++@cindex timeout for called functions
++Show the timeout used when calling functions in the program from a
++breakpoint or watchpoint condition.
++@end table
++
+ @subsection Calling functions with no debug info
+
+ @cindex no debug info functions
+diff --git a/gdb/infcall.c b/gdb/infcall.c
+--- a/gdb/infcall.c
++++ b/gdb/infcall.c
+@@ -96,6 +96,53 @@ show_may_call_functions_p (struct ui_file *file, int from_tty,
+ value);
+ }
+
++/* A timeout (in seconds) for direct inferior calls. A direct inferior
++ call is one the user triggers from the prompt, e.g. with a 'call' or
++ 'print' command. Compare with the definition of indirect calls below. */
++
++static unsigned int direct_call_timeout = UINT_MAX;
++
++/* Implement 'show direct-call-timeout'. */
++
++static void
++show_direct_call_timeout (struct ui_file *file, int from_tty,
++ struct cmd_list_element *c, const char *value)
++{
++ if (target_has_execution () && !target_can_async_p ())
++ gdb_printf (file, _("Current target does not support async mode, timeout "
++ "for direct inferior calls is \"unlimited\".\n"));
++ else if (direct_call_timeout == UINT_MAX)
++ gdb_printf (file, _("Timeout for direct inferior function calls "
++ "is \"unlimited\".\n"));
++ else
++ gdb_printf (file, _("Timeout for direct inferior function calls "
++ "is \"%s seconds\".\n"), value);
++}
++
++/* A timeout (in seconds) for indirect inferior calls. An indirect inferior
++ call is one that originates from within GDB, for example, when
++ evaluating an expression for a conditional breakpoint. Compare with
++ the definition of direct calls above. */
++
++static unsigned int indirect_call_timeout = 30;
++
++/* Implement 'show indirect-call-timeout'. */
++
++static void
++show_indirect_call_timeout (struct ui_file *file, int from_tty,
++ struct cmd_list_element *c, const char *value)
++{
++ if (target_has_execution () && !target_can_async_p ())
++ gdb_printf (file, _("Current target does not support async mode, timeout "
++ "for indirect inferior calls is \"unlimited\".\n"));
++ else if (indirect_call_timeout == UINT_MAX)
++ gdb_printf (file, _("Timeout for indirect inferior function calls "
++ "is \"unlimited\".\n"));
++ else
++ gdb_printf (file, _("Timeout for indirect inferior function calls "
++ "is \"%s seconds\".\n"), value);
++}
++
+ /* How you should pass arguments to a function depends on whether it
+ was defined in K&R style or prototype style. If you define a
+ function using the K&R syntax that takes a `float' argument, then
+@@ -620,6 +667,85 @@ call_thread_fsm::should_notify_stop ()
+ return true;
+ }
+
++/* A class to control creation of a timer that will interrupt a thread
++ during an inferior call. */
++struct infcall_timer_controller
++{
++ /* Setup an event-loop timer that will interrupt PTID if the inferior
++ call takes too long. DIRECT_CALL_P is true when this inferior call is
++ a result of the user using a 'print' or 'call' command, and false when
++ this inferior call is a result of e.g. a conditional breakpoint
++ expression, this is used to select which timeout to use. */
++ infcall_timer_controller (thread_info *thr, bool direct_call_p)
++ : m_thread (thr)
++ {
++ unsigned int timeout
++ = direct_call_p ? direct_call_timeout : indirect_call_timeout;
++ if (timeout < UINT_MAX && target_can_async_p ())
++ {
++ int ms = timeout * 1000;
++ int id = create_timer (ms, infcall_timer_controller::timed_out, this);
++ m_timer_id.emplace (id);
++ infcall_debug_printf ("Setting up infcall timeout timer for "
++ "ptid %s: %d milliseconds",
++ m_thread->ptid.to_string ().c_str (), ms);
++ }
++ }
++
++ /* Destructor. Ensure that the timer is removed from the event loop. */
++ ~infcall_timer_controller ()
++ {
++ /* If the timer has already triggered, then it will have already been
++ deleted from the event loop. If the timer has not triggered, then
++ delete it now. */
++ if (m_timer_id.has_value () && !m_triggered)
++ delete_timer (*m_timer_id);
++
++ /* Just for clarity, discard the timer id now. */
++ m_timer_id.reset ();
++ }
++
++ /* Return true if there was a timer in place, and the timer triggered,
++ otherwise, return false. */
++ bool triggered_p ()
++ {
++ gdb_assert (!m_triggered || m_timer_id.has_value ());
++ return m_triggered;
++ }
++
++private:
++ /* The thread we should interrupt. */
++ thread_info *m_thread;
++
++ /* Set true when the timer is triggered. */
++ bool m_triggered = false;
++
++ /* Given a value when a timer is in place. */
++ gdb::optional<int> m_timer_id;
++
++ /* Callback for the timer, forwards to ::trigger below. */
++ static void
++ timed_out (gdb_client_data context)
++ {
++ infcall_timer_controller *ctrl
++ = static_cast<infcall_timer_controller *> (context);
++ ctrl->trigger ();
++ }
++
++ /* Called when the timer goes off. Stop thread M_THREAD. */
++ void
++ trigger ()
++ {
++ m_triggered = true;
++
++ scoped_disable_commit_resumed disable_commit_resumed ("infcall timeout");
++
++ infcall_debug_printf ("Stopping thread %s",
++ m_thread->ptid.to_string ().c_str ());
++ target_stop (m_thread->ptid);
++ }
++};
++
+ /* Subroutine of call_function_by_hand to simplify it.
+ Start up the inferior and wait for it to stop.
+ Return the exception if there's an error, or an exception with
+@@ -630,13 +756,15 @@ call_thread_fsm::should_notify_stop ()
+
+ static struct gdb_exception
+ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
+- struct thread_info *call_thread, CORE_ADDR real_pc)
++ struct thread_info *call_thread, CORE_ADDR real_pc,
++ bool *timed_out_p)
+ {
+ INFCALL_SCOPED_DEBUG_ENTER_EXIT;
+
+ struct gdb_exception caught_error;
+ ptid_t call_thread_ptid = call_thread->ptid;
+ int was_running = call_thread->state == THREAD_RUNNING;
++ *timed_out_p = false;
+
+ infcall_debug_printf ("call function at %s in thread %s, was_running = %d",
+ core_addr_to_string (real_pc),
+@@ -681,11 +809,23 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
+ infrun_debug_show_threads ("non-exited threads after proceed for inferior-call",
+ all_non_exited_threads ());
+
++ /* Setup a timer (if possible, and if the settings allow) to prevent
++ the inferior call running forever. */
++ bool direct_call_p = !call_thread->control.in_cond_eval;
++ infcall_timer_controller infcall_timer (call_thread, direct_call_p);
++
+ /* Inferior function calls are always synchronous, even if the
+ target supports asynchronous execution. */
+ wait_sync_command_done ();
+
+- infcall_debug_printf ("inferior call completed successfully");
++ /* If the timer triggered then the inferior call failed. */
++ if (infcall_timer.triggered_p ())
++ {
++ infcall_debug_printf ("inferior call timed out");
++ *timed_out_p = true;
++ }
++ else
++ infcall_debug_printf ("inferior call completed successfully");
+ }
+ catch (gdb_exception &e)
+ {
+@@ -1357,6 +1497,10 @@ call_function_by_hand_dummy (struct value *function,
+ scoped_restore restore_stopped_by_random_signal
+ = make_scoped_restore (&stopped_by_random_signal, 0);
+
++ /* Set to true by the call to run_inferior_call below if the inferior
++ call is artificially interrupted by GDB due to taking too long. */
++ bool timed_out_p = false;
++
+ /* - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP - SNIP -
+ If you're looking to implement asynchronous dummy-frames, then
+ just below is the place to chop this function in two.. */
+@@ -1383,7 +1527,8 @@ call_function_by_hand_dummy (struct value *function,
+ struct_addr);
+ {
+ std::unique_ptr<call_thread_fsm> sm_up (sm);
+- e = run_inferior_call (std::move (sm_up), call_thread.get (), real_pc);
++ e = run_inferior_call (std::move (sm_up), call_thread.get (), real_pc,
++ &timed_out_p);
+ }
+
+ if (e.reason < 0)
+@@ -1535,7 +1680,10 @@ When the function is done executing, GDB will silently stop."),
+ std::string name = get_function_name (funaddr, name_buf,
+ sizeof (name_buf));
+
+- if (stopped_by_random_signal)
++ /* If the inferior call timed out then it will have been interrupted
++ by a signal, but we want to report this differently to the user,
++ which is done later in this function. */
++ if (stopped_by_random_signal && !timed_out_p)
+ {
+ /* We stopped inside the FUNCTION because of a random
+ signal. Further execution of the FUNCTION is not
+@@ -1586,6 +1734,36 @@ GDB remains in the frame where the signal was received.\n\
+ To change this behavior use \"set unwindonsignal on\".\n\
+ Evaluation of the expression containing the function\n\
+ (%s) will be abandoned.\n\
++When the function is done executing, GDB will silently stop."),
++ name.c_str ());
++ }
++ }
++
++ if (timed_out_p)
++ {
++ /* A timeout results in a signal being sent to the inferior. */
++ gdb_assert (stopped_by_random_signal);
++
++ /* Indentation is weird here. A later patch is going to move the
++ following block into an if/else, so I'm leaving the indentation
++ here to minimise the later patch.
++
++ Also, the error message used below refers to 'set
++ unwind-on-timeout' which doesn't exist yet. This will be added
++ in a later commit, I'm leaving this in for now to minimise the
++ churn caused by the commit that adds unwind-on-timeout. */
++ {
++ /* The user wants to stay in the frame where we stopped
++ (default). Discard inferior status, we're not at the same
++ point we started at. */
++ discard_infcall_control_state (inf_status.release ());
++
++ error (_("\
++The program being debugged timed out while in a function called from GDB.\n\
++GDB remains in the frame where the timeout occurred.\n\
++To change this behavior use \"set unwind-on-timeout on\".\n\
++Evaluation of the expression containing the function\n\
++(%s) will be abandoned.\n\
+ When the function is done executing, GDB will silently stop."),
+ name.c_str ());
+ }
+@@ -1699,6 +1877,30 @@ The default is to unwind the frame."),
+ show_unwind_on_terminating_exception_p,
+ &setlist, &showlist);
+
++ add_setshow_uinteger_cmd ("direct-call-timeout", no_class,
++ &direct_call_timeout, _("\
++Set the timeout, for direct calls to inferior function calls."), _("\
++Show the timeout, for direct calls to inferior function calls."), _("\
++If running on a target that supports, and is running in, async mode\n\
++then this timeout is used for any inferior function calls triggered\n\
++directly from the prompt, i.e. from a 'call' or 'print' command. The\n\
++timeout is specified in seconds."),
++ nullptr,
++ show_direct_call_timeout,
++ &setlist, &showlist);
++
++ add_setshow_uinteger_cmd ("indirect-call-timeout", no_class,
++ &indirect_call_timeout, _("\
++Set the timeout, for indirect calls to inferior function calls."), _("\
++Show the timeout, for indirect calls to inferior function calls."), _("\
++If running on a target that supports, and is running in, async mode\n\
++then this timeout is used for any inferior function calls triggered\n\
++indirectly, i.e. being made as part of a breakpoint, or watchpoint,\n\
++condition expression. The timeout is specified in seconds."),
++ nullptr,
++ show_indirect_call_timeout,
++ &setlist, &showlist);
++
+ add_setshow_boolean_cmd
+ ("infcall", class_maintenance, &debug_infcall,
+ _("Set inferior call debugging."),
+diff --git a/gdb/testsuite/gdb.base/help.exp b/gdb/testsuite/gdb.base/help.exp
+--- a/gdb/testsuite/gdb.base/help.exp
++++ b/gdb/testsuite/gdb.base/help.exp
+@@ -121,7 +121,7 @@ gdb_test "help info bogus-gdb-command" "Undefined info command: \"bogus-gdb-comm
+ gdb_test "help gotcha" "Undefined command: \"gotcha\"\. Try \"help\"\."
+
+ # Test apropos regex.
+-gdb_test "apropos \\\(print\[\^\[ bsiedf\\\".-\]\\\)" "handle -- Specify how to handle signals\."
++gdb_test "apropos \\\(print\[\^\[ bsiedf\\\"'.-\]\\\)" "handle -- Specify how to handle signals\."
+ # Test apropos >1 word string.
+ gdb_test "apropos handle signal" "handle -- Specify how to handle signals\."
+ # Test apropos apropos.
+diff --git a/gdb/testsuite/gdb.base/infcall-timeout.c b/gdb/testsuite/gdb.base/infcall-timeout.c
+new file mode 100644
+--- /dev/null
++++ b/gdb/testsuite/gdb.base/infcall-timeout.c
+@@ -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;
++}
+diff --git a/gdb/testsuite/gdb.base/infcall-timeout.exp b/gdb/testsuite/gdb.base/infcall-timeout.exp
+new file mode 100644
+--- /dev/null
++++ b/gdb/testsuite/gdb.base/infcall-timeout.exp
+@@ -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
++ }
++ }
++}
+diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.c b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.c
+new file mode 100644
+--- /dev/null
++++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.c
+@@ -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;
++}
+diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.exp b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.exp
+new file mode 100644
+--- /dev/null
++++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-timeout.exp
+@@ -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
++ }
++ }
++ }
++}