[05/10] gdb/python: add some additional methods to gdb.PendingFrame

Message ID 5bd7327ee61fd1634235cd4ffddbfbc91d392e03.1678460067.git.aburgess@redhat.com
State New
Headers
Series Improvements & Cleanup For Python Unwinder API |

Commit Message

Andrew Burgess March 10, 2023, 2:55 p.m. UTC
  The gdb.Frame class has far more methods than gdb.PendingFrame.  Given
that a PendingFrame hasn't yet been claimed by an unwinder, there is a
limit to which methods we can add to it, but many of the methods that
the Frame class has, the PendingFrame class could also support.

In this commit I've added those methods to PendingFrame that I believe
are safe.

In terms of implementation: if I was starting from scratch then I
would implement many of these (or most of these) as attributes rather
than methods.  However, given both Frame and PendingFrame are just
different representation of a frame, I think there is value in keeping
the interface for the two classes the same.  For this reason
everything here is a method -- that's what the Frame class does.

The new methods I've added are:

  - gdb.PendingFrame.is_valid(): Return True if the pending frame
    object is valid.

  - gdb.PendingFrame.name(): Return the name for the frame's function,
    or None.

  - gdb.PendingFrame.pc(): Return the $pc register value for this
    frame.

  - gdb.PendingFrame.language(): Return a string containing the
    language for this frame, or None.

  - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line object
    for the current location within the pending frame, or None.

  - gdb.PendingFrame.block(): Return a gdb.Block for the current
    pending frame, or None.

  - gdb.PendingFrame.function(): Return a gdb.Symbol for the current
    pending frame, or None.

In every case I've just copied the implementation over from gdb.Frame
and cleaned the code slightly e.g. NULL to nullptr.  Additionally each
function required a small update to reflect the PendingFrame type, but
that's pretty minor.

There are tests for all the new methods.

For more extensive testing, I added the following code to the file
gdb/python/lib/command/unwinders.py:

  from gdb.unwinder import Unwinder

  class TestUnwinder(Unwinder):
      def __init__(self):
          super().__init__("XXX_TestUnwinder_XXX")

      def __call__(self,pending_frame):
          lang = pending_frame.language()
          try:
              block = pending_frame.block()
              assert isinstance(block, gdb.Block)
          except RuntimeError as rte:
              assert str(rte) == "Cannot locate block for frame."
          function = pending_frame.function()
          arch = pending_frame.architecture()
          assert arch is None or isinstance(arch, gdb.Architecture)
          name = pending_frame.name()
          assert name is None or isinstance(name, str)
          valid = pending_frame.is_valid()
          pc = pending_frame.pc()
          sal = pending_frame.find_sal()
          assert sal is None or isinstance(sal, gdb.Symtab_and_line)
          return None

  gdb.unwinder.register_unwinder(None, TestUnwinder())

This registers a global unwinder that calls each of the new
PendingFrame methods and checks the result is of an acceptable type.
The unwinder never claims any frames though, so shouldn't change how
GDB actually behaves.

I then ran the testsuite.  There was only a single regression, a test
that uses 'disable unwinder' and expects a single unwinder to be
disabled -- the extra unwinder is now disabled too, which changes the
test output.  So I'm reasonably confident that the new methods are not
going to crash GDB.
---
 gdb/NEWS                               |  20 +++
 gdb/doc/python.texi                    |  40 +++++
 gdb/python/py-unwind.c                 | 221 +++++++++++++++++++++++++
 gdb/testsuite/gdb.python/py-unwind.exp |  33 +++-
 gdb/testsuite/gdb.python/py-unwind.py  | 104 ++++++++++++
 5 files changed, 417 insertions(+), 1 deletion(-)
  

Comments

Eli Zaretskii March 10, 2023, 3:42 p.m. UTC | #1
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 10 Mar 2023 14:55:22 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
>  gdb/NEWS                               |  20 +++
>  gdb/doc/python.texi                    |  40 +++++
>  gdb/python/py-unwind.c                 | 221 +++++++++++++++++++++++++
>  gdb/testsuite/gdb.python/py-unwind.exp |  33 +++-
>  gdb/testsuite/gdb.python/py-unwind.py  | 104 ++++++++++++
>  5 files changed, 417 insertions(+), 1 deletion(-)

Thanks.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index ed0f86e79ec..0d9049ff134 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -110,6 +110,26 @@ show always-read-ctf
>       without the particular unwinder, depending on how 'enabled' was
>       changed.
>  
> +  ** New methods added to the gdb.PendingFrame class.  These methods
> +     have the same behaviour as the corresponding methods on
> +     gdb.Frame.  The new methods are:
> +
> +     - gdb.PendingFrame.name(): Return the name for the frame's
> +       function, or None.
> +     - gdb.PendingFrame.is_valid(): Return True if the pending frame
> +       object is valid.
> +     - gdb.PendingFrame.pc(): Return the $pc register value for this
> +       frame.
> +     - gdb.PendingFrame.language(): Return a string containing the
> +       language for this frame, or None.

"containing the language"?  I think this is better:

  Return the language of this frame, as a string, or None.

> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
> +       object for the current location within the pending frame, or
> +       None.
> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
> +       pending frame, or None.
> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
> +       current pending frame, or None.

Btw, why do you follow each method name with a "()"?  That looks like
a call with no arguments, which is not what you mean, right?

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Andrew Burgess March 14, 2023, 10:18 a.m. UTC | #2
Eli Zaretskii <eliz@gnu.org> writes:

