[3/5] gdb/python: add PendingFrame.level and Frame.level methods

Message ID 5d6ac8c94e5c473d533d945ba9759af2d8ae57a6.1622321523.git.andrew.burgess@embecosm.com
State New
Headers show
Series
  • Fix for an assertion when unwinding with inline frames
Related show

Commit Message

Andrew Burgess May 29, 2021, 8:57 p.m.
Add new methods to the PendingFrame and Frame classes to obtain the
stack frame level for each object.

The use of 'level' as the method name is consistent with the existing
attribute RecordFunctionSegment.level (though this is an attribute
rather than a method).

For Frame/PendingFrame I went with methods as these classes currently
only use methods, including for simple data like architecture, so I
want to be consistent with this interface.

gdb/ChangeLog:

	* python/py-frame.c (frapy_level): New function.
	(frame_object_methods): Register 'level' method.
	* python/py-unwind.c (pending_framepy_level): New function.
	(pending_frame_object_methods): Register 'level' method.

gdb/doc/ChangeLog:

	* python.texi (Unwinding Frames in Python): Mention
	PendingFrame.level.
	(Frames In Python): Mention Frame.level.

gdb/testsuite/ChangeLog:

	* gdb.python/py-frame.exp: Add Frame.level tests.
	* gdb.python/py-pending-frame-level.c: New file.
	* gdb.python/py-pending-frame-level.exp: New file.
	* gdb.python/py-pending-frame-level.py: New file.
---
 gdb/ChangeLog                                 |  7 ++
 gdb/doc/ChangeLog                             |  6 ++
 gdb/doc/python.texi                           |  9 +++
 gdb/python/py-frame.c                         | 23 +++++++
 gdb/python/py-unwind.c                        | 19 ++++++
 gdb/testsuite/ChangeLog                       |  7 ++
 gdb/testsuite/gdb.python/py-frame.exp         | 11 ++++
 .../gdb.python/py-pending-frame-level.c       | 49 ++++++++++++++
 .../gdb.python/py-pending-frame-level.exp     | 65 +++++++++++++++++++
 .../gdb.python/py-pending-frame-level.py      | 55 ++++++++++++++++
 10 files changed, 251 insertions(+)
 create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.c
 create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.exp
 create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.py

-- 
2.25.4

Comments

Simon Marchi via Gdb-patches May 30, 2021, 5:55 a.m. | #1
> From: Andrew Burgess <andrew.burgess@embecosm.com>

> Date: Sat, 29 May 2021 21:57:12 +0100

> 

> Add new methods to the PendingFrame and Frame classes to obtain the

> stack frame level for each object.

> 

> The use of 'level' as the method name is consistent with the existing

> attribute RecordFunctionSegment.level (though this is an attribute

> rather than a method).

> 

> For Frame/PendingFrame I went with methods as these classes currently

> only use methods, including for simple data like architecture, so I

> want to be consistent with this interface.

> 

> gdb/ChangeLog:

> 

> 	* python/py-frame.c (frapy_level): New function.

> 	(frame_object_methods): Register 'level' method.

> 	* python/py-unwind.c (pending_framepy_level): New function.

> 	(pending_frame_object_methods): Register 'level' method.

> 

> gdb/doc/ChangeLog:

> 

> 	* python.texi (Unwinding Frames in Python): Mention

> 	PendingFrame.level.

> 	(Frames In Python): Mention Frame.level.

> 

> gdb/testsuite/ChangeLog:

> 

> 	* gdb.python/py-frame.exp: Add Frame.level tests.

> 	* gdb.python/py-pending-frame-level.c: New file.

> 	* gdb.python/py-pending-frame-level.exp: New file.

> 	* gdb.python/py-pending-frame-level.py: New file.


OK for the documentation part.

Thanks.
Andrew Burgess May 30, 2021, 6:34 p.m. | #2
I realised that I failed to add a NEW entry.  The patch below is
identical to the original patch, but also includes a NEWS entry.

