diff --git a/gdb/frame.c b/gdb/frame.c
index 0909109c1f4..9ab8fa0310e 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -69,6 +69,7 @@ set_backtrace_options user_set_backtrace_options;
static frame_info_ptr get_prev_frame_raw (frame_info_ptr this_frame);
static const char *frame_stop_reason_symbol_string (enum unwind_stop_reason reason);
+static frame_info_ptr create_new_frame (frame_id id);
/* Status of some values cached in the frame_info object. */
@@ -1669,9 +1670,12 @@ get_current_frame (void)
If SELECTED_FRAME_ID / SELECTED_FRAME_LEVEL are null_frame_id / -1,
and the target has stack and is stopped, the selected frame is the
- current (innermost) frame. This means that SELECTED_FRAME_LEVEL is
- never 0 and SELECTED_FRAME_ID is never the ID of the innermost
- frame.
+ current (innermost) target frame. SELECTED_FRAME_ID is never the ID
+ of the current (innermost) target frame. SELECTED_FRAME_LEVEL may
+ only be 0 if the selected frame is a user-created one (created and
+ selected through the "select-frame view" command), in which case
+ SELECTED_FRAME_ID is the frame id derived from the user-provided
+ addresses.
If SELECTED_FRAME_ID / SELECTED_FRAME_LEVEL are null_frame_id / -1,
and the target has no stack or is executing, then there's no
@@ -1699,9 +1703,9 @@ void
restore_selected_frame (frame_id frame_id, int frame_level)
noexcept
{
- /* save_selected_frame never returns level == 0, so we shouldn't see
- it here either. */
- gdb_assert (frame_level != 0);
+ /* Unless it is a user-created frame, save_selected_frame never returns
+ level == 0, so we shouldn't see it here either. */
+ gdb_assert (frame_level != 0 || frame_id.user_created_p);
/* FRAME_ID can be null_frame_id only IFF frame_level is -1. */
gdb_assert ((frame_level == -1 && !frame_id_p (frame_id))
@@ -1735,6 +1739,15 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
return;
}
+ /* This means the selected frame was a user-created one. Create a new one
+ using the user-provided addresses, which happen to be in the frame id. */
+ if (frame_level == 0)
+ {
+ gdb_assert (a_frame_id.user_created_p);
+ select_frame (create_new_frame (a_frame_id));
+ return;
+ }
+
/* select_frame never saves 0 in SELECTED_FRAME_LEVEL, so we
shouldn't see it here. */
gdb_assert (frame_level > 0);
@@ -1859,7 +1872,10 @@ select_frame (frame_info_ptr fi)
selected_frame = fi;
selected_frame_level = frame_relative_level (fi);
- if (selected_frame_level == 0)
+
+ /* If the frame is a user-created one, save its level and frame id just like
+ any other non-level-0 frame. */
+ if (selected_frame_level == 0 && !fi->this_id.value.user_created_p)
{
/* Treat the current frame especially -- we want to always
save/restore it without warning, even if the frame ID changes
diff --git a/gdb/testsuite/gdb.base/frame-view.c b/gdb/testsuite/gdb.base/frame-view.c
new file mode 100644
index 00000000000..7b1cbd6abd4
--- /dev/null
+++ b/gdb/testsuite/gdb.base/frame-view.c
@@ -0,0 +1,74 @@
+/* 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 . */
+
+#include
+#include
+
+struct type_1
+{
+ int m;
+};
+
+struct type_2
+{
+ int n;
+};
+
+static int
+baz (struct type_1 z1, struct type_2 z2)
+{
+ return z1.m + z2.n;
+}
+
+static int
+bar (struct type_1 y1, struct type_2 y2)
+{
+ return baz (y1, y2);
+}
+
+static int
+foo (struct type_1 x1, struct type_2 x2)
+{
+ return bar (x1, x2);
+}
+
+static void *
+thread_func (void *p)
+{
+ struct type_1 t1;
+ struct type_2 t2;
+ t1.m = 11;
+ t2.n = 11;
+ foo (t1, t2);
+
+ return NULL;
+}
+
+int
+main (void)
+{
+ pthread_t thread;
+ int res;
+
+ res = pthread_create (&thread, NULL, thread_func, NULL);
+ assert (res == 0);
+
+ res = pthread_join (thread, NULL);
+ assert (res == 0);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/frame-view.exp b/gdb/testsuite/gdb.base/frame-view.exp
new file mode 100644
index 00000000000..d2dba143448
--- /dev/null
+++ b/gdb/testsuite/gdb.base/frame-view.exp
@@ -0,0 +1,70 @@
+# 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 .
+
+# Test the "frame view" family of commands.
+
+standard_testfile
+
+if { [build_executable "failed to prepare" \
+ ${testfile} ${srcfile}] } {
+ return
+}
+
+proc test_select_frame_view {} {
+ clean_restart $::binfile
+
+ if { ![runto_main] } {
+ return
+ }
+
+ # Stop thread 2 at a baz.
+ gdb_test "break baz"
+ gdb_test "continue" "Thread 2.*hit Breakpoint $::decimal, baz .*"
+
+ # Grab the stack pointer and pc of thread 2's frame.
+ set frame_sp ""
+ set frame_pc ""
+
+ gdb_test_multiple "info frame" "" {
+ -re -wrap ".*frame at ($::hex):.*" {
+ set frame_sp $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ gdb_test_multiple "print/x \$pc" "" {
+ -re -wrap " = ($::hex)" {
+ set frame_pc $expect_out(1,string)
+ pass $gdb_test_name
+ }
+ }
+
+ if { $frame_sp == "" || $frame_pc == "" } {
+ # Something must have failed and logged a failure above.
+ return
+ }
+
+ # Select thread 2's frame in thread 1.
+ gdb_test "thread 1" "Switching to thread 1 .*"
+ gdb_test_no_output "select-frame view $frame_sp $frame_pc"
+
+ # Verify that the "frame" command does not change the selected frame.
+ # There used to be a bug where the "frame" command would lose the
+ # selection of user-created frames.
+ gdb_test "frame" "#0 baz \\(z1=.*, z2=.*\\).*" "frame"
+ gdb_test "frame" "#0 baz \\(z1=.*, z2=.*\\).*" "frame again"
+}
+
+test_select_frame_view