>> Cc: Andrew Burgess <aburgess@redhat.com>
>> Date: Fri, 10 Mar 2023 14:55:22 +0000
>> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
>> 
>>  gdb/NEWS                               |  20 +++
>>  gdb/doc/python.texi                    |  40 +++++
>>  gdb/python/py-unwind.c                 | 221 +++++++++++++++++++++++++
>>  gdb/testsuite/gdb.python/py-unwind.exp |  33 +++-
>>  gdb/testsuite/gdb.python/py-unwind.py  | 104 ++++++++++++
>>  5 files changed, 417 insertions(+), 1 deletion(-)
>
> Thanks.
>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index ed0f86e79ec..0d9049ff134 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -110,6 +110,26 @@ show always-read-ctf
>>       without the particular unwinder, depending on how 'enabled' was
>>       changed.
>>  
>> +  ** New methods added to the gdb.PendingFrame class.  These methods
>> +     have the same behaviour as the corresponding methods on
>> +     gdb.Frame.  The new methods are:
>> +
>> +     - gdb.PendingFrame.name(): Return the name for the frame's
>> +       function, or None.
>> +     - gdb.PendingFrame.is_valid(): Return True if the pending frame
>> +       object is valid.
>> +     - gdb.PendingFrame.pc(): Return the $pc register value for this
>> +       frame.
>> +     - gdb.PendingFrame.language(): Return a string containing the
>> +       language for this frame, or None.
>
> "containing the language"?  I think this is better:
>
>   Return the language of this frame, as a string, or None.

Changed to use your wording.

>
>> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
>> +       object for the current location within the pending frame, or
>> +       None.
>> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
>> +       pending frame, or None.
>> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
>> +       current pending frame, or None.
>
> Btw, why do you follow each method name with a "()"?  That looks like
> a call with no arguments, which is not what you mean, right?

I did indeed mean a call with no arguments.  These would be used like
this:

  class TestUnwinder(Unwinder):
      def __init__(self):
          super().__init__("Unwinder_Name")
  
      def __call__(self, pending_frame):
          is_valid = pending_frame.is_valid()
          name = pending_frame.name()
          pc = pending_frame.pc()
          language = pending_frame.language()
          sal = pending_frame.find_sal()
          block = pending_frame.block()
          function = pending_frame.function()

Maybe you thought these would be better implemented as attributes?  If
you did then I 100% agree, but I think these have to be methods in order
to match the existing Frame API.  If we use the same API for
PendingFrame and Frame then a user can write code that will work on
either object type.

Does this address your concerns?  Or did I not understand?

Thanks,
Andrew
  
Eli Zaretskii March 14, 2023, 12:59 p.m. UTC | #3
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Tue, 14 Mar 2023 10:18:09 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
> >> +       object for the current location within the pending frame, or
> >> +       None.
> >> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
> >> +       pending frame, or None.
> >> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
> >> +       current pending frame, or None.
> >
> > Btw, why do you follow each method name with a "()"?  That looks like
> > a call with no arguments, which is not what you mean, right?
> 
> I did indeed mean a call with no arguments.  These would be used like
> this:
> 
>   class TestUnwinder(Unwinder):
>       def __init__(self):
>           super().__init__("Unwinder_Name")
>   
>       def __call__(self, pending_frame):
>           is_valid = pending_frame.is_valid()
>           name = pending_frame.name()
>           pc = pending_frame.pc()
>           language = pending_frame.language()
>           sal = pending_frame.find_sal()
>           block = pending_frame.block()
>           function = pending_frame.function()

That's not my point.  AFAIU, the text on which I commented documents
the methods and what each one of them does.  Then the "()" has no
place there, since you are naming the methods, not showing how to call
them.  Right?
  
Andrew Burgess March 16, 2023, 2:28 p.m. UTC | #4
Eli Zaretskii <eliz@gnu.org> writes:

>> From: Andrew Burgess <aburgess@redhat.com>
>> Cc: gdb-patches@sourceware.org
>> Date: Tue, 14 Mar 2023 10:18:09 +0000
>> 
>> Eli Zaretskii <eliz@gnu.org> writes:
>> 
>> >> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
>> >> +       object for the current location within the pending frame, or
>> >> +       None.
>> >> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
>> >> +       pending frame, or None.
>> >> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
>> >> +       current pending frame, or None.
>> >
>> > Btw, why do you follow each method name with a "()"?  That looks like
>> > a call with no arguments, which is not what you mean, right?
>> 
>> I did indeed mean a call with no arguments.  These would be used like
>> this:
>> 
>>   class TestUnwinder(Unwinder):
>>       def __init__(self):
>>           super().__init__("Unwinder_Name")
>>   
>>       def __call__(self, pending_frame):
>>           is_valid = pending_frame.is_valid()
>>           name = pending_frame.name()
>>           pc = pending_frame.pc()
>>           language = pending_frame.language()
>>           sal = pending_frame.find_sal()
>>           block = pending_frame.block()
>>           function = pending_frame.function()
>
> That's not my point.  AFAIU, the text on which I commented documents
> the methods and what each one of them does.  Then the "()" has no
> place there, since you are naming the methods, not showing how to call
> them.  Right?

I like to think we're documenting how to use the API, which includes how
to call them.  I do end up being a user of the Python API docs pretty
extensively, and when I do I'm looking for how do I call this method,
and what arguments should I be passing.

As far as I can tell the most common style in the docs is to include the
argument list, and I think it would be more confusing if we only
included the argument list for the case when there were some actual
arguments.  Why leave the user to infer the empty argument list when we
can just go ahead and say it.

Finally, this becomes more confusing I think with Python that supports
both methods (requires parenthesis to call) and attributes which don't
require parenthesis to access.  Thus under the current scheme we have:

  @defvar ClassName.Attribute
  Contains a value.
  @end defvar

  @defun ClassName.NoArgsMethod()
  Does stuff.
  @end defun

  @defun ClassName.TakesArgs(@var{arg1}, @var{arg2})
  Does other stuff.
  @end defun

If I've understood your correctly (sorry if I have not), then you are
suggesting dropping the '()' from the 'NoArgsMethod' case.  But I think
this would be inconsistent with the 'TakesArgs' method, and risks
confusion with the 'Attribute'.

Thanks,
Andrew
  