Thanks,
Andrew

--

commit 6a686fcc389d6b3eb20e200ca9703c9ee2226343
Author: Andrew Burgess <andrew.burgess@embecosm.com>
Date:   Wed May 26 22:01:59 2021 +0100

    gdb/python: add PendingFrame.level and Frame.level methods
    
    Add new methods to the PendingFrame and Frame classes to obtain the
    stack frame level for each object.
    
    The use of 'level' as the method name is consistent with the existing
    attribute RecordFunctionSegment.level (though this is an attribute
    rather than a method).
    
    For Frame/PendingFrame I went with methods as these classes currently
    only use methods, including for simple data like architecture, so I
    want to be consistent with this interface.
    
    gdb/ChangeLog:
    
            * NEWS: Mention the two new methods.
            * python/py-frame.c (frapy_level): New function.
            (frame_object_methods): Register 'level' method.
            * python/py-unwind.c (pending_framepy_level): New function.
            (pending_frame_object_methods): Register 'level' method.
    
    gdb/doc/ChangeLog:
    
            * python.texi (Unwinding Frames in Python): Mention
            PendingFrame.level.
            (Frames In Python): Mention Frame.level.
    
    gdb/testsuite/ChangeLog:
    
            * gdb.python/py-frame.exp: Add Frame.level tests.
            * gdb.python/py-pending-frame-level.c: New file.
            * gdb.python/py-pending-frame-level.exp: New file.
            * gdb.python/py-pending-frame-level.py: New file.

diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..0b8b363b87b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -217,6 +217,12 @@ QMemTags
      gives the connection number as seen in 'info connections' and
      'info inferiors'.
 
+  ** New method gdb.Frame.level() which returns the stack level of the
+     frame object.
+
+  ** New method gdb.PendingFrame.level() which returns the stack level
+     of the frame object.
+
 *** Changes in GDB 10
 
 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core"
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 23e6ac666ff..b7e16351a5d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2605,6 +2605,11 @@
 the particular frame being unwound.
 @end defun
 
+@defun PendingFrame.level ()
+Return an integer, the stack frame level for this frame.
+@xref{Frames, ,Stack Frames}.
+@end defun
+
 @subheading Unwinder Output: UnwindInfo
 
 Use @code{PendingFrame.create_unwind_info} method described above to
@@ -4813,6 +4818,10 @@
 Stack}.
 @end defun
 
+@defun Frame.level ()
+Return an integer, the stack frame level for this frame.  @xref{Frames, ,Stack Frames}.
+@end defun
+
 @node Blocks In Python
 @subsubsection Accessing blocks from Python
 
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index c8eab5291ea..4f218c40367 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -577,6 +577,27 @@ frapy_select (PyObject *self, PyObject *args)
   Py_RETURN_NONE;
 }
 
+/* The stack frame level for this frame.  */
+
+static PyObject *
+frapy_level (PyObject *self, PyObject *args)
+{
+  struct frame_info *fi;
+
+  try
+    {
+      FRAPY_REQUIRE_VALID (self, fi);
+
+      return gdb_py_object_from_int (frame_relative_level (fi)).release ();
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  Py_RETURN_NONE;
+}
+
 /* Implementation of gdb.newest_frame () -> gdb.Frame.
    Returns the newest frame object.  */
 
@@ -748,6 +769,8 @@ Return the frame's symtab and line." },
 Return the value of the variable in this frame." },
   { "select", frapy_select, METH_NOARGS,
     "Select this frame as the user's current frame." },
+  { "level", frapy_level, METH_NOARGS,
+    "The stack level of this frame." },
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index d6e2f85dbc1..ff1a7e922a7 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -463,6 +463,23 @@ pending_framepy_architecture (PyObject *self, PyObject *args)
   return gdbarch_to_arch_object (pending_frame->gdbarch);
 }
 
