[v4,5/6] gdb: refine commands to control scheduler locking.

Message ID 20251213143709.301799-6-christina.schimpe@intel.com
State New
Headers
Series Refinement of scheduler-locking settings |

Commit Message

Christina Schimpe Dec. 13, 2025, 2:37 p.m. UTC
  From: Natalia Saiapova <natalia.saiapova@intel.com>

In this patch, we introduce new command options for set/show scheduler
locking.  New options give the user a finer control over the scheduler.

Introduce
set scheduler-locking <non-step | replay step | replay non-step> | step> <on | off>
show scheduler-locking <non-step | replay step | replay non-step| step>

For example, with these commands a user can get a combined scheduler locking
for stepping commands during the normal execution and for all commands in
replay mode.

The existing scheduler-locking settings still exist and work as shortcuts.

  set scheduler-locking step
is equivalent to
  set scheduler-locking non-step off
  set scheduler-locking replay non-step off
  set scheduler-locking replay step on
  set scheduler-locking step on

  set scheduler-locking on
is equivalent to
  set scheduler-locking non-step on
  set scheduler-locking replay non-step on
  set scheduler-locking replay step on
  set scheduler-locking step on

  set scheduler-locking replay
is equivalent to
  set scheduler-locking non-step off
  set scheduler-locking replay non-step on
  set scheduler-locking replay step on
  set scheduler-locking step off

  set scheduler-locking off
is equivalent to
  set scheduler-locking non-step off
  set scheduler-locking replay non-step off
  set scheduler-locking replay step off
  set scheduler-locking step off

This is bound to the structure we introduced in the previous commit:
  gdb: change the internal representation of scheduler locking.

To introduce it under scheduler-locking I had to change the way the show
command works.

  (gdb) show scheduler-locking
  scheduler-locking non-step:  "off"  Scheduler locking for non-stepping
  commands is "off" during normal execution.
  scheduler-locking replay non-step:  "on"  Scheduler locking for
  non-stepping commands is "on" during replay mode.
  scheduler-locking replay step:  "on"  Scheduler locking for stepping
  commands is "on" during replay mode.
  scheduler-locking step:  "off"  Scheduler locking for stepping commands
  is "off" during normal execution.

  (gdb) show scheduler-locking replay
  scheduler-locking replay non-step:  "on"  Scheduler locking for
  non-stepping commands is "on" during replay mode.
  scheduler-locking replay step:  "on"  Scheduler locking for stepping
  commands is "on" during replay mode.

  (gdb) show scheduler-locking replay step
  "on"  Scheduler locking for stepping commands is "on" during replay mode.

  (gdb) show scheduler-locking non-step
  "off"  Scheduler locking for non-stepping commands is "off" during
  normal execution.

Note, there is a small inconsistency with the "set scheduler-locking step".
If we did not keep the older way of setting the scheduler locking, command
  set scheduler-locking step
would be the same as
  set scheduler-locking step on
while to be backward compatible, we have it as
  set scheduler-locking step on
  set scheduler-locking replay step on

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS                                      |  20 ++
 gdb/doc/gdb.texinfo                           |  70 +++++-
 gdb/infrun.c                                  | 224 ++++++++++++++----
 .../gdb.mi/user-selected-context-sync.exp     |  22 +-
 .../gdb.threads/hand-call-in-threads.exp      |  10 +-
 .../multiple-successive-infcall.exp           |   6 +-
 gdb/testsuite/gdb.threads/schedlock.exp       |  80 ++++++-
 gdb/testsuite/lib/gdb.exp                     |  70 ++++--
 8 files changed, 400 insertions(+), 102 deletions(-)
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 0eff1d551a3..50f6092c571 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -709,6 +709,26 @@  list .
   lines.  This makes it more obvious that there is no information, as opposed
   to implying there is no inferior loaded.
 
