gdb/python: add PendingFrame.level and Frame.level methods
authorAndrew Burgess <andrew.burgess@embecosm.com>
Wed, 26 May 2021 21:01:59 +0000 (22:01 +0100)
committerAndrew Burgess <andrew.burgess@embecosm.com>
Mon, 21 Jun 2021 15:20:08 +0000 (16:20 +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:

* 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.

gdb/ChangeLog
gdb/NEWS
gdb/doc/ChangeLog
gdb/doc/python.texi
gdb/python/py-frame.c
gdb/python/py-unwind.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.python/py-frame.exp
gdb/testsuite/gdb.python/py-pending-frame-level.c [new file with mode: 0644]
gdb/testsuite/gdb.python/py-pending-frame-level.exp [new file with mode: 0644]
gdb/testsuite/gdb.python/py-pending-frame-level.py [new file with mode: 0644]

index 24aeaa416391f94ad5ac1aef19c3b3dd4f0713f1..0501a56bc5a85caf717b05d9fef7da35225ffd26 100644 (file)
@@ -1,3 +1,11 @@
+2021-06-21  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * 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.
+
 2021-06-21  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * python/py-inferior.c (infpy_get_connection_num): Call
index 56743fc9aeaadecc269e39b6346b51bd9b49dcf0..65699e11abec95cae1890bce88426d0258cf34cf 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -220,6 +220,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"
index 0fa355c9e3b28c9f65ffbde23b9fc208ee61566e..9595c21e043a5654966c590ae3646900fe623411 100644 (file)
@@ -1,3 +1,9 @@
+2021-06-21  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * python.texi (Unwinding Frames in Python): Mention
+       PendingFrame.level.
+       (Frames In Python): Mention Frame.level.
+
 2021-06-16  Felix Willgerodt  <felix.willgerodt@intel.com>
 
        * gdb.texinfo (Process Record and Replay): Stop mentioning lines
index ab934a8c0127c38ad1aca387d589c35eccd2a550..01a2e8b7974006a35f5aa434ef58d07ffaa1ddcd 100644 (file)
@@ -2605,6 +2605,11 @@ for this @code{gdb.PendingFrame}.  This represents the architecture of
 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 @@ Set this frame to be the selected frame.  @xref{Stack, ,Examining the
 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
 
index c8eab5291ea471199fc405a5ba50c713196ff0d3..ee57eb105769c0493354464b1aa1d436c09f42af 100644 (file)
@@ -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_longest (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 */
 };
 
index d6e2f85dbc1928ef3d605273adbd98129dd50c10..d3ef1911ab8204d74e180e889590706a10696e62 100644 (file)
@@ -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_longest (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 */
 };
 
index ddd8b1391d15c0b322687c9443b493c08ce826db..0f5c1527277aa6a731ee62353ce2b8b81ea30655 100644 (file)
@@ -1,3 +1,10 @@
+2021-06-21  Andrew Burgess  <andrew.burgess@embecosm.com>
+
+       * 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.
+
 2021-06-21  Andrew Burgess  <andrew.burgess@embecosm.com>
 
        * gdb.python/py-unwind-user-regs.c: New file.
index a6a5c0de726341f56349e436999a0ba8f06efd92..05c7fb00dfdbb96b27ffe9c1d42813d32dcabd92 100644 (file)
@@ -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 (file)
index 0000000..5e5495c
--- /dev/null
@@ -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 (file)
index 0000000..1aadcae
--- /dev/null
@@ -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 (file)
index 0000000..87b226c
--- /dev/null
@@ -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 is None:
+            return None
+        func = block.function
+        if func is 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)