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