+/* Implementation of PendingFrame.level (self) -> Integer.  */
+
+static PyObject *
+pending_framepy_level (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  if (pending_frame->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+		       "Attempting to read stack level from stale PendingFrame");
+      return NULL;
+    }
+  int level = frame_relative_level (pending_frame->frame_info);
+  return gdb_py_object_from_int (level).release ();
+}
+
 /* frame_unwind.this_id method.  */
 
 static void
@@ -704,6 +721,8 @@ static PyMethodDef pending_frame_object_methods[] =
     pending_framepy_architecture, METH_NOARGS,
     "architecture () -> gdb.Architecture\n"
     "The architecture for this PendingFrame." },
+  { "level", pending_framepy_level, METH_NOARGS,
+    "The stack level of this frame." },
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp
index a6a5c0de726..05c7fb00dfd 100644
--- a/gdb/testsuite/gdb.python/py-frame.exp
+++ b/gdb/testsuite/gdb.python/py-frame.exp
@@ -70,6 +70,17 @@ gdb_test "up" ".*" ""
 
 gdb_py_test_silent_cmd "python f1 = gdb.selected_frame ()" "get second frame" 0
 gdb_py_test_silent_cmd "python f0 = f1.newer ()" "get first frame" 0
+gdb_py_test_silent_cmd "python f2 = f1.older ()" "get last frame" 0
+
+# Check the Frame.level method.
+gdb_test "python print ('bframe.level = %d' % bframe.level ())" \
+    "bframe\\.level = 0"
+gdb_test "python print ('f0.level = %d' % f0.level ())" \
+    "f0\\.level = 0"
+gdb_test "python print ('f1.level = %d' % f1.level ())" \
+    "f1\\.level = 1"
+gdb_test "python print ('f2.level = %d' % f2.level ())" \
+    "f2\\.level = 2"
 
 gdb_test "python print (f1 == gdb.newest_frame())" False \
     "selected frame -vs- newest frame"
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.c b/gdb/testsuite/gdb.python/py-pending-frame-level.c
new file mode 100644
index 00000000000..5e5495c1d71
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.c
@@ -0,0 +1,49 @@
+/* This test program 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/>.  */
+
+volatile int global_var;
+
+void __attribute__ ((noinline))
+f0 (void)
+{
+  ++global_var;		/* Break here.  */
+}
+
+void __attribute__ ((noinline))
+f1 (void)
+{
+  f0 ();
+}
+
+void __attribute__ ((noinline))
+f2 (void)
+{
+  f1 ();
+}
+
+void __attribute__ ((noinline))
+f3 (void)
+{
+  f2 ();
+}
+
+int
+main (void)
+{
+  f3 ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.exp b/gdb/testsuite/gdb.python/py-pending-frame-level.exp
new file mode 100644
index 00000000000..1aadcaeacae
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.exp
@@ -0,0 +1,65 @@
+# 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/>.
+
+# Test gdb.PendingFrame.level method.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests 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]
+
+gdb_breakpoint [gdb_get_line_number "Break here"]
+gdb_continue_to_breakpoint "stop at test breakpoint"
+
+# An initial look at the stack to ensure it is correct.
+gdb_test_sequence "bt"  "Initial backtrace" {
+    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "
+    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "
+    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "
+    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "
+    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "
+}
+
+# Load the script containing the unwinder.
+gdb_test_no_output "source ${pyfile}"\
+    "import python scripts"
+
+# Now look at the stack again, we should see output from the Python
+# unwinder mixed in.
+gdb_test_sequence "bt"  "Backtrace with extra Python output" {
+    "Func f0, Level 0"
+    "Func f1, Level 1"
+    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "
+    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "
+    "Func f2, Level 2"
+    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "
+    "Func f3, Level 3"
+    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "
+    "Func main, Level 4"
+    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "
+}
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.py b/gdb/testsuite/gdb.python/py-pending-frame-level.py
new file mode 100644
index 00000000000..182edcdc0df
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.py
@@ -0,0 +1,55 @@
+# 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
+
+
+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, "show level")
+
+    def __call__(self, pending_frame):
+        pc_desc = pending_frame.architecture().registers().find("pc")
+        pc = pending_frame.read_register(pc_desc)
+
+        block = gdb.block_for_pc(int(pc))
+        if block == None:
+            return None
+        func = block.function
+        if func == None:
+            return None
+
+        print("Func %s, Level %d" % (str(func), pending_frame.level()))
+
+        # This unwinder never claims any frames.
+        return None
+
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
Simon Marchi via Gdb-patches May 30, 2021, 6:54 p.m. | #3
> Date: Sun, 30 May 2021 19:34:42 +0100