+set scheduler-locking <command type> (on|off)
+show scheduler-locking <command type>
+  where <command-type> is one of the following:
+    non-step | replay non-step | replay step | step.
+  Extend the scheduler locking settings with a set of set/show
+  commands, which can be used individually to control the scheduler during
+  stepping and non-stepping commands. Stepping commands include step, stepi, next.
+  Non-stepping commands include continue, finish, until, jump, return.
+    'non-step' -- when on, the scheduler is locked during non-stepping commands
+    in normal mode.
+    'replay non-step' -- when on, the scheduler is locked during non-stepping
+    commands in replay mode.
+    'replay step' -- when on, the scheduler is locked during stepping
+    commands in replay mode.
+    'step' -- when on, the scheduler is locked during stepping commands
+    in normal mode.
+  The older scheduler locking settings can be used as shortcuts, their behavior
+  is preserved.
+  The output of "show scheduler-locking" has changed to support the new settings.
+
 * New commands
 
 info missing-debug-handler
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index bd5d0250483..3f88a005470 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -7381,28 +7381,67 @@  On some OSes, you can modify @value{GDBN}'s default behavior by
 locking the OS scheduler to allow only a single thread to run.
 
 @table @code
-@item set scheduler-locking @var{mode}
 @cindex scheduler-locking
+@item set scheduler-locking @var{type} [@code{on}|@code{off}]
+@cindex scheduler locking type
+@cindex lock scheduler
+Set the scheduler locking settings.  It applies to normal execution,
+record mode, and replay mode.  You can configure scheduler locking separately
+for stepping and non-stepping commands.  Examples of stepping commands are:
+@samp{step}, @samp{stepi}, @samp{next}.  Examples of non-stepping commands are
+@samp{continue}, @samp{finish}, @samp{jump}, @samp{until}, @samp{return} or
+inferior function calls.
+
+The following @var{type}-settings are available:
+
+@table @code
+@item non-step
+When @code{on}, the scheduler is locked for non-stepping commands during
+normal execution and record modes.  For non-stepping commands other threads
+may not preempt the current thread.  This setting is @code{off} by default.
+
+@item replay non-step
+When @code{on}, the scheduler is locked for non-stepping commands during
+replay mode.  For non-stepping commands other threads may not preempt
+the current thread.  This setting is @code{on} by default.
+
+@item replay step
+When @code{on}, the scheduler is locked for stepping commands during replay
+mode.  While stepping, other threads may not preempt the current thread,
+so that the focus of debugging does not change unexpectedly.  This setting
+is @code{on} by default.
+
+@item step
+When @code{on}, the scheduler is locked for stepping commands during
+normal execution and record modes.  While stepping, other threads may not
+preempt the current thread, so that the focus of debugging does not change
+unexpectedly.  This setting is @code{off} by default.
+
+@end table
+
+@item set scheduler-locking @var{shortcut-mode}
 @cindex scheduler locking mode
 @cindex lock scheduler
-Set the scheduler locking mode.  It applies to normal execution,
-record mode, and replay mode.  @var{mode} can be one of
-the following:
+Set the scheduler locking mode.  It applies to normal execution, record mode,
+and replay mode.  @var{shortcut-mode} is a shortcut to set several scheduler
+locking types at once and can be one of the following:
 
 @table @code
 @item off
-There is no locking and any thread may run at any time.
+There is no locking and any thread may run at any time.  This is
+equivalent to setting all type options to @code{off}.
 
 @item on
-Only the current thread may run when the inferior is resumed.  New
-threads created by the resumed thread are held stopped at their entry
-point, before they execute any instruction.
+Only the current thread may run when the inferior is resumed.  New threads
+created by the resumed thread are held stopped at their entry point, before
+they execute any instruction.  This is equivalent to setting all type options
+to @code{on}.
 
 @item step
 Behaves like @code{on} when stepping, and @code{off} otherwise.
 Threads other than the current never get a chance to run when you
-step, and they are completely free to run when you use commands like
-@samp{continue}, @samp{until}, or @samp{finish}.
+step, and they are completely free to run when you use non-stepping
+commands.
 
 This mode optimizes for single-stepping; it prevents other threads
 from preempting the current thread while you are stepping, so that the
@@ -7411,9 +7450,18 @@  another thread hits a breakpoint during its timeslice, @value{GDBN}
 does not change the current thread away from the thread that you are
 debugging.
 
+This is equivalent to set @samp{scheduler-locking step} and
+@samp{scheduler-locking replay step} to @code{on}, while other settings
+are @code{off}.
+
 @item replay
 Behaves like @code{on} in replay mode, and @code{off} in either record
 mode or during normal execution.  This is the default mode.