Eli Zaretskii March 16, 2023, 2:46 p.m. UTC | #5
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Thu, 16 Mar 2023 14:28:46 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> >> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
> >> >> +       object for the current location within the pending frame, or
> >> >> +       None.
> >> >> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
> >> >> +       pending frame, or None.
> >> >> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
> >> >> +       current pending frame, or None.
> >> >
> >> > Btw, why do you follow each method name with a "()"?  That looks like
> >> > a call with no arguments, which is not what you mean, right?
> >> 
> >> I did indeed mean a call with no arguments.  These would be used like
> >> this:
> >> 
> >>   class TestUnwinder(Unwinder):
> >>       def __init__(self):
> >>           super().__init__("Unwinder_Name")
> >>   
> >>       def __call__(self, pending_frame):
> >>           is_valid = pending_frame.is_valid()
> >>           name = pending_frame.name()
> >>           pc = pending_frame.pc()
> >>           language = pending_frame.language()
> >>           sal = pending_frame.find_sal()
> >>           block = pending_frame.block()
> >>           function = pending_frame.function()
> >
> > That's not my point.  AFAIU, the text on which I commented documents
> > the methods and what each one of them does.  Then the "()" has no
> > place there, since you are naming the methods, not showing how to call
> > them.  Right?
> 
> I like to think we're documenting how to use the API, which includes how
> to call them.

That's true in Texinfo, but my comment was about NEWS.

>   @defvar ClassName.Attribute
>   Contains a value.
>   @end defvar
> 
>   @defun ClassName.NoArgsMethod()
>   Does stuff.
>   @end defun
> 
>   @defun ClassName.TakesArgs(@var{arg1}, @var{arg2})
>   Does other stuff.
>   @end defun
> 
> If I've understood your correctly (sorry if I have not), then you are
> suggesting dropping the '()' from the 'NoArgsMethod' case.

Not in Texinfo, no.  In Texinfo, we do show the signature when we
document a function or a method.  But NEWS is mostly free-form text,
and in free-form text there's no reason to append a signature whenever
you mention the name of a function or a method.
  
Andrew Burgess March 16, 2023, 5:26 p.m. UTC | #6
Eli Zaretskii <eliz@gnu.org> writes:

>> From: Andrew Burgess <aburgess@redhat.com>
>> Cc: gdb-patches@sourceware.org
>> Date: Thu, 16 Mar 2023 14:28:46 +0000
>> 
>> Eli Zaretskii <eliz@gnu.org> writes:
>> 
>> >> >> +     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
>> >> >> +       object for the current location within the pending frame, or
>> >> >> +       None.
>> >> >> +     - gdb.PendingFrame.block(): Return a gdb.Block for the current
>> >> >> +       pending frame, or None.
>> >> >> +     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
>> >> >> +       current pending frame, or None.
>> >> >
>> >> > Btw, why do you follow each method name with a "()"?  That looks like
>> >> > a call with no arguments, which is not what you mean, right?
>> >> 
>> >> I did indeed mean a call with no arguments.  These would be used like
>> >> this:
>> >> 
>> >>   class TestUnwinder(Unwinder):
>> >>       def __init__(self):
>> >>           super().__init__("Unwinder_Name")
>> >>   
>> >>       def __call__(self, pending_frame):
>> >>           is_valid = pending_frame.is_valid()
>> >>           name = pending_frame.name()
>> >>           pc = pending_frame.pc()
>> >>           language = pending_frame.language()
>> >>           sal = pending_frame.find_sal()
>> >>           block = pending_frame.block()
>> >>           function = pending_frame.function()
>> >
>> > That's not my point.  AFAIU, the text on which I commented documents
>> > the methods and what each one of them does.  Then the "()" has no
>> > place there, since you are naming the methods, not showing how to call
>> > them.  Right?
>> 
>> I like to think we're documenting how to use the API, which includes how
>> to call them.
>
> That's true in Texinfo, but my comment was about NEWS.

OK, now I just feel like an idiot :/

Sorry for wasting your time.

The updated NEW entry is:

  ** New methods added to the gdb.PendingFrame class.  These methods
     have the same behaviour as the corresponding methods on
     gdb.Frame.  The new methods are:

     - gdb.PendingFrame.name: Return the name for the frame's
       function, or None.
     - gdb.PendingFrame.is_valid: Return True if the pending frame
       object is valid.
     - gdb.PendingFrame.pc: Return the $pc register value for this
       frame.
     - gdb.PendingFrame.language: Return a string containing the
       language for this frame, or None.
     - gdb.PendingFrame.find_sal: Return a gdb.Symtab_and_line
       object for the current location within the pending frame, or
       None.
     - gdb.PendingFrame.block: Return a gdb.Block for the current
       pending frame, or None.
     - gdb.PendingFrame.function: Return a gdb.Symbol for the
       current pending frame, or None.

The complete patch with this NEWS entry change is below for your
consideration.

Thanks for your patience,

Andrew

---