> From: Andrew Burgess <andrew.burgess@embecosm.com>

> 

> I realised that I failed to add a NEW entry.  The patch below is

> identical to the original patch, but also includes a NEWS entry.


The NEWS entry is OK, thanks.
Tom Tromey June 7, 2021, 2:57 p.m. | #4
>>>>> "Andrew" == Andrew Burgess <andrew.burgess@embecosm.com> writes:


Andrew> Add new methods to the PendingFrame and Frame classes to obtain the
Andrew> stack frame level for each object.

Andrew> The use of 'level' as the method name is consistent with the existing
Andrew> attribute RecordFunctionSegment.level (though this is an attribute
Andrew> rather than a method).

Andrew> For Frame/PendingFrame I went with methods as these classes currently
Andrew> only use methods, including for simple data like architecture, so I
Andrew> want to be consistent with this interface.

gdb is already inconsistent here, something we should have been more
careful about.

This looks good to me.  Thank you.

Tom
Andrew Burgess June 21, 2021, 7:42 p.m. | #5
I've pushed this patch now.

Thanks,
Andrew

* Andrew Burgess <andrew.burgess@embecosm.com> [2021-05-29 21:57:12 +0100]:

> Add new methods to the PendingFrame and Frame classes to obtain the

> stack frame level for each object.

> 

> The use of 'level' as the method name is consistent with the existing

> attribute RecordFunctionSegment.level (though this is an attribute

> rather than a method).

> 

> For Frame/PendingFrame I went with methods as these classes currently

> only use methods, including for simple data like architecture, so I

> want to be consistent with this interface.

> 

> gdb/ChangeLog:

> 

> 	* python/py-frame.c (frapy_level): New function.

> 	(frame_object_methods): Register 'level' method.

> 	* python/py-unwind.c (pending_framepy_level): New function.

> 	(pending_frame_object_methods): Register 'level' method.

> 

> gdb/doc/ChangeLog:

> 

> 	* python.texi (Unwinding Frames in Python): Mention

> 	PendingFrame.level.

> 	(Frames In Python): Mention Frame.level.

> 

> gdb/testsuite/ChangeLog:

> 

> 	* gdb.python/py-frame.exp: Add Frame.level tests.

> 	* gdb.python/py-pending-frame-level.c: New file.

> 	* gdb.python/py-pending-frame-level.exp: New file.

> 	* gdb.python/py-pending-frame-level.py: New file.

> ---

>  gdb/ChangeLog                                 |  7 ++

>  gdb/doc/ChangeLog                             |  6 ++

>  gdb/doc/python.texi                           |  9 +++

>  gdb/python/py-frame.c                         | 23 +++++++

>  gdb/python/py-unwind.c                        | 19 ++++++

>  gdb/testsuite/ChangeLog                       |  7 ++

>  gdb/testsuite/gdb.python/py-frame.exp         | 11 ++++

>  .../gdb.python/py-pending-frame-level.c       | 49 ++++++++++++++

>  .../gdb.python/py-pending-frame-level.exp     | 65 +++++++++++++++++++

>  .../gdb.python/py-pending-frame-level.py      | 55 ++++++++++++++++

>  10 files changed, 251 insertions(+)

>  create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.c