+
+This is equivalent to set @samp{scheduler-locking replay non-step} and
+@samp{scheduler-locking replay step} to @code{on}, while other settings
+are @code{off}.
+
 @end table
 
 @item show scheduler-locking
@@ -34549,7 +34597,7 @@  the end or beginning of a replay log if one is being used.
 @end itemize
 In all-stop mode (@pxref{All-Stop
 Mode}), may resume only one thread, or all threads, depending on the
-value of the @samp{scheduler-locking} variable.  If @samp{--all} is
+value of the @samp{scheduler-locking} variables.  If @samp{--all} is
 specified, all threads (in all inferiors) will be resumed.  The @samp{--all} option is
 ignored in all-stop mode.  If the @samp{--thread-group} options is
 specified, then all threads in that thread group are resumed.
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 7e00dffff37..fc0491178d9 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -75,6 +75,8 @@ 
 #include "extension.h"
 #include "disasm.h"
 #include "interps.h"
+#include "cli/cli-decode.h"
+#include <set>
 
 /* Prototypes for local functions */
 
@@ -111,6 +113,12 @@  static bool schedlock_applies (thread_info *tp, bool step,
 			       bool record_will_replay);
 static bool schedlock_applies_to_opts (const schedlock_options &, bool step);
 
+/* Command lists for the scheduler locking.  */
+static cmd_list_element *schedlock_set_cmdlist;
+static cmd_list_element *schedlock_show_cmdlist;
+static cmd_list_element *schedlock_set_replay_cmdlist;
+static cmd_list_element *schedlock_show_replay_cmdlist;
+
 /* Asynchronous signal handler registered as event loop source for
    when we have pending events ready to be passed to the core.  */
 static struct async_event_handler *infrun_async_inferior_event_token;
@@ -2344,7 +2352,8 @@  struct schedlock_options
 
     operator bool () const { return value; }
     const char *c_str () const { return value ? "on" : "off"; }
-    /* Set new value.  Return true, if the value has changed.  */
+    /* Set new value.  Return true, if the value has changed.
+       Also notifies the observer, if the value has changed.  */
     bool set (bool new_value);
   };
 
@@ -2371,6 +2380,9 @@  schedlock_options::option::set (bool new_value)
   if (value != new_value)
     {
       value = new_value;
+      std::string param_name = "scheduler-locking " + name;
+
+      interps_notify_param_changed (param_name.c_str (), c_str ());
       return true;
     }
 
@@ -2391,15 +2403,6 @@  static const char schedlock_off[] = "off";
 static const char schedlock_on[] = "on";
 static const char schedlock_step[] = "step";
 static const char schedlock_replay[] = "replay";
-static const char *const scheduler_enums[] = {
-  schedlock_off,
-  schedlock_on,
-  schedlock_step,
-  schedlock_replay,
-  nullptr
-};
-
-static const char *scheduler_mode = schedlock_replay;
 
 schedlock schedlock {{{"non-step", false}, {"step", false}},
 		     {{"replay non-step", true}, {"replay step", true}}};
@@ -2420,35 +2423,89 @@  set_schedlock_shortcut_option (const char *shortcut)
   /* Check that we got a valid shortcut option.  */
   gdb_assert (is_on || is_step || is_replay || is_off);
 
-  schedlock.normal.non_step.set (is_on);
-  schedlock.normal.step.set (is_on || is_step);
-  schedlock.replay.non_step.set (is_on || is_replay);
-  schedlock.replay.step.set (is_on || is_replay || is_step);
+  bool any_changed = schedlock.normal.non_step.set (is_on);
+  any_changed = schedlock.normal.step.set (is_on || is_step) || any_changed;
+  any_changed = schedlock.replay.non_step.set (is_on || is_replay) || any_changed;
+  any_changed = schedlock.replay.step.set (is_on || is_replay || is_step)
+    || any_changed;
+
+  /* If at least one parameter has changed, notify the observer
+     in the old-fashioned way.  */
+  if (any_changed)
+    interps_notify_param_changed ("scheduler-locking", shortcut);
 }
 