commit 4dcad36a4a58e9c19189cb966d68d051483c1929
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Wed Mar 8 16:11:45 2023 +0000

    gdb/python: add some additional methods to gdb.PendingFrame
    
    The gdb.Frame class has far more methods than gdb.PendingFrame.  Given
    that a PendingFrame hasn't yet been claimed by an unwinder, there is a
    limit to which methods we can add to it, but many of the methods that
    the Frame class has, the PendingFrame class could also support.
    
    In this commit I've added those methods to PendingFrame that I believe
    are safe.
    
    In terms of implementation: if I was starting from scratch then I
    would implement many of these (or most of these) as attributes rather
    than methods.  However, given both Frame and PendingFrame are just
    different representation of a frame, I think there is value in keeping
    the interface for the two classes the same.  For this reason
    everything here is a method -- that's what the Frame class does.
    
    The new methods I've added are:
    
      - gdb.PendingFrame.is_valid: Return True if the pending frame
        object is valid.
    
      - gdb.PendingFrame.name: Return the name for the frame's function,
        or None.
    
      - gdb.PendingFrame.pc: Return the $pc register value for this
        frame.
    
      - gdb.PendingFrame.language: Return a string containing the
        language for this frame, or None.
    
      - gdb.PendingFrame.find_sal: Return a gdb.Symtab_and_line object
        for the current location within the pending frame, or None.
    
      - gdb.PendingFrame.block: Return a gdb.Block for the current
        pending frame, or None.
    
      - gdb.PendingFrame.function: Return a gdb.Symbol for the current
        pending frame, or None.
    
    In every case I've just copied the implementation over from gdb.Frame
    and cleaned the code slightly e.g. NULL to nullptr.  Additionally each
    function required a small update to reflect the PendingFrame type, but
    that's pretty minor.
    
    There are tests for all the new methods.
    
    For more extensive testing, I added the following code to the file
    gdb/python/lib/command/unwinders.py:
    
      from gdb.unwinder import Unwinder
    
      class TestUnwinder(Unwinder):
          def __init__(self):
              super().__init__("XXX_TestUnwinder_XXX")
    
          def __call__(self,pending_frame):
              lang = pending_frame.language()
              try:
                  block = pending_frame.block()
                  assert isinstance(block, gdb.Block)
              except RuntimeError as rte:
                  assert str(rte) == "Cannot locate block for frame."
              function = pending_frame.function()
              arch = pending_frame.architecture()
              assert arch is None or isinstance(arch, gdb.Architecture)
              name = pending_frame.name()
              assert name is None or isinstance(name, str)
              valid = pending_frame.is_valid()
              pc = pending_frame.pc()
              sal = pending_frame.find_sal()
              assert sal is None or isinstance(sal, gdb.Symtab_and_line)
              return None
    
      gdb.unwinder.register_unwinder(None, TestUnwinder())
    
    This registers a global unwinder that calls each of the new
    PendingFrame methods and checks the result is of an acceptable type.
    The unwinder never claims any frames though, so shouldn't change how
    GDB actually behaves.
    
    I then ran the testsuite.  There was only a single regression, a test
    that uses 'disable unwinder' and expects a single unwinder to be
    disabled -- the extra unwinder is now disabled too, which changes the
    test output.  So I'm reasonably confident that the new methods are not
    going to crash GDB.
    
    Reviewed-By: Eli Zaretskii <eliz@gnu.org>

diff --git a/gdb/NEWS b/gdb/NEWS
index 99e3d0bf222..da66fb1da8d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -118,6 +118,26 @@ show always-read-ctf
      without the particular unwinder, depending on how 'enabled' was
      changed.
 
+  ** New methods added to the gdb.PendingFrame class.  These methods
+     have the same behaviour as the corresponding methods on
+     gdb.Frame.  The new methods are:
+
+     - gdb.PendingFrame.name: Return the name for the frame's
+       function, or None.
+     - gdb.PendingFrame.is_valid: Return True if the pending frame
+       object is valid.
+     - gdb.PendingFrame.pc: Return the $pc register value for this
+       frame.
+     - gdb.PendingFrame.language: Return a string containing the
+       language for this frame, or None.
+     - gdb.PendingFrame.find_sal: Return a gdb.Symtab_and_line
+       object for the current location within the pending frame, or
+       None.
+     - gdb.PendingFrame.block: Return a gdb.Block for the current
+       pending frame, or None.
+     - gdb.PendingFrame.function: Return a gdb.Symbol for the
+       current pending frame, or None.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 8af2d1faea3..2e5d79d4074 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2830,6 +2830,46 @@
 @xref{Frames, ,Stack Frames}.
 @end defun
 
+@defun PendingFrame.name ()
+Returns the function name of this pending frame, or @code{None} if it
+can't be obtained.
+@end defun
+
+@defun PendingFrame.is_valid ()
+Returns true if the @code{gdb.PendingFrame} object is valid, false if
+not.  A pending frame object becomes invalid when the call to the
+unwinder, for which the pending frame was created, returns.
+
+All @code{gdb.PendingFrame} methods, except this one, will raise an
+exception if the pending frame object is invalid at the time the
+method is called.
+@end defun
+
+@defun PendingFrame.pc ()
+Returns the pending frame's resume address.
+@end defun
+
+@defun PendingFrame.block ()
+Return the pending frame's code block (@pxref{Blocks In Python}).  If
+the frame does not have a block -- for example, if there is no
+debugging information for the code in question -- then this will raise
+a @code{RuntimeError} exception.
+@end defun
+
+@defun PendingFrame.function ()
+Return the symbol for the function corresponding to this pending frame.
+@xref{Symbols In Python}.
+@end defun
+
+@defun PendingFrame.find_sal ()
+Return the pending frame's symtab and line object (@pxref{Symbol
+Tables In Python}).
+@end defun
+
+@defun PendingFrame.language ()
+Return the language of this frame, as a string, or None.
+@end defun
+
 @subheading Unwinder Output: UnwindInfo
 
 Use @code{PendingFrame.create_unwind_info} method described above to
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index 12b14616363..5ed3ff37e64 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -28,6 +28,10 @@
 #include "regcache.h"
 #include "valprint.h"
 #include "user-regs.h"
+#include "stack.h"
+#include "charset.h"
+#include "block.h"
+
 
 /* Debugging of Python unwinders.  */
 
@@ -412,6 +416,200 @@ pending_framepy_read_register (PyObject *self, PyObject *args)
   return result;
 }
 