>  create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.exp

>  create mode 100644 gdb/testsuite/gdb.python/py-pending-frame-level.py

> 

> diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi

> index 23e6ac666ff..b7e16351a5d 100644

> --- a/gdb/doc/python.texi

> +++ b/gdb/doc/python.texi

> @@ -2605,6 +2605,11 @@

>  the particular frame being unwound.

>  @end defun

>  

> +@defun PendingFrame.level ()

> +Return an integer, the stack frame level for this frame.

> +@xref{Frames, ,Stack Frames}.

> +@end defun

> +

>  @subheading Unwinder Output: UnwindInfo

>  

>  Use @code{PendingFrame.create_unwind_info} method described above to

> @@ -4813,6 +4818,10 @@

>  Stack}.

>  @end defun

>  

> +@defun Frame.level ()

> +Return an integer, the stack frame level for this frame.  @xref{Frames, ,Stack Frames}.

> +@end defun

> +

>  @node Blocks In Python

>  @subsubsection Accessing blocks from Python

>  

> diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c

> index c8eab5291ea..4f218c40367 100644

> --- a/gdb/python/py-frame.c

> +++ b/gdb/python/py-frame.c

> @@ -577,6 +577,27 @@ frapy_select (PyObject *self, PyObject *args)

>    Py_RETURN_NONE;

>  }

>  

> +/* The stack frame level for this frame.  */

> +

> +static PyObject *

> +frapy_level (PyObject *self, PyObject *args)

> +{

> +  struct frame_info *fi;

> +

> +  try

> +    {

> +      FRAPY_REQUIRE_VALID (self, fi);

> +

> +      return gdb_py_object_from_int (frame_relative_level (fi)).release ();

> +    }

> +  catch (const gdb_exception &except)

> +    {

> +      GDB_PY_HANDLE_EXCEPTION (except);

> +    }

> +

> +  Py_RETURN_NONE;

> +}

> +

>  /* Implementation of gdb.newest_frame () -> gdb.Frame.

>     Returns the newest frame object.  */

>  

> @@ -748,6 +769,8 @@ Return the frame's symtab and line." },

>  Return the value of the variable in this frame." },

>    { "select", frapy_select, METH_NOARGS,

>      "Select this frame as the user's current frame." },

> +  { "level", frapy_level, METH_NOARGS,

> +    "The stack level of this frame." },

>    {NULL}  /* Sentinel */

>  };

>  

> diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c

> index d6e2f85dbc1..ff1a7e922a7 100644

> --- a/gdb/python/py-unwind.c

> +++ b/gdb/python/py-unwind.c

> @@ -463,6 +463,23 @@ pending_framepy_architecture (PyObject *self, PyObject *args)

>    return gdbarch_to_arch_object (pending_frame->gdbarch);

>  }

>  

> +/* Implementation of PendingFrame.level (self) -> Integer.  */

> +

> +static PyObject *

> +pending_framepy_level (PyObject *self, PyObject *args)

> +{

> +  pending_frame_object *pending_frame = (pending_frame_object *) self;

> +

> +  if (pending_frame->frame_info == NULL)

> +    {

> +      PyErr_SetString (PyExc_ValueError,

> +		       "Attempting to read stack level from stale PendingFrame");

> +      return NULL;

> +    }

> +  int level = frame_relative_level (pending_frame->frame_info);

> +  return gdb_py_object_from_int (level).release ();

> +}

> +

>  /* frame_unwind.this_id method.  */

>  

>  static void

> @@ -704,6 +721,8 @@ static PyMethodDef pending_frame_object_methods[] =

>      pending_framepy_architecture, METH_NOARGS,

>      "architecture () -> gdb.Architecture\n"

>      "The architecture for this PendingFrame." },

> +  { "level", pending_framepy_level, METH_NOARGS,

> +    "The stack level of this frame." },

>    {NULL}  /* Sentinel */

>  };

>  

> diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp

> index a6a5c0de726..05c7fb00dfd 100644

> --- a/gdb/testsuite/gdb.python/py-frame.exp

> +++ b/gdb/testsuite/gdb.python/py-frame.exp

> @@ -70,6 +70,17 @@ gdb_test "up" ".*" ""

>  

>  gdb_py_test_silent_cmd "python f1 = gdb.selected_frame ()" "get second frame" 0

>  gdb_py_test_silent_cmd "python f0 = f1.newer ()" "get first frame" 0

> +gdb_py_test_silent_cmd "python f2 = f1.older ()" "get last frame" 0

> +

> +# Check the Frame.level method.

> +gdb_test "python print ('bframe.level = %d' % bframe.level ())" \

> +    "bframe\\.level = 0"

> +gdb_test "python print ('f0.level = %d' % f0.level ())" \

> +    "f0\\.level = 0"

> +gdb_test "python print ('f1.level = %d' % f1.level ())" \

> +    "f1\\.level = 1"

> +gdb_test "python print ('f2.level = %d' % f2.level ())" \

> +    "f2\\.level = 2"

>  

>  gdb_test "python print (f1 == gdb.newest_frame())" False \

>      "selected frame -vs- newest frame"

> diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.c b/gdb/testsuite/gdb.python/py-pending-frame-level.c

> new file mode 100644

> index 00000000000..5e5495c1d71

> --- /dev/null

> +++ b/gdb/testsuite/gdb.python/py-pending-frame-level.c

> @@ -0,0 +1,49 @@

> +/* This test program 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/>.  */

> +

> +volatile int global_var;

> +

> +void __attribute__ ((noinline))

> +f0 (void)

> +{

> +  ++global_var;		/* Break here.  */

> +}

> +

> +void __attribute__ ((noinline))

> +f1 (void)

> +{

> +  f0 ();

> +}

> +

> +void __attribute__ ((noinline))

> +f2 (void)

> +{

> +  f1 ();

> +}

> +

> +void __attribute__ ((noinline))

> +f3 (void)

> +{

> +  f2 ();

> +}

> +

> +int

> +main (void)

> +{

> +  f3 ();

> +  return 0;

> +}

> diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.exp b/gdb/testsuite/gdb.python/py-pending-frame-level.exp

> new file mode 100644

> index 00000000000..1aadcaeacae

> --- /dev/null

> +++ b/gdb/testsuite/gdb.python/py-pending-frame-level.exp

> @@ -0,0 +1,65 @@

> +# 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/>.

> +

> +# Test gdb.PendingFrame.level method.

> +

> +load_lib gdb-python.exp

> +

> +standard_testfile

> +

> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {

> +    return -1

> +}

> +

> +# Skip all tests 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]

> +

> +gdb_breakpoint [gdb_get_line_number "Break here"]

> +gdb_continue_to_breakpoint "stop at test breakpoint"

> +

> +# An initial look at the stack to ensure it is correct.

> +gdb_test_sequence "bt"  "Initial backtrace" {

> +    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "

> +    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "

> +    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "

> +    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "

> +    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "

> +}

> +

> +# Load the script containing the unwinder.

> +gdb_test_no_output "source ${pyfile}"\

> +    "import python scripts"

> +

> +# Now look at the stack again, we should see output from the Python

> +# unwinder mixed in.

> +gdb_test_sequence "bt"  "Backtrace with extra Python output" {

> +    "Func f0, Level 0"

> +    "Func f1, Level 1"

> +    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "

> +    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "

> +    "Func f2, Level 2"

> +    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "

> +    "Func f3, Level 3"

> +    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "

> +    "Func main, Level 4"

> +    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "

> +}

> diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.py b/gdb/testsuite/gdb.python/py-pending-frame-level.py

> new file mode 100644

> index 00000000000..182edcdc0df

> --- /dev/null

> +++ b/gdb/testsuite/gdb.python/py-pending-frame-level.py

