outermost, with UNWIND_SAME_ID stop reason.  Unlike the other
    validity tests, that compare THIS_FRAME and the next frame, we do
    this right after creating the previous frame, to avoid ever ending
-   up with two frames with the same id in the frame chain.  */
+   up with two frames with the same id in the frame chain.
+
+   There is however, one case where this cycle detection is not desirable,
+   when asking for the previous frame of an inline frame, in this case, if
+   the previous frame is a duplicate and we return nullptr then we will be
+   unable to calculate the frame_id of the inline frame, this in turn
+   causes inline_frame_this_id() to fail.  So for inline frames (and only
+   for inline frames), the previous frame will always be returned, even when it
+   has a duplicate frame_id.  We're not worried about cycles in the frame
+   chain as, if the previous frame returned here has a duplicate frame_id,
+   then the frame_id of the inline frame, calculated based off the frame_id
+   of the previous frame, should also be a duplicate.  */
 
 static struct frame_info *
-get_prev_frame_if_no_cycle (struct frame_info *this_frame)
+get_prev_frame_maybe_check_cycle (struct frame_info *this_frame)
 {
-  struct frame_info *prev_frame;
-
-  prev_frame = get_prev_frame_raw (this_frame);
+  struct frame_info *prev_frame = get_prev_frame_raw (this_frame);
 
   /* Don't compute the frame id of the current frame yet.  Unwinding
      the sentinel frame can fail (e.g., if the thread is gone and we
   try
     {
       compute_frame_id (prev_frame);
-      if (!frame_stash_add (prev_frame))
+
+      bool cycle_detection_p = get_frame_type (this_frame) != INLINE_FRAME;
+
+      /* This assert checks GDB's state with respect to calculating the
+        frame-id of THIS_FRAME, in the case where THIS_FRAME is an inline
+        frame.
+
+        If THIS_FRAME is frame #0, and is an inline frame, then we put off
+        calculating the frame_id until we specifically make a call to
+        get_frame_id().  As a result we can enter this function in two
+        possible states.  If GDB asked for the previous frame of frame #0
+        then THIS_FRAME will be frame #0 (an inline frame), and the
+        frame_id will be in the NOT_COMPUTED state.  However, if GDB asked
+        for the frame_id of frame #0, then, as getting the frame_id of an
+        inline frame requires us to get the frame_id of the previous
+        frame, we will still end up in here, and the frame_id status will
+        be COMPUTING.
+
+        If, instead, THIS_FRAME is at a level greater than #0 then things
+        are simpler.  For these frames we immediately compute the frame_id
+        when the frame is initially created, and so, for those frames, we
+        will always enter this function with the frame_id status of
+        COMPUTING.  */
+      gdb_assert (cycle_detection_p
+                 || (this_frame->level > 0
+                     && (this_frame->this_id.p
+                         == frame_id_status::COMPUTING))
+                 || (this_frame->level == 0
+                     && (this_frame->this_id.p
+                         != frame_id_status::COMPUTED)));
+
+      /* We must do the CYCLE_DETECTION_P check after attempting to add
+        PREV_FRAME into the cache; if PREV_FRAME is unique then we do want
+        it in the cache, but if it is a duplicate and CYCLE_DETECTION_P is
+        false, then we don't want to unlink it.  */
+      if (!frame_stash_add (prev_frame) && cycle_detection_p)
        {
          /* Another frame with the same id was already in the stash.  We just
             detected a cycle.  */
      until we have unwound all the way down to the previous non-inline
      frame.  */
   if (get_frame_type (this_frame) == INLINE_FRAME)
-    return get_prev_frame_if_no_cycle (this_frame);
+    return get_prev_frame_maybe_check_cycle (this_frame);
 
   /* If this_frame is the current frame, then compute and stash its
      frame id prior to fetching and computing the frame id of the
        }
     }
 
-  return get_prev_frame_if_no_cycle (this_frame);
+  return get_prev_frame_maybe_check_cycle (this_frame);
 }
 
 /* Return a "struct frame_info" corresponding to the frame that called
 
      function, there must be previous frames, so this is safe - as
      long as we're careful not to create any cycles.  See related
      comments in get_prev_frame_always_1.  */
-  *this_id = get_frame_id (get_prev_frame_always (this_frame));
+  frame_info *prev_frame = get_prev_frame_always (this_frame);
+  if (prev_frame == nullptr)
+    error (_("failed to find previous frame when computing inline frame id"));
+  *this_id = get_frame_id (prev_frame);
 
   /* We need a valid frame ID, so we need to be based on a valid
      frame.  FSF submission NOTE: this would be a good assertion to
 
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021 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/>.  */
+
+static void inline_func (void);
+static void normal_func (void);
+
+volatile int global_var;
+volatile int level_counter;
+
+static void __attribute__((noinline))
+normal_func (void)
+{
+  /* Do some work.  */
+  ++global_var;
+
+  /* Now the inline function.  */
+  --level_counter;
+  inline_func ();
+  ++level_counter;
+
+  /* Do some work.  */
+  ++global_var;
+}
+
+static inline void __attribute__((__always_inline__))
+inline_func (void)
+{
+  if (level_counter > 1)
+    {
+      --level_counter;
+      normal_func ();
+      ++level_counter;
+    }
+  else
+    ++global_var;      /* Break here.  */
+}
+
+int
+main ()
+{
+  level_counter = 6;
+  normal_func ();
+  return 0;
+}
 
--- /dev/null
+# Copyright (C) 2021 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/>.
+
+# This test checks for an edge case when unwinding inline frames which
+# occur towards the older end of the stack when the stack ends with a
+# cycle.  Consider this well formed stack:
+#
+#   main -> normal_frame -> inline_frame
+#
+# Now consider that, for whatever reason, the stack unwinding of
+# "normal_frame" becomes corrupted, such that the stack appears to be
+# this:
+#
+#   .-> normal_frame -> inline_frame
+#   |      |
+#   '------'
+#
+# When confronted with such a situation we would expect GDB to detect
+# the stack frame cycle and terminate the backtrace at the first
+# instance of "normal_frame" with a message:
+#
+#   Backtrace stopped: previous frame identical to this frame (corrupt stack?)
+#
+# However, at one point there was a bug in GDB's inline frame
+# mechanism such that the fact that "inline_frame" was inlined into
+# "normal_frame" would cause GDB to trigger an assertion.
+#
+# This text makes use of a Python unwinder which can fake the cyclic
+# stack cycle, further the test sets up multiple levels of normal and
+# inline frames.  At the point of testing the stack looks like this:
+#
+#   main -> normal_func -> inline_func -> normal_func -> inline_func -> normal_func -> inline_func
+#
+# Where "normal_func" is a normal frame, and "inline_func" is an inline frame.
+#
+# The python unwinder is then used to force a stack cycle at each
+# "normal_func" frame in turn, we then check that GDB can successfully unwind
+# the stack.
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}]} {
+    return -1
+}
+
+# Skip this test if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+if ![runto_main] then {
+    fail "can't run to main"
+    return 0
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+# Run to the breakpoint where we will carry out the test.
+gdb_breakpoint [gdb_get_line_number "Break here"]
+gdb_continue_to_breakpoint "stop at test breakpoint"
+
+# Load the script containing the unwinder, this must be done at the
+# testing point as the script will examine the stack as it is loaded.
+gdb_test_no_output "source ${pyfile}"\
+    "import python scripts"
+
+# Check the unbroken stack.
+gdb_test_sequence "bt" "backtrace when the unwind is left unbroken" {
+    "\\r\\n#0 \[^\r\n\]* inline_func \\(\\) at "
+    "\\r\\n#1 \[^\r\n\]* normal_func \\(\\) at "
+    "\\r\\n#2 \[^\r\n\]* inline_func \\(\\) at "
+    "\\r\\n#3 \[^\r\n\]* normal_func \\(\\) at "
+    "\\r\\n#4 \[^\r\n\]* inline_func \\(\\) at "
+    "\\r\\n#5 \[^\r\n\]* normal_func \\(\\) at "
+    "\\r\\n#6 \[^\r\n\]* main \\(\\) at "
+}
+
+with_test_prefix "cycle at level 5" {
+    # Arrange to introduce a stack cycle at frame 5.
+    gdb_test_no_output "python stop_at_level=5"
+    gdb_test "maint flush register-cache" \
+       "Register cache flushed\\."
+    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 5" \
+       [multi_line \
+            "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "#4 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#5 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+}
+
+with_test_prefix "cycle at level 3" {
+    # Arrange to introduce a stack cycle at frame 3.
+    gdb_test_no_output "python stop_at_level=3"
+    gdb_test "maint flush register-cache" \
+       "Register cache flushed\\."
+    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 3" \
+       [multi_line \
+            "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+}
+
+with_test_prefix "cycle at level 1" {
+    # Arrange to introduce a stack cycle at frame 1.
+    gdb_test_no_output "python stop_at_level=1"
+    gdb_test "maint flush register-cache" \
+       "Register cache flushed\\."
+    gdb_test_lines "bt" "backtrace when the unwind is broken at frame 1" \
+       [multi_line \
+            "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+            "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+            "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+}
+
+# Flush the register cache (which also flushes the frame cache) so we
+# get a full backtrace again, then switch on frame debugging and try
+# to back trace.  At one point this triggered an assertion.
+gdb_test "maint flush register-cache" \
+    "Register cache flushed\\." ""
+gdb_test_no_output "set debug frame 1"
+gdb_test_multiple "bt" "backtrace with debugging on" {
+    -re "^$gdb_prompt $" {
+       pass $gdb_test_name
+    }
+    -re "\[^\r\n\]+\r\n" {
+       exp_continue
+    }
+}
+gdb_test "p 1 + 2 + 3" " = 6" \
+    "ensure GDB is still alive"
 
--- /dev/null
+# Copyright (C) 2021 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/>.
+
+import gdb
+from gdb.unwinder import Unwinder
+
+# Set this to the stack level the backtrace should be corrupted at.
+# This will only work for frame 1, 3, or 5 in the test this unwinder
+# was written for.
+stop_at_level = None
+
+# Set this to the stack frame size of frames 1, 3, and 5.  These
+# frames will all have the same stack frame size as they are the same
+# function called recursively.
+stack_adjust = None
+
+
+class FrameId(object):
+    def __init__(self, sp, pc):
+        self._sp = sp
+        self._pc = pc
+
+    @property
+    def sp(self):
+        return self._sp
+
+    @property
+    def pc(self):
+        return self._pc
+
+
+class TestUnwinder(Unwinder):
+    def __init__(self):
+        Unwinder.__init__(self, "stop at level")
+
+    def __call__(self, pending_frame):
+        global stop_at_level
+        global stack_adjust
+
+        if stop_at_level is None or pending_frame.level() != stop_at_level:
+            return None
+
+        if stack_adjust is None:
+            raise gdb.GdbError("invalid stack_adjust")
+
+        if not stop_at_level in [1, 3, 5]:
+            raise gdb.GdbError("invalid stop_at_level")
+
+        sp_desc = pending_frame.architecture().registers().find("sp")
+        sp = pending_frame.read_register(sp_desc) + stack_adjust
+        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
+        unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
+
+        for reg in pending_frame.architecture().registers("general"):
+            val = pending_frame.read_register(reg)
+            unwinder.add_saved_register(reg, val)
+        return unwinder
+
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
+
+# When loaded, it is expected that the stack looks like:
+#
+#   main -> normal_func -> inline_func -> normal_func -> inline_func -> normal_func -> inline_func
+#
+# Compute the stack frame size of normal_func, which has inline_func
+# inlined within it.
+f0 = gdb.newest_frame()
+f1 = f0.older()
+f2 = f1.older()
+f0_sp = f0.read_register("sp")
+f2_sp = f2.read_register("sp")
+stack_adjust = f2_sp - f0_sp