+/* Implement PendingFrame.is_valid().  Return True if this pending frame
+   object is still valid.  */
+
+static PyObject *
+pending_framepy_is_valid (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  if (pending_frame->frame_info == nullptr)
+    Py_RETURN_FALSE;
+
+  Py_RETURN_TRUE;
+}
+
+/* Implement PendingFrame.name().  Return a string that is the name of the
+   function for this frame, or None if the name can't be found.  */
+
+static PyObject *
+pending_framepy_name (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  gdb::unique_xmalloc_ptr<char> name;
+
+  try
+    {
+      enum language lang;
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      name = find_frame_funname (frame, &lang, nullptr);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  if (name != nullptr)
+    return PyUnicode_Decode (name.get (), strlen (name.get ()),
+			     host_charset (), nullptr);
+
+  Py_RETURN_NONE;
+}
+
+/* Implement gdb.PendingFrame.pc().  Returns an integer containing the
+   frame's current $pc value.  */
+
+static PyObject *
+pending_framepy_pc (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  CORE_ADDR pc = 0;
+
+  try
+    {
+      pc = get_frame_pc (pending_frame->frame_info);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  return gdb_py_object_from_ulongest (pc).release ();
+}
+
+/* Implement gdb.PendingFrame.language().  Return the name of the language
+   for this frame.  */
+
+static PyObject *
+pending_framepy_language (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  try
+    {
+      frame_info_ptr fi = pending_frame->frame_info;
+
+      enum language lang = get_frame_language (fi);
+      const language_defn *lang_def = language_def (lang);
+
+      return host_string_to_python_string (lang_def->name ()).release ();
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  Py_RETURN_NONE;
+}
+
+/* Implement PendingFrame.find_sal().  Return the PendingFrame's symtab and
+   line.  */
+
+static PyObject *
+pending_framepy_find_sal (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  PyObject *sal_obj = nullptr;
+
+  try
+    {
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      symtab_and_line sal = find_frame_sal (frame);
+      sal_obj = symtab_and_line_to_sal_object (sal);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  return sal_obj;
+}
+
+/* Implement PendingFrame.block().  Return a gdb.Block for the pending
+   frame's code, or raise  RuntimeError if the block can't be found.  */
+
+static PyObject *
+pending_framepy_block (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  frame_info_ptr frame = pending_frame->frame_info;
+  const struct block *block = nullptr, *fn_block;
+
+  try
+    {
+      block = get_frame_block (frame, nullptr);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  for (fn_block = block;
+       fn_block != nullptr && fn_block->function () == nullptr;
+       fn_block = fn_block->superblock ())
+    ;
+
+  if (block == nullptr
+      || fn_block == nullptr
+      || fn_block->function () == nullptr)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot locate block for frame."));
+      return nullptr;
+    }
+
+  return block_to_block_object (block, fn_block->function ()->objfile ());
+}
+
+/* Implement gdb.PendingFrame.function().  Return a gdb.Symbol
+   representing the function of this frame, or None if no suitable symbol
+   can be found.  */
+
+static PyObject *
+pending_framepy_function (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  struct symbol *sym = nullptr;
+
+  try
+    {
+      enum language funlang;
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      gdb::unique_xmalloc_ptr<char> funname
+	= find_frame_funname (frame, &funlang, &sym);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  if (sym != nullptr)
+    return symbol_to_symbol_object (sym);
+
+  Py_RETURN_NONE;
+}
+
 /* Implementation of
    PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo.  */
 
@@ -737,6 +935,29 @@ static PyMethodDef pending_frame_object_methods[] =
     pending_framepy_architecture, METH_NOARGS,
     "architecture () -> gdb.Architecture\n"
     "The architecture for this PendingFrame." },
+  { "name",
+    pending_framepy_name, METH_NOARGS,
+    "name() -> String.\n\
+Return the function name of the frame, or None if it can't be determined." },
+  { "is_valid",
+    pending_framepy_is_valid, METH_NOARGS,
+    "is_valid () -> Boolean.\n\
+Return true if this PendingFrame is valid, false if not." },
+  { "pc",
+    pending_framepy_pc, METH_NOARGS,
+    "pc () -> Long.\n\
+Return the frame's resume address." },
+  { "language", pending_framepy_language, METH_NOARGS,
+    "The language of this frame." },
+  { "find_sal", pending_framepy_find_sal, METH_NOARGS,
+    "find_sal () -> gdb.Symtab_and_line.\n\
+Return the frame's symtab and line." },
+  { "block", pending_framepy_block, METH_NOARGS,
+    "block () -> gdb.Block.\n\
+Return the frame's code block." },
+  { "function", pending_framepy_function, METH_NOARGS,
+    "function () -> gdb.Symbol.\n\
+Returns the symbol for the function corresponding to this frame." },
   { "level", pending_framepy_level, METH_NOARGS,
     "The stack level of this frame." },
   {NULL}  /* Sentinel */
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
index 3e214ee0f45..f670da5a7cd 100644
--- a/gdb/testsuite/gdb.python/py-unwind.exp
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -149,15 +149,46 @@ gdb_test_no_output "python obj = simple_unwinder(\"simple\")"
 gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj)"
 check_for_broken_backtrace "backtrace to capture a PendingFrame object"
 
+# Check the captured PendingFrame is not valid.
+gdb_test "python print(captured_pending_frame.is_valid())" "False"
+
 # Call methods on the captured gdb.PendingFrame and check we see the
 # expected error.
 gdb_test_no_output "python pf = captured_pending_frame"
 foreach cmd {"pf.read_register(\"pc\")" \
 		 "pf.create_unwind_info(None)" \
 		 "pf.architecture()" \
-		 "pf.level()"} {
+		 "pf.level()" \
+		 "pf.name()" \
+		 "pf.pc()" \
+		 "pf.language()" \
+		 "pf.find_sal()" \
+		 "pf.block()" \
+		 "pf.function()" } {
     gdb_test "python $cmd" \
 	[multi_line \
 	     "ValueError: gdb\\.PendingFrame is invalid\\." \
 	     "Error while executing Python code\\."]
 }
+
+# Turn on the useful unwinder so we have the full backtrace again, and
+# disable the simple unwinder -- because we can!
+gdb_test "enable unwinder global \"test unwinder\"" \
+    "1 unwinder enabled" \
+    "re-enable 'test unwinder' so we can check PendingFrame methods"
+gdb_test "disable unwinder global \"simple\"" \
+    "1 unwinder disabled"
+check_for_fixed_backtrace \
+    "check backtrace before testing PendingFrame methods"
+
+# Gather information about every frame.
+gdb_test_no_output "python capture_all_frame_information()"
+gdb_test_no_output "python gdb.newest_frame().select()"
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
+gdb_test_no_output "python obj = validating_unwinder()"
+gdb_test_no_output "python gdb.unwinder.register_unwinder(pspace, obj)"
+
+check_for_fixed_backtrace \
+    "check backtrace to validate all information"
+
+gdb_test_no_output "python check_all_frame_information_matched()"
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
index b30e843e7e5..8e3c1f398bc 100644
--- a/gdb/testsuite/gdb.python/py-unwind.py
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -144,9 +144,113 @@ class simple_unwinder(Unwinder):
     def __call__(self, pending_frame):
         global captured_pending_frame
 
+        assert pending_frame.is_valid()
+
         if captured_pending_frame is None:
             captured_pending_frame = pending_frame
         return None
 
 
+# Return a dictionary of information about FRAME.
+def capture_frame_information(frame):
+    name = frame.name()
+    level = frame.level()
+    language = frame.language()
+    function = frame.function()
+    architecture = frame.architecture()
+    pc = frame.pc()
+    sal = frame.find_sal()
+    try:
+        block = frame.block()
+        assert isinstance(block, gdb.Block)
+    except RuntimeError as rte:
+        assert str(rte) == "Cannot locate block for frame."
+        block = "RuntimeError: " + str(rte)
+
+    return {
+        "name": name,
+        "level": level,
+        "language": language,
+        "function": function,
+        "architecture": architecture,
+        "pc": pc,
+        "sal": sal,
+        "block": block,
+    }
+
+
+# List of information about each frame.  The index into this list is
+# the frame level.  This is populated by
+# capture_all_frame_information.
+all_frame_information = []
+
+# Fill in the global ALL_FRAME_INFORMATION list.
+def capture_all_frame_information():
+    global all_frame_information
+
+    all_frame_information = []
+
+    gdb.newest_frame().select()
+    frame = gdb.selected_frame()
+    count = 0
+
+    while frame is not None:
+        frame.select()
+        info = capture_frame_information(frame)
+        level = info["level"]
+        info["matched"] = False
+
+        while len(all_frame_information) <= level:
+            all_frame_information.append(None)
+
+        assert all_frame_information[level] is None
+        all_frame_information[level] = info
+
+        if frame.name == "main" or count > 10:
+            break
+
+        count += 1
+        frame = frame.older()
+
+
+# Assert that every entry in the global ALL_FRAME_INFORMATION list was
+# matched by the validating_unwinder.
+def check_all_frame_information_matched():
+    global all_frame_information
+    for entry in all_frame_information:
+        assert entry["matched"]
+
+
+# An unwinder that doesn't match any frames.  What it does do is
+# lookup information from the PendingFrame object and compare it
+# against information stored in the global ALL_FRAME_INFORMATION list.
+class validating_unwinder(Unwinder):
+    def __init__(self):
+        super().__init__("validating_unwinder")
+
+    def __call__(self, pending_frame):
+        info = capture_frame_information(pending_frame)
+        level = info["level"]
+
+        global all_frame_information
+        old_info = all_frame_information[level]
+
+        assert old_info is not None
+        assert not old_info["matched"]
+
+        for key, value in info.items():
+            assert key in old_info, f"{key} not in old_info"
+            assert type(value) == type(old_info[key])
+            if isinstance(value, gdb.Block):
+                assert value.start == old_info[key].start
+                assert value.end == old_info[key].end
+                assert value.is_static == old_info[key].is_static
+                assert value.is_global == old_info[key].is_global
+            else:
+                assert str(value) == str(old_info[key])
+
+        old_info["matched"] = True
+        return None
+
+
 print("Python script imported")
  
Eli Zaretskii March 16, 2023, 7:54 p.m. UTC | #7
> From: Andrew Burgess <aburgess@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Thu, 16 Mar 2023 17:26:47 +0000
> 
> The updated NEW entry is:
> 
>   ** New methods added to the gdb.PendingFrame class.  These methods
>      have the same behaviour as the corresponding methods on
>      gdb.Frame.  The new methods are:
> 
>      - gdb.PendingFrame.name: Return the name for the frame's
>        function, or None.
>      - gdb.PendingFrame.is_valid: Return True if the pending frame
>        object is valid.
>      - gdb.PendingFrame.pc: Return the $pc register value for this
>        frame.
>      - gdb.PendingFrame.language: Return a string containing the
>        language for this frame, or None.
>      - gdb.PendingFrame.find_sal: Return a gdb.Symtab_and_line
>        object for the current location within the pending frame, or
>        None.
>      - gdb.PendingFrame.block: Return a gdb.Block for the current
>        pending frame, or None.
>      - gdb.PendingFrame.function: Return a gdb.Symbol for the
>        current pending frame, or None.
> 
> The complete patch with this NEWS entry change is below for your
> consideration.

LGTM, thanks.
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index ed0f86e79ec..0d9049ff134 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -110,6 +110,26 @@  show always-read-ctf
      without the particular unwinder, depending on how 'enabled' was
      changed.
 
+  ** New methods added to the gdb.PendingFrame class.  These methods
+     have the same behaviour as the corresponding methods on
+     gdb.Frame.  The new methods are:
+
+     - gdb.PendingFrame.name(): Return the name for the frame's
+       function, or None.
+     - gdb.PendingFrame.is_valid(): Return True if the pending frame
+       object is valid.
+     - gdb.PendingFrame.pc(): Return the $pc register value for this
+       frame.
+     - gdb.PendingFrame.language(): Return a string containing the
+       language for this frame, or None.
+     - gdb.PendingFrame.find_sal(): Return a gdb.Symtab_and_line
+       object for the current location within the pending frame, or
+       None.
+     - gdb.PendingFrame.block(): Return a gdb.Block for the current
+       pending frame, or None.
+     - gdb.PendingFrame.function(): Return a gdb.Symbol for the
+       current pending frame, or None.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index c36130210ba..1c4239841af 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -2830,6 +2830,46 @@ 
 @xref{Frames, ,Stack Frames}.
 @end defun
 
+@defun PendingFrame.name ()
+Returns the function name of this pending frame, or @code{None} if it
+can't be obtained.
+@end defun
+
+@defun PendingFrame.is_valid ()
+Returns true if the @code{gdb.PendingFrame} object is valid, false if
+not.  A pending frame object becomes invalid when the call to the
+unwinder, for which the pending frame was created, returns.
+
+All @code{gdb.PendingFrame} methods, except this one, will raise an
+exception if the pending frame object is invalid at the time the
+method is called.
+@end defun
+
+@defun PendingFrame.pc ()
+Returns the pending frame's resume address.
+@end defun
+
+@defun PendingFrame.block ()
+Return the pending frame's code block (@pxref{Blocks In Python}).  If
+the frame does not have a block -- for example, if there is no
+debugging information for the code in question -- then this will raise
+a @code{RuntimeError} exception.
+@end defun
+
+@defun PendingFrame.function ()
+Return the symbol for the function corresponding to this pending frame.
+@xref{Symbols In Python}.
+@end defun
+
+@defun PendingFrame.find_sal ()
+Return the pending frame's symtab and line object (@pxref{Symbol
+Tables In Python}).
+@end defun
+
+@defun PendingFrame.language ()
+Return a string, the source language for this pending frame.
+@end defun
+
 @subheading Unwinder Output: UnwindInfo
 
 Use @code{PendingFrame.create_unwind_info} method described above to
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index 12b14616363..5ed3ff37e64 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -28,6 +28,10 @@ 
 #include "regcache.h"
 #include "valprint.h"
 #include "user-regs.h"
+#include "stack.h"
+#include "charset.h"
+#include "block.h"
+
 
 /* Debugging of Python unwinders.  */
 
@@ -412,6 +416,200 @@  pending_framepy_read_register (PyObject *self, PyObject *args)
   return result;
 }
 