> @@ -0,0 +1,55 @@

> +# 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

> +

> +

> +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, "show level")

> +

> +    def __call__(self, pending_frame):

> +        pc_desc = pending_frame.architecture().registers().find("pc")

> +        pc = pending_frame.read_register(pc_desc)

> +

> +        block = gdb.block_for_pc(int(pc))

> +        if block == None:

> +            return None

> +        func = block.function

> +        if func == None:

> +            return None

> +

> +        print("Func %s, Level %d" % (str(func), pending_frame.level()))

> +

> +        # This unwinder never claims any frames.

> +        return None

> +

> +

> +gdb.unwinder.register_unwinder(None, TestUnwinder(), True)

> -- 

> 2.25.4

>

Patch

diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 23e6ac666ff..b7e16351a5d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2605,6 +2605,11 @@ 
 the particular frame being unwound.
 @end defun
 
+@defun PendingFrame.level ()
+Return an integer, the stack frame level for this frame.
+@xref{Frames, ,Stack Frames}.
+@end defun
+
 @subheading Unwinder Output: UnwindInfo
 
 Use @code{PendingFrame.create_unwind_info} method described above to
@@ -4813,6 +4818,10 @@ 
 Stack}.
 @end defun
 
+@defun Frame.level ()
+Return an integer, the stack frame level for this frame.  @xref{Frames, ,Stack Frames}.
+@end defun
+
 @node Blocks In Python
 @subsubsection Accessing blocks from Python
 
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index c8eab5291ea..4f218c40367 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -577,6 +577,27 @@  frapy_select (PyObject *self, PyObject *args)
   Py_RETURN_NONE;
 }
 
+/* The stack frame level for this frame.  */
+
+static PyObject *
+frapy_level (PyObject *self, PyObject *args)
+{
+  struct frame_info *fi;
+
+  try
+    {
+      FRAPY_REQUIRE_VALID (self, fi);
+
+      return gdb_py_object_from_int (frame_relative_level (fi)).release ();
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  Py_RETURN_NONE;
+}
+
 /* Implementation of gdb.newest_frame () -> gdb.Frame.
    Returns the newest frame object.  */
 
@@ -748,6 +769,8 @@  Return the frame's symtab and line." },
 Return the value of the variable in this frame." },
   { "select", frapy_select, METH_NOARGS,
     "Select this frame as the user's current frame." },
+  { "level", frapy_level, METH_NOARGS,
+    "The stack level of this frame." },
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index d6e2f85dbc1..ff1a7e922a7 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -463,6 +463,23 @@  pending_framepy_architecture (PyObject *self, PyObject *args)
   return gdbarch_to_arch_object (pending_frame->gdbarch);
 }
 
+/* Implementation of PendingFrame.level (self) -> Integer.  */
+
+static PyObject *
+pending_framepy_level (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  if (pending_frame->frame_info == NULL)
+    {
+      PyErr_SetString (PyExc_ValueError,
+		       "Attempting to read stack level from stale PendingFrame");
+      return NULL;
+    }
+  int level = frame_relative_level (pending_frame->frame_info);
+  return gdb_py_object_from_int (level).release ();
+}
+
 /* frame_unwind.this_id method.  */
 
 static void
@@ -704,6 +721,8 @@  static PyMethodDef pending_frame_object_methods[] =
     pending_framepy_architecture, METH_NOARGS,
     "architecture () -> gdb.Architecture\n"
     "The architecture for this PendingFrame." },
+  { "level", pending_framepy_level, METH_NOARGS,
+    "The stack level of this frame." },
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp
index a6a5c0de726..05c7fb00dfd 100644
--- a/gdb/testsuite/gdb.python/py-frame.exp
+++ b/gdb/testsuite/gdb.python/py-frame.exp
@@ -70,6 +70,17 @@  gdb_test "up" ".*" ""
 
 gdb_py_test_silent_cmd "python f1 = gdb.selected_frame ()" "get second frame" 0
 gdb_py_test_silent_cmd "python f0 = f1.newer ()" "get first frame" 0
