diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index bffe8d554e7..744587504b3 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -1937,37 +1937,74 @@ finish_command (const char *arg, int from_tty)
 static void
 info_program_command (const char *args, int from_tty)
 {
-  bpstat *bs;
-  int num, stat;
-  ptid_t ptid;
-  process_stratum_target *proc_target;
+  scoped_restore_current_thread restore_thread;
 
-  if (!target_has_execution ())
-    {
-      gdb_printf (_("The program being debugged is not being run.\n"));
-      return;
-    }
+  thread_info *tp;
+
+  /* In non-stop, since every thread is controlled individually, we'll
+     show execution info about the current thread.  In all-stop, we'll
+     show execution info about the last stop.  */
 
   if (non_stop)
     {
-      ptid = inferior_ptid;
-      proc_target = current_inferior ()->process_target ();
+      if (!target_has_execution ())
+	{
+	  gdb_printf (_("The program being debugged is not being run.\n"));
+	  return;
+	}
+
+      if (inferior_ptid == null_ptid)
+	error (_("No selected thread."));
+
+      tp = inferior_thread ();
+
+      gdb_printf (_("Selected thread %s (%s).\n"),
+		  print_thread_id (tp),
+		  target_pid_to_str (tp->ptid).c_str ());
+
+      if (tp->state == THREAD_EXITED)
+	{
+	  gdb_printf (_("Selected thread has exited.\n"));
+	  return;
+	}
+      else if (tp->state == THREAD_RUNNING)
+	{
+	  gdb_printf (_("Selected thread is running.\n"));
+	  return;
+	}
     }
   else
-    get_last_target_status (&proc_target, &ptid, nullptr);
+    {
+      tp = get_previous_thread ();
+
+      if (tp == nullptr)
+	{
+	  gdb_printf (_("The program being debugged is not being run.\n"));
+	  return;
+	}
 
-  if (ptid == null_ptid || ptid == minus_one_ptid)
-    error (_("No selected thread."));
+      switch_to_thread (tp);
 
-  thread_info *tp = find_thread_ptid (proc_target, ptid);
+      gdb_printf (_("Last stopped for thread %s (%s).\n"),
+		  print_thread_id (tp),
+		  target_pid_to_str (tp->ptid).c_str ());
 
-  if (tp->state == THREAD_EXITED)
-    error (_("Invalid selected thread."));
-  else if (tp->state == THREAD_RUNNING)
-    error (_("Selected thread is running."));
+      if (tp->state == THREAD_EXITED)
+	{
+	  gdb_printf (_("Thread has since exited.\n"));
+	  return;
+	}
+
+      if (tp->state == THREAD_RUNNING)
+	{
+	  gdb_printf (_("Thread is now running.\n"));
+	  return;
+	}
+    }
 
-  bs = tp->control.stop_bpstat;
-  stat = bpstat_num (&bs, &num);
+  int num;
+  bpstat *bs = tp->control.stop_bpstat;
+  int stat = bpstat_num (&bs, &num);
 
   target_files_info ();
   gdb_printf (_("Program stopped at %s.\n"),
diff --git a/gdb/infrun.c b/gdb/infrun.c
index a936f18fbb1..252f6e5eca5 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -149,8 +149,9 @@ show_step_stop_if_no_debug (struct ui_file *file, int from_tty,
 }
 
 /* proceed and normal_stop use this to notify the user when the
-   inferior stopped in a different thread than it had been running
-   in.  */
+   inferior stopped in a different thread than it had been running in.
+   It can also be used to find for which thread normal_stop last
+   reported a stop.  */
 static thread_info_ref previous_thread;
 
 /* See infrun.h.  */
@@ -164,6 +165,14 @@ update_previous_thread ()
     previous_thread = thread_info_ref::new_reference (inferior_thread ());
 }
 
+/* See infrun.h.  */
+
+thread_info *
+get_previous_thread ()
+{
+  return previous_thread.get ();
+}
+
 /* If set (default for legacy reasons), when following a fork, GDB
    will detach from one of the fork branches, child or parent.
    Exactly which branch is detached depends on 'set follow-fork-mode'
diff --git a/gdb/infrun.h b/gdb/infrun.h
index acaec9b5064..18dd3c2ff60 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -121,6 +121,9 @@ extern enum exec_direction_kind execution_direction;
    inferior_thread, or at nullptr, if there's no selected thread.  */
 extern void update_previous_thread ();
 
+/* Get a weak reference to 'previous_thread'.  */
+extern thread_info *get_previous_thread ();
+
 extern void start_remote (int from_tty);
 
 /* Clear out all variables saying what to do when inferior is
diff --git a/gdb/testsuite/gdb.base/catch-follow-exec.exp b/gdb/testsuite/gdb.base/catch-follow-exec.exp
index 08aab086b60..e2e19981c0f 100644
--- a/gdb/testsuite/gdb.base/catch-follow-exec.exp
+++ b/gdb/testsuite/gdb.base/catch-follow-exec.exp
@@ -31,16 +31,25 @@ proc catch_follow_exec { } {
 	return -1
     }
 
-    gdb_test "catch exec" \
-	{Catchpoint [0-9]+ \(exec\)}
+    set bpnum ""
+    gdb_test_multiple "catch exec" "" {
+	-wrap -re "Catchpoint ($::decimal) \\\(exec\\\)" {
+	    set bpnum $expect_out(1,string)
+	}
+    }
+    if {$bpnum == ""} {
+	return
+    }
 
     gdb_test_no_output "set follow-exec-mode new"
 
     gdb_test "continue" \
-	".*hit Catchpoint.*"
+	"Thread 2.1 .*hit Catchpoint $bpnum.*"
+
+    set any "\[^\r\n\]*"
 
     gdb_test "info prog" \
-	"No selected thread."
+	"Last stopped for thread 2.1 \\\($any\\\)\\..*It stopped at breakpoint $bpnum\\..*"
 }
 
 catch_follow_exec
diff --git a/gdb/testsuite/gdb.base/info-program.c b/gdb/testsuite/gdb.base/info-program.c
new file mode 100644
index 00000000000..fcb3c614f5f
--- /dev/null
+++ b/gdb/testsuite/gdb.base/info-program.c
@@ -0,0 +1,66 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022 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/>.  */
+
+#ifdef USE_THREADS
+
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+pthread_barrier_t barrier;
+pthread_t child_thread;
+
+void *
+child_function (void *arg)
+{
+  pthread_barrier_wait (&barrier);
+
+  while (1)
+    usleep (100);
+
+  pthread_exit (NULL);
+}
+
+#endif /* USE_THREADS */
+
+static void
+done (void)
+{
+}
+
+int
+main (void)
+{
+#ifdef USE_THREADS
+  int res;
+
+  alarm (300);
+
+  pthread_barrier_init (&barrier, NULL, 2);
+
+  res = pthread_create (&child_thread, NULL, child_function, NULL);
+  pthread_barrier_wait (&barrier);
+#endif /* USE_THREADS */
+
+  done ();
+
+#ifdef USE_THREADS
+  pthread_join (child_thread, NULL);
+#endif /* USE_THREADS */
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/info-program.exp b/gdb/testsuite/gdb.base/info-program.exp
index 1496ea518dd..a1c779354b6 100644
--- a/gdb/testsuite/gdb.base/info-program.exp
+++ b/gdb/testsuite/gdb.base/info-program.exp
@@ -13,31 +13,124 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-standard_testfile normal.c
+# Test "info program".
+#
+# We build both single-threaded and multi-threaded programs so that if
+# the target doesn't support multi-threading, we still exercise the
+# command.
+#
+# With the multi-threaded program, we test that in all-stop mode, GDB
+# prints information about the last thread that stopped, not the
+# current thread.  In non-stop mode, the command always prints info
+# about the selected thread, so we test that.
 
-if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
-    return -1
-}
+standard_testfile
 
-if { ![runto_main] } {
-    return -1
-}
+# Run the test with the given parameters:
+#
+#   - THREADS: threads flavor, either single-threaded or
+#              multi-threaded.
+#   - NON-STOP: "set non-stop" value, "on" or "off".
+
+proc do_test { threads non-stop } {
+    save_vars { ::GDBFLAGS } {
+	append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
+	clean_restart $::binfile-$threads
+    }
+
+    gdb_test "info program" \
+	"The program being debugged is not being run." \
+	"info program before run"
+
+    if { ![runto done] } {
+	return -1
+    }
+
+    if {${non-stop} == "on"} {
+	set thread_line "Selected"
+    } else {
+	set thread_line "Last stopped for"
+    }
 
-gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped at breakpoint $decimal\.\r\nType \"info stack\" or \"info registers\" for more information\." \
-    "info program after run to main"
+    gdb_test "info program" \
+	[multi_line \
+	     "$thread_line thread 1 (\[^\r\n\]+)\\." \
+	     ".*" \
+	     "Program stopped at $::hex\." \
+	     "It stopped at breakpoint $::decimal\." \
+	     "Type \"info stack\" or \"info registers\" for more information\."] \
+	"info program after run to main"
 
-# We don't really care where this step lands, so long as GDB reports
-# that the inferior stopped  due to a step in the subsequent test.
-gdb_test "next" ".*" "step before info program"
+    # We don't really care where this step lands, so long as GDB reports
+    # that the inferior stopped  due to a step in the subsequent test.
+    gdb_test "next" ".*" "step before info program"
 
-gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped after being stepped\.\r\nType \"info stack\" or \"info registers\" for more information\." \
-    "info program after next"
+    gdb_test "info program" \
+	[multi_line \
+	     "$thread_line thread 1 (\[^\r\n\]+)\\." \
+	     ".*" \
+	     "Program stopped at $::hex\." \
+	     "It stopped after being stepped\." \
+	     "Type \"info stack\" or \"info registers\" for more information\."] \
+	"info program after next"
 
-if {![runto_main]} {
-    return -1
+    if {$threads == "mt"} {
+	gdb_test "thread 2" "\\\[Switching to thread 2 .*"
+
+	if {${non-stop} == "on"} {
+	    gdb_test "info program" \
+		[multi_line \
+		     "$thread_line thread 2 (\[^\r\n\]+)\\." \
+		     "Selected thread is running\\."] \
+		"info program after next, other thread"
+	} else {
+	    gdb_test "info program" \
+		[multi_line \
+		     "$thread_line thread 1 (\[^\r\n\]+)\\." \
+		     ".*" \
+		     "Program stopped at $::hex\." \
+		     "It stopped after being stepped\." \
+		     "Type \"info stack\" or \"info registers\" for more information\."] \
+		"info program after next, other thread"
+	}
+    }
+
+    gdb_test "kill" "" "kill program" \
+	"Kill the program being debugged.*y or n. $" "y"
+
+    gdb_test "info program" "The program being debugged is not being run." \
+	"info program, after kill"
+
+    if { ![runto done] } {
+	return -1
+    }
+
+    delete_breakpoints
+
+    gdb_test "info program" \
+	[multi_line \
+	     "$thread_line thread 1 (\[^\r\n\]+)\\." \
+	     ".*" \
+	     "Program stopped at $::hex\." \
+	     "It stopped at a breakpoint that has since been deleted\." \
+	     "Type \"info stack\" or \"info registers\" for more information\."] \
+	"info program after deleting all breakpoints"
 }
 
-delete_breakpoints
+# Build executables and test them, one for each
+# single-thread/multi-thread flavor.
+foreach_with_prefix threads {st mt} {
+    set opts {debug}
+    if {$threads == "mt"} {
+	lappend opts pthreads "additional_flags=-DUSE_THREADS"
+    }
 
-gdb_test "info program" "Program stopped at $hex\.\r\nIt stopped at a breakpoint that has since been deleted\.\r\nType \"info stack\" or \"info registers\" for more information\." \
-    "info program after deleting all breakpoints"
+    if { [build_executable "failed to prepare $threads" \
+	    ${testfile}-${threads} ${srcfile} $opts] } {
+	continue
+    }
+
+    foreach_with_prefix non-stop {on off} {
+	do_test ${threads} ${non-stop}
+    }
+}