+/* Default callback for set methods of scheduler locking options.
+   Checks that the scheduler locking is supported.
+   If no, it reverts all options to "off" and throws an error.  */
+
 static void
-show_scheduler_mode (struct ui_file *file, int from_tty,
-		     struct cmd_list_element *c, const char *value)
+set_schedlock_callback (const char *args, int from_tty, cmd_list_element *c)
 {
-  gdb_printf (file,
-	      _("Mode for locking scheduler "
-		"during execution is \"%s\".\n"),
-	      value);
+  if (target_can_lock_scheduler ())
+    return;
+
+  /* Set scheduler locking off and error out.  */
+  set_schedlock_shortcut_option (schedlock_off);
+  error (_("Target '%s' cannot support this command."), target_shortname ());
 }
 
+/* Support for shortcut schedlock options: "on", "off", "step", "replay".  */
+
 static void
-set_schedlock_func (const char *args, int from_tty, struct cmd_list_element *c)
+set_schedlock_step (const char *args, int from_tty, cmd_list_element *c)
 {
-  if (!target_can_lock_scheduler ())
-    {
-      scheduler_mode = schedlock_off;
-      /* Set scheduler locking off.  */
-      set_schedlock_shortcut_option (schedlock_off);
-      error (_("Target '%s' cannot support this command."),
-	     target_shortname ());
-    }
+  if (!args || !*args)
+    set_schedlock_shortcut_option (schedlock_step);
+  set_schedlock_callback (args, from_tty, nullptr);
+}
 
-  set_schedlock_shortcut_option (scheduler_mode);
+static void
+set_schedlock_replay (const char *args, int from_tty)
+{
+  set_schedlock_shortcut_option (schedlock_replay);
+  set_schedlock_callback (args, from_tty, nullptr);
+}
+
+static void
+set_schedlock_on (const char *args, int from_tty)
+{
+  set_schedlock_shortcut_option (schedlock_on);
+  set_schedlock_callback (args, from_tty, nullptr);
+}
+
+static void
+set_schedlock_off (const char *args, int from_tty)
+{
+  set_schedlock_shortcut_option (schedlock_off);
+  set_schedlock_callback (args, from_tty, nullptr);
+}
+
+/* Default method to show a single option of scheduler locking.  */
+
+static void
+show_schedlock_option (ui_file *file, int from_tty,
+		       cmd_list_element *c, const char *value)
+{
+  gdb_assert (c->prefix != nullptr);
+  const char *mode;
+  if (strcmp (c->prefix->name, "replay") == 0)
+    mode = "replay mode";
+  else if (strcmp (c->prefix->name, "scheduler-locking") == 0)
+    mode = "normal execution";
+  else
+    gdb_assert_not_reached ("Unexpected command prefix.");
+
+  const char *type;
+  if (strcmp (c->name, "step") == 0)
+    type = "stepping commands";
+  else if (strcmp (c->name, "non-step") == 0)
+    type = "non-stepping commands";
+  else
+    gdb_assert_not_reached ("Unexpected command name.");
+
+  gdb_printf (file, _("\"%s\"  Scheduler locking for %s is "
+		      "\"%s\" during the %s.\n"), value, type, value, mode);
 }
 
 /* True if execution commands resume all threads of all processes by
@@ -8535,15 +8592,7 @@  switch_back_to_stepped_thread (struct execution_control_state *ecs)
 	  return true;
 	}
 
-      /* If scheduler locking applies even if not stepping, there's no
-	 need to walk over threads.  Above we've checked whether the
-	 current thread is stepping.  If some other thread not the
-	 event thread is stepping, then it must be that scheduler
-	 locking is not in effect.  */
-      if (schedlock_applies (ecs->event_thread))
-	return false;
-
-      /* Otherwise, we no longer expect a trap in the current thread.
+      /* We no longer expect a trap in the current thread.
 	 Clear the trap_expected flag before switching back -- this is
 	 what keep_going does as well, if we call it.  */
       ecs->event_thread->control.trap_expected = 0;
@@ -10902,21 +10951,92 @@  By default, the debugger will use the same inferior."),
 			show_follow_exec_mode_string,
 			&setlist, &showlist);
 