+gdb_py_test_silent_cmd "python f2 = f1.older ()" "get last frame" 0
+
+# Check the Frame.level method.
+gdb_test "python print ('bframe.level = %d' % bframe.level ())" \
+    "bframe\\.level = 0"
+gdb_test "python print ('f0.level = %d' % f0.level ())" \
+    "f0\\.level = 0"
+gdb_test "python print ('f1.level = %d' % f1.level ())" \
+    "f1\\.level = 1"
+gdb_test "python print ('f2.level = %d' % f2.level ())" \
+    "f2\\.level = 2"
 
 gdb_test "python print (f1 == gdb.newest_frame())" False \
     "selected frame -vs- newest frame"
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.c b/gdb/testsuite/gdb.python/py-pending-frame-level.c
new file mode 100644
index 00000000000..5e5495c1d71
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.c
@@ -0,0 +1,49 @@ 
+/* This test program 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/>.  */
+
+volatile int global_var;
+
+void __attribute__ ((noinline))
+f0 (void)
+{
+  ++global_var;		/* Break here.  */
+}
+
+void __attribute__ ((noinline))
+f1 (void)
+{
+  f0 ();
+}
+
+void __attribute__ ((noinline))
+f2 (void)
+{
+  f1 ();
+}
+
+void __attribute__ ((noinline))
+f3 (void)
+{
+  f2 ();
+}
+
+int
+main (void)
+{
+  f3 ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.exp b/gdb/testsuite/gdb.python/py-pending-frame-level.exp
new file mode 100644
index 00000000000..1aadcaeacae
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.exp
@@ -0,0 +1,65 @@ 
+# 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/>.
+
+# Test gdb.PendingFrame.level method.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+# Skip all tests 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]
+
+gdb_breakpoint [gdb_get_line_number "Break here"]
+gdb_continue_to_breakpoint "stop at test breakpoint"
+
+# An initial look at the stack to ensure it is correct.
+gdb_test_sequence "bt"  "Initial backtrace" {
+    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "
+    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "
+    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "
+    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "
+    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "
+}
+
+# Load the script containing the unwinder.
+gdb_test_no_output "source ${pyfile}"\
+    "import python scripts"
+
+# Now look at the stack again, we should see output from the Python
+# unwinder mixed in.
+gdb_test_sequence "bt"  "Backtrace with extra Python output" {
+    "Func f0, Level 0"
+    "Func f1, Level 1"
+    "\\r\\n#0 \[^\r\n\]* f0 \\(\\) at "
+    "\\r\\n#1 \[^\r\n\]* f1 \\(\\) at "
+    "Func f2, Level 2"
+    "\\r\\n#2 \[^\r\n\]* f2 \\(\\) at "
+    "Func f3, Level 3"
+    "\\r\\n#3 \[^\r\n\]* f3 \\(\\) at "
+    "Func main, Level 4"
+    "\\r\\n#4 \[^\r\n\]* main \\(\\) at "
+}
diff --git a/gdb/testsuite/gdb.python/py-pending-frame-level.py b/gdb/testsuite/gdb.python/py-pending-frame-level.py
new file mode 100644
index 00000000000..182edcdc0df
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pending-frame-level.py
@@ -0,0 +1,55 @@ 
+# 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
+
+
+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, "show level")
+
+    def __call__(self, pending_frame):
+        pc_desc = pending_frame.architecture().registers().find("pc")
+        pc = pending_frame.read_register(pc_desc)
+
+        block = gdb.block_for_pc(int(pc))
+        if block == None:
+            return None
+        func = block.function
+        if func == None:
+            return None
+
+        print("Func %s, Level %d" % (str(func), pending_frame.level()))
+
+        # This unwinder never claims any frames.
+        return None
+
+
+gdb.unwinder.register_unwinder(None, TestUnwinder(), True)