+/* Implement PendingFrame.is_valid().  Return True if this pending frame
+   object is still valid.  */
+
+static PyObject *
+pending_framepy_is_valid (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  if (pending_frame->frame_info == nullptr)
+    Py_RETURN_FALSE;
+
+  Py_RETURN_TRUE;
+}
+
+/* Implement PendingFrame.name().  Return a string that is the name of the
+   function for this frame, or None if the name can't be found.  */
+
+static PyObject *
+pending_framepy_name (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  gdb::unique_xmalloc_ptr<char> name;
+
+  try
+    {
+      enum language lang;
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      name = find_frame_funname (frame, &lang, nullptr);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  if (name != nullptr)
+    return PyUnicode_Decode (name.get (), strlen (name.get ()),
+			     host_charset (), nullptr);
+
+  Py_RETURN_NONE;
+}
+
+/* Implement gdb.PendingFrame.pc().  Returns an integer containing the
+   frame's current $pc value.  */
+
+static PyObject *
+pending_framepy_pc (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  CORE_ADDR pc = 0;
+
+  try
+    {
+      pc = get_frame_pc (pending_frame->frame_info);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  return gdb_py_object_from_ulongest (pc).release ();
+}
+
+/* Implement gdb.PendingFrame.language().  Return the name of the language
+   for this frame.  */
+
+static PyObject *
+pending_framepy_language (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  try
+    {
+      frame_info_ptr fi = pending_frame->frame_info;
+
+      enum language lang = get_frame_language (fi);
+      const language_defn *lang_def = language_def (lang);
+
+      return host_string_to_python_string (lang_def->name ()).release ();
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  Py_RETURN_NONE;
+}
+
+/* Implement PendingFrame.find_sal().  Return the PendingFrame's symtab and
+   line.  */
+
+static PyObject *
+pending_framepy_find_sal (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  PyObject *sal_obj = nullptr;
+
+  try
+    {
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      symtab_and_line sal = find_frame_sal (frame);
+      sal_obj = symtab_and_line_to_sal_object (sal);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  return sal_obj;
+}
+
+/* Implement PendingFrame.block().  Return a gdb.Block for the pending
+   frame's code, or raise  RuntimeError if the block can't be found.  */
+
+static PyObject *
+pending_framepy_block (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  frame_info_ptr frame = pending_frame->frame_info;
+  const struct block *block = nullptr, *fn_block;
+
+  try
+    {
+      block = get_frame_block (frame, nullptr);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  for (fn_block = block;
+       fn_block != nullptr && fn_block->function () == nullptr;
+       fn_block = fn_block->superblock ())
+    ;
+
+  if (block == nullptr
+      || fn_block == nullptr
+      || fn_block->function () == nullptr)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot locate block for frame."));
+      return nullptr;
+    }
+
+  return block_to_block_object (block, fn_block->function ()->objfile ());
+}
+
+/* Implement gdb.PendingFrame.function().  Return a gdb.Symbol
+   representing the function of this frame, or None if no suitable symbol
+   can be found.  */
+
+static PyObject *
+pending_framepy_function (PyObject *self, PyObject *args)
+{
+  pending_frame_object *pending_frame = (pending_frame_object *) self;
+
+  PENDING_FRAMEPY_REQUIRE_VALID (pending_frame);
+
+  struct symbol *sym = nullptr;
+
+  try
+    {
+      enum language funlang;
+      frame_info_ptr frame = pending_frame->frame_info;
+
+      gdb::unique_xmalloc_ptr<char> funname
+	= find_frame_funname (frame, &funlang, &sym);
+    }
+  catch (const gdb_exception &except)
+    {
+      GDB_PY_HANDLE_EXCEPTION (except);
+    }
+
+  if (sym != nullptr)
+    return symbol_to_symbol_object (sym);
+
+  Py_RETURN_NONE;
+}
+
 /* Implementation of
    PendingFrame.create_unwind_info (self, frameId) -> UnwindInfo.  */
 
@@ -737,6 +935,29 @@  static PyMethodDef pending_frame_object_methods[] =
     pending_framepy_architecture, METH_NOARGS,
     "architecture () -> gdb.Architecture\n"
     "The architecture for this PendingFrame." },
+  { "name",
+    pending_framepy_name, METH_NOARGS,
+    "name() -> String.\n\
+Return the function name of the frame, or None if it can't be determined." },
+  { "is_valid",
+    pending_framepy_is_valid, METH_NOARGS,
+    "is_valid () -> Boolean.\n\
+Return true if this PendingFrame is valid, false if not." },
+  { "pc",
+    pending_framepy_pc, METH_NOARGS,
+    "pc () -> Long.\n\
+Return the frame's resume address." },
+  { "language", pending_framepy_language, METH_NOARGS,
+    "The language of this frame." },
+  { "find_sal", pending_framepy_find_sal, METH_NOARGS,
+    "find_sal () -> gdb.Symtab_and_line.\n\
+Return the frame's symtab and line." },
+  { "block", pending_framepy_block, METH_NOARGS,
+    "block () -> gdb.Block.\n\
+Return the frame's code block." },
+  { "function", pending_framepy_function, METH_NOARGS,
+    "function () -> gdb.Symbol.\n\
+Returns the symbol for the function corresponding to this frame." },
   { "level", pending_framepy_level, METH_NOARGS,
     "The stack level of this frame." },
   {NULL}  /* Sentinel */
diff --git a/gdb/testsuite/gdb.python/py-unwind.exp b/gdb/testsuite/gdb.python/py-unwind.exp
index 3e214ee0f45..f670da5a7cd 100644
--- a/gdb/testsuite/gdb.python/py-unwind.exp
+++ b/gdb/testsuite/gdb.python/py-unwind.exp
@@ -149,15 +149,46 @@  gdb_test_no_output "python obj = simple_unwinder(\"simple\")"
 gdb_test_no_output "python gdb.unwinder.register_unwinder(None, obj)"
 check_for_broken_backtrace "backtrace to capture a PendingFrame object"
 
+# Check the captured PendingFrame is not valid.
+gdb_test "python print(captured_pending_frame.is_valid())" "False"
+
 # Call methods on the captured gdb.PendingFrame and check we see the
 # expected error.
 gdb_test_no_output "python pf = captured_pending_frame"
 foreach cmd {"pf.read_register(\"pc\")" \
 		 "pf.create_unwind_info(None)" \
 		 "pf.architecture()" \
-		 "pf.level()"} {
+		 "pf.level()" \
+		 "pf.name()" \
+		 "pf.pc()" \
+		 "pf.language()" \
+		 "pf.find_sal()" \
+		 "pf.block()" \
+		 "pf.function()" } {
     gdb_test "python $cmd" \
 	[multi_line \
 	     "ValueError: gdb\\.PendingFrame is invalid\\." \
 	     "Error while executing Python code\\."]
 }
+
+# Turn on the useful unwinder so we have the full backtrace again, and
+# disable the simple unwinder -- because we can!
+gdb_test "enable unwinder global \"test unwinder\"" \
+    "1 unwinder enabled" \
+    "re-enable 'test unwinder' so we can check PendingFrame methods"
+gdb_test "disable unwinder global \"simple\"" \
+    "1 unwinder disabled"
+check_for_fixed_backtrace \
+    "check backtrace before testing PendingFrame methods"
+
+# Gather information about every frame.
+gdb_test_no_output "python capture_all_frame_information()"
+gdb_test_no_output "python gdb.newest_frame().select()"
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
+gdb_test_no_output "python obj = validating_unwinder()"
+gdb_test_no_output "python gdb.unwinder.register_unwinder(pspace, obj)"
+
+check_for_fixed_backtrace \
+    "check backtrace to validate all information"
+
+gdb_test_no_output "python check_all_frame_information_matched()"
diff --git a/gdb/testsuite/gdb.python/py-unwind.py b/gdb/testsuite/gdb.python/py-unwind.py
index b30e843e7e5..8e3c1f398bc 100644
--- a/gdb/testsuite/gdb.python/py-unwind.py
+++ b/gdb/testsuite/gdb.python/py-unwind.py
@@ -144,9 +144,113 @@  class simple_unwinder(Unwinder):
     def __call__(self, pending_frame):
         global captured_pending_frame
 
+        assert pending_frame.is_valid()
+
         if captured_pending_frame is None:
             captured_pending_frame = pending_frame
         return None
 
 
+# Return a dictionary of information about FRAME.
+def capture_frame_information(frame):
+    name = frame.name()
+    level = frame.level()
+    language = frame.language()
+    function = frame.function()
+    architecture = frame.architecture()
+    pc = frame.pc()
+    sal = frame.find_sal()
+    try:
+        block = frame.block()
+        assert isinstance(block, gdb.Block)
+    except RuntimeError as rte:
+        assert str(rte) == "Cannot locate block for frame."
+        block = "RuntimeError: " + str(rte)
+
+    return {
+        "name": name,
+        "level": level,
+        "language": language,
+        "function": function,
+        "architecture": architecture,
+        "pc": pc,
+        "sal": sal,
+        "block": block,
+    }
+
+
+# List of information about each frame.  The index into this list is
+# the frame level.  This is populated by
+# capture_all_frame_information.
+all_frame_information = []
+
+# Fill in the global ALL_FRAME_INFORMATION list.
+def capture_all_frame_information():
+    global all_frame_information
+
+    all_frame_information = []
+
+    gdb.newest_frame().select()
+    frame = gdb.selected_frame()
+    count = 0
+
+    while frame is not None:
+        frame.select()
+        info = capture_frame_information(frame)
+        level = info["level"]
+        info["matched"] = False
+
+        while len(all_frame_information) <= level:
+            all_frame_information.append(None)
+
+        assert all_frame_information[level] is None
+        all_frame_information[level] = info
+
+        if frame.name == "main" or count > 10:
+            break
+
+        count += 1
+        frame = frame.older()
+
+
+# Assert that every entry in the global ALL_FRAME_INFORMATION list was
+# matched by the validating_unwinder.
+def check_all_frame_information_matched():
+    global all_frame_information
+    for entry in all_frame_information:
+        assert entry["matched"]
+
+
+# An unwinder that doesn't match any frames.  What it does do is
+# lookup information from the PendingFrame object and compare it
+# against information stored in the global ALL_FRAME_INFORMATION list.
+class validating_unwinder(Unwinder):
+    def __init__(self):
+        super().__init__("validating_unwinder")
+
+    def __call__(self, pending_frame):
+        info = capture_frame_information(pending_frame)
+        level = info["level"]
+
+        global all_frame_information
+        old_info = all_frame_information[level]
+
+        assert old_info is not None
+        assert not old_info["matched"]
+
+        for key, value in info.items():
+            assert key in old_info, f"{key} not in old_info"
+            assert type(value) == type(old_info[key])
+            if isinstance(value, gdb.Block):
+                assert value.start == old_info[key].start
+                assert value.end == old_info[key].end
+                assert value.is_static == old_info[key].is_static
+                assert value.is_global == old_info[key].is_global
+            else:
+                assert str(value) == str(old_info[key])
+
+        old_info["matched"] = True
+        return None
+
+
 print("Python script imported")