-  add_setshow_enum_cmd ("scheduler-locking", class_run,
-			scheduler_enums, &scheduler_mode, _("\
-Set mode for locking scheduler during execution."), _("\
-Show mode for locking scheduler during execution."), _("\
-off    == no locking (threads may preempt at any time)\n\
-on     == full locking (no thread except the current thread may run)\n\
-	  This applies to both normal execution and replay mode.\n\
-step   == scheduler locked during stepping commands (step, next, stepi, nexti).\n\
-	  In this mode, other threads may run during other commands.\n\
-	  This applies to both normal execution and replay mode.\n\
-replay == scheduler locked in replay mode and unlocked during normal execution."),
-			set_schedlock_func,	/* traps on target vector */
-			show_scheduler_mode,
+  /* Commands for set/show scheduler-locking.  */
+
+  add_setshow_prefix_cmd ("scheduler-locking", class_run, _("\
+Scheduler locking settings.\n\
+Configure scheduler locking settings in various conditions."), _("\
+Show scheduler locking settings in various conditions."),
+			&schedlock_set_cmdlist,
+			&schedlock_show_cmdlist,
 			&setlist, &showlist);
 
+  add_setshow_boolean_cmd ("non-step", class_run, &schedlock.normal.non_step.value, _("\
+Scheduler locking for non-stepping commands during normal execution."), _("\
+Show scheduler locking for non-stepping commands during normal execution."),
+			   _("\
+Controls scheduler locking for non-stepping commands during normal execution.\n\
+Commands include continue, until, finish.  The setting does not affect \
+stepping."),
+			   set_schedlock_callback,
+			   show_schedlock_option,
+			   &schedlock_set_cmdlist,
+			   &schedlock_show_cmdlist);
+
+  add_setshow_boolean_cmd ("step", class_run, &schedlock.normal.step.value, _("\
+Scheduler locking for stepping commands.  W/o arguments locks the scheduler \
+for stepping."), _("\
+Show scheduler locking for stepping commands during normal execution."), _("\
+If argument \"on\" or \"off\", sets scheduler locking behavior for stepping\n\
+commands only during normal execution.\n\
+Commands include step, next, stepi, nexti."),
+			   set_schedlock_step,
+			   show_schedlock_option,
+			   &schedlock_set_cmdlist,
+			   &schedlock_show_cmdlist);
+
+  /* Commands for set/show scheduler-locking in replay mode.
+     The base command adds support for the shortcut
+       set scheduler-locking replay
+     command.  */
+
+  add_setshow_prefix_cmd ("replay", class_run, _("\
+Scheduler locking settings for replay mode.\n\
+Configure scheduler locking in various conditions such as during continuing\n\
+or stepping."),
+("Show scheduler locking in replay mode."),
+			&schedlock_set_replay_cmdlist,
+			&schedlock_show_replay_cmdlist,
+			&schedlock_set_cmdlist,
+			&schedlock_show_cmdlist);
+  add_prefix_cmd ("replay", class_run, set_schedlock_replay, _("\
+Scheduler locking settings for replay mode. \
+W/o arguments completely locks the scheduler in replay mode."),
+		  &schedlock_set_replay_cmdlist,
+	   0, &schedlock_set_cmdlist);
+
+  add_setshow_boolean_cmd ("non-step", class_run, &schedlock.replay.non_step.value, _("\
+Set scheduler locking for non-stepping commands in replay mode."), _("\
+Show scheduler locking for non-stepping commands in replay mode."), _("\
+Controls scheduler locking for non-stepping commands in replay mode.\n\
+Commands include continue, until, finish.  The setting does not affect \
+stepping."),
+			   set_schedlock_callback,
+			   show_schedlock_option,
+			   &schedlock_set_replay_cmdlist,
+			   &schedlock_show_replay_cmdlist);
+
+  add_setshow_boolean_cmd ("step", class_run, &schedlock.replay.step.value, _("\
+Set scheduler locking for stepping commands in replay mode."), _("\
+Show scheduler locking for stepping commands in replay mode."), _("\
+Controls scheduler locking for stepping commands in replay mode.\n\
+Commands include step, next, stepi, nexti."),
+			   set_schedlock_callback,
+			   show_schedlock_option,
+			   &schedlock_set_replay_cmdlist,
+			   &schedlock_show_replay_cmdlist);
+
+/* Commands "set scheduler-locking on" and "set scheduler-locking off"
+   are provided for backward compatibility.  */
+  c = add_cmd ("on", class_run, set_schedlock_on, _("\
+[Shortcut] Full locking (no thread except the current thread may run).\n\
+This applies to both normal execution and replay mode."),
+	   &schedlock_set_cmdlist);
+
+  c = add_cmd ("off", class_run, set_schedlock_off, _("\
+[Shortcut] No locking (threads may preempt at any time)."),
+	   &schedlock_set_cmdlist);
+
   add_setshow_boolean_cmd ("schedule-multiple", class_run, &sched_multi, _("\
 Set mode for resuming threads of all processes."), _("\
 Show mode for resuming threads of all processes."), _("\
diff --git a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 618bed48ea8..2e1d3d06300 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -257,17 +257,12 @@  proc make_cli_in_mi_re { command cli_in_mi_mode mode event inf cli_thread
 # Return the current value of the "scheduler-locking" parameter.
 
 proc show_scheduler_locking { } {
-    global gdb_prompt
-    global expect_out
-
-    set any "\[^\r\n\]*"
-
     set test "show scheduler-locking"
-    gdb_test_multiple $test $test {
-	-re ".*Mode for locking scheduler during execution is \"(${any})\".\r\n$gdb_prompt " {
-	    pass $test
-	    return $expect_out(1,string)
-	}
+    set schedlock [get_scheduler_locking $test]
+
+    if {$schedlock ne "unknown"} {
+	pass $test
+	return $schedlock
     }
 
     error "Couldn't get current scheduler-locking value."
@@ -313,7 +308,7 @@  proc test_continue_to_start { mode inf } {
 	    }
 
 	    if { $mode == "all-stop" } {
-		set previous_schedlock_val [show_scheduler_locking]
+		set previous_schedlock [show_scheduler_locking]
 
 		# Set scheduler-locking on, so that we can control threads
 		# independently.
@@ -344,7 +339,10 @@  proc test_continue_to_start { mode inf } {
 		}
 
 		# Restore scheduler-locking to its original value.
-		gdb_test_no_output "set scheduler-locking $previous_schedlock_val"
+		foreach opt {"non-step" "replay non-step" "replay step" "step"} {
+		    gdb_test_no_output \
+			"set scheduler-locking $opt [dict get $previous_schedlock $opt]"
+		}
 	    } else { # $mode == "non-stop"
 		# Put a thread-specific breakpoint for thread 2 of the current
 		# inferior.  We don't put a breakpoint for thread 3, since we
diff --git a/gdb/testsuite/gdb.threads/hand-call-in-threads.exp b/gdb/testsuite/gdb.threads/hand-call-in-threads.exp
index 5194bf85a66..893954b0f5d 100644
--- a/gdb/testsuite/gdb.threads/hand-call-in-threads.exp
+++ b/gdb/testsuite/gdb.threads/hand-call-in-threads.exp
@@ -68,7 +68,10 @@  gdb_test "continue" \
 # Before we start making hand function calls, turn on scheduler locking.
 
 gdb_test_no_output "set scheduler-locking on" "enable scheduler locking"
-gdb_test "show scheduler-locking" ".* locking scheduler .* is \"on\"." "show scheduler locking on"
+set test "show scheduler-locking on"
+gdb_assert {[get_scheduler_locking $test \
+		 [dict create "non-step" "on" "replay non-step" "on" \
+		      "replay step" "on" "step" "on"]] ne "unknown"} $test
 
 # Now hand-call a function in each thread, having the function
 # stop without returning.
@@ -139,7 +142,10 @@  gdb_test_multiple "maint print dummy-frames" "all dummies popped" {
 
 # Before we resume the full program, turn off scheduler locking.
 gdb_test_no_output "set scheduler-locking off" "disable scheduler locking"
-gdb_test "show scheduler-locking" ".* locking scheduler .* is \"off\"." "show scheduler locking off"
+set test "show scheduler-locking off"
+gdb_assert {[get_scheduler_locking $test \
+		 [dict create "non-step" "off" "replay non-step" "off" \
+		      "replay step" "off" "step" "off"]] ne "unknown"} $test
 
 # Continue one last time, the program should exit normally.
 #
diff --git a/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp b/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
index 2694ce58e89..d43102a7602 100644
--- a/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
+++ b/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
@@ -49,8 +49,10 @@  foreach_with_prefix thread {5 4 3}  {
 gdb_breakpoint [gdb_get_line_number "testmarker01"]
 gdb_continue_to_breakpoint "testmarker01"
 gdb_test_no_output "set scheduler-locking on"
-gdb_test "show scheduler-locking" \
-  "Mode for locking scheduler during execution is \"on\"."
+set test "show scheduler-locking"
+gdb_assert {[get_scheduler_locking $test \
+		 [dict create "non-step" "on" "replay non-step" "on" \
+		      "replay step" "on" "step" "on"]] ne "unknown"} $test
 
 foreach_with_prefix thread {5 4 3 2 1}  {
   gdb_test "thread ${thread}" "Switching to .*"
diff --git a/gdb/testsuite/gdb.threads/schedlock.exp b/gdb/testsuite/gdb.threads/schedlock.exp
index 125786823ea..7ec8e7809db 100644
--- a/gdb/testsuite/gdb.threads/schedlock.exp
+++ b/gdb/testsuite/gdb.threads/schedlock.exp
@@ -94,7 +94,8 @@  proc get_current_thread { description } {
 # 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"]
+    global srcfile
+    gdb_breakpoint [concat "$srcfile:" [gdb_get_line_number "schedlock.exp: main loop"] " if arg != 0"]
 
     set test "return to loop"
     if {$msg != ""} {
@@ -264,16 +265,21 @@  with_test_prefix "schedlock=on: cmd=continue" {
 }
 
 # Test stepping/nexting with different modes of scheduler locking.
-proc test_step { schedlock cmd call_function } {
+# Do scheduler-locking off setting before the test if PRESET_SCHEDLOCK_OFF is 1.
+# LOCKED defines whether we expect the thread to be locked.  If -1, then
+# determine it first.
+proc test_step { schedlock cmd call_function { preset_schedlock_off 1 } { locked -1 } } {
     global NUM
 
-    gdb_test_no_output "set scheduler-locking off"
+    if {$preset_schedlock_off} {
+	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"} {
+    if {$preset_schedlock_off && $schedlock != "off"} {
 	gdb_test_no_output "set scheduler-locking $schedlock"
     }
 
@@ -284,16 +290,18 @@  proc test_step { schedlock cmd call_function } {
 
     step_ten_loops $cmd
 
-    if { $schedlock == "on" || $schedlock == "step" } {
-	set locked 1
-    } else {
-	set locked 0
+    if { $locked == -1 } {
+	if { $schedlock == "on" || $schedlock == "step"} {
+	    set locked 1
+	} else {
+	    set locked 0
+	}
     }
 
     check_result $cmd $curthread $before_args $locked
 }
 
-# Test stepping/nexting with different modes of scheduler locking.
+# Test stepping/nexting with different shortcut modes of scheduler locking.
 foreach schedlock {"off" "step" "on"} {
     with_test_prefix "schedlock=$schedlock" {
 	with_test_prefix "cmd=step" {
@@ -312,3 +320,57 @@  foreach schedlock {"off" "step" "on"} {
 	}
     }
 }
+
+proc test_schedlock_opts {non_step step} {
+    set test "show scheduler-locking"
+    if {[get_scheduler_locking $test \
+			 [dict create "non-step" $non_step "replay non-step" "off" \
+				  "replay step" "off" "step" $step]] eq "unknown"} {
+	fail $test
+    } else {
+	pass $test
+    }
+
+    set locked 0
+    if {$step eq "on"} {
+	set locked 1
+    }
+
+    # Stepping tests.
+    with_test_prefix "cmd=step" {
+	test_step "" "step" 0 0 $locked
+    }
+    with_test_prefix "cmd=next" {
+	foreach call_function {0 1} {
+	    with_test_prefix "call_function=$call_function" {
+		test_step "" "next" $call_function 0 $locked
+	    }
+	}
+    }
+
+    # Continuing tests.
+    set locked 0
+    if {$non_step eq "on"} {
+	set locked 1
+    }
+    with_test_prefix "cmd=continue" {
+	# Use whichever we stopped in.
+	set curthread [get_current_thread "before"]
+	set cont_args [get_args "before"]
+	my_continue "continue"
+	check_result "continue" $curthread $cont_args $locked
+    }
+}
+
+gdb_test_no_output "set scheduler-locking off"
+
+# Test different options of scheduler locking.
+foreach non_step {"off" "on"} {
+    foreach step {"off" "on"} {
+	with_test_prefix "non-step=$non_step step=$step" {
+	    gdb_test_no_output "set scheduler-locking non-step $non_step"
+	    gdb_test_no_output "set scheduler-locking step $step"
+	    test_schedlock_opts $non_step $step
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index f7d9b61fec0..021edc32d0f 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -9852,6 +9852,52 @@  gdb_caching_proc gdb_target_symbol_prefix {} {
     return $prefix
 }
 
+# Return a dictionary of scheduler locking settings with keys:
+# non-step, replay non-step, replay step, step.
+# TEST is an optional test name.
+# EXPECTED is a dictionary of expected values for scheduler locking with
+# the same keys.  If EXPECTED has less elements than scheduler locking
+# settings, that means that both on and off can be expected for missing
+# settings.
+proc get_scheduler_locking {{test ""} {expected ""}} {
+    global gdb_prompt
+    if {$test eq ""} {
+	set test "reading current scheduler-locking mode"
+    }
+
+    set opts {"non-step" "replay non-step" "replay step" "step"}
+
+    # Fill the missing entries in EXPECTED list.
+    foreach opt $opts {
+	if {![dict exists $expected $opt]} {
+	    dict set expected $opt "\(?:on|off\)"
+	}
+    }
+
+    set any "\[^\r\n\]+"
+    set schedlock_regex ""
+    foreach opt $opts {
+	set opt_regex \
+	    "${any}$opt: +\"\([dict get $expected $opt]\)\"${any}"
+	set schedlock_regex "$schedlock_regex\[\r\n\]+$opt_regex"
+    }
+
+    set current_schedlock_mode "unknown"
+    gdb_test_multiple "show scheduler-locking" $test {
+	-re -wrap $schedlock_regex {
+	    set current_schedlock_mode [dict create]
+	    set i 1
+	    foreach opt $opts {
+		dict set current_schedlock_mode $opt $expect_out($i,string)
+		incr $i
+	    }
+	}
+	-re -wrap "" {}
+	timeout {}
+    }
+    return $current_schedlock_mode
+}
+
 # Return 1 if target supports scheduler locking, otherwise return 0.
 
 gdb_caching_proc target_supports_scheduler_locking {} {
@@ -9871,28 +9917,24 @@  gdb_caching_proc target_supports_scheduler_locking {} {
     }
 
     set supports_schedule_locking -1
-    set current_schedule_locking_mode ""
 
     set test "reading current scheduler-locking mode"
-    gdb_test_multiple "show scheduler-locking" $test {
-	-re "Mode for locking scheduler during execution is \"(\[\^\"\]*)\".*$gdb_prompt" {
-	    set current_schedule_locking_mode $expect_out(1,string)
-	}
-	-re "$gdb_prompt $" {
-	    set supports_schedule_locking 0
-	}
-	timeout {
-	    set supports_schedule_locking 0
-	}
+    set current_schedlock [get_scheduler_locking $test]
+    if { $current_schedlock eq "unknown" } {
+	set supports_schedule_locking 0
     }
 
     if { $supports_schedule_locking == -1 } {
 	set test "checking for scheduler-locking support"
-	gdb_test_multiple "set scheduler-locking $current_schedule_locking_mode" $test {
-	    -re "Target '\[^'\]+' cannot support this command\..*$gdb_prompt $" {
+	set regex_schedlock \
+	    "set scheduler-locking step [dict get $current_schedlock step]"
+
+	# Try to set scheduler-locking run.
+	gdb_test_multiple $regex_schedlock $test {
+	    -re -wrap "Target '\[^'\]+' cannot support this command\..*" {
 		set supports_schedule_locking 0
 	    }
-	    -re "$gdb_prompt $" {
+	    -re -wrap "" {
 		set supports_schedule_locking 1
 	    }
 	    timeout {