[v8,05/10] python: Introduce gdb.RecordAuxiliary class.

Message ID 20230321154626.448816-6-felix.willgerodt@intel.com
State New
Headers
Series Extensions for PTWRITE |

Commit Message

Willgerodt, Felix March 21, 2023, 3:46 p.m. UTC
  Auxiliary instructions are no real instructions and get their own object
class, similar to gaps. gdb.Record.instruction_history is now possibly a
list of gdb.RecordInstruction, gdb.RecordGap or gdb.RecordAuxiliary
objects.

This patch is in preparation for the new ptwrite feature, which is based on
auxiliary instructions.
---
 gdb/doc/python.texi           | 13 +++++++
 gdb/python/py-record-btrace.c | 35 +++++++++++------
 gdb/python/py-record.c        | 73 ++++++++++++++++++++++++++++++++++-
 gdb/python/py-record.h        |  3 ++
 4 files changed, 111 insertions(+), 13 deletions(-)
  

Comments

Simon Marchi March 24, 2023, 2:27 p.m. UTC | #1
On 3/21/23 11:46, Felix Willgerodt via Gdb-patches wrote:
> @@ -163,6 +165,14 @@ btpy_insn_or_gap_new (thread_info *tinfo, Py_ssize_t number)
>        return recpy_gap_new (err_code, err_string, number);
>      }
>  
> +  const struct btrace_insn *insn = btrace_insn_get (&iter);
> +  gdb_assert (insn != nullptr);
> +
> +  if (insn->iclass == BTRACE_INSN_AUX)
> +    return recpy_aux_new
> +      (iter.btinfo->aux_data.at (insn->aux_data_index).c_str (), number);

Not really important, but recpy_aux_new could take a
`const std::string &`, it would simplify callers a bit.

> +
> +

Remove one newline.

> +/* Implementation of Auxiliary.data [str].  */
> +
> +static PyObject *
> +recpy_aux_data (PyObject *self, void *closure)
> +{
> +  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
> +
> +  return PyUnicode_FromString (obj->data);
> +}

Nothing new with this patch, since this is the same pattern used for
other object, but just wondering: obj->data seems to be borrowed from
the btrace backend.  Are there some lifetime issues if you do:

1. Create a gdb.RecordAuxiliary object "b"
2. Issue a gdb command to clear the btrace data
3. Access "b.data"

?  It seems to me like obj->data might point to freed data.

Simon
  
Terekhov, Mikhail via Gdb-patches March 31, 2023, 10:58 a.m. UTC | #2
> -----Original Message-----
> From: Simon Marchi <simark@simark.ca>
> Sent: Freitag, 24. März 2023 15:28
> To: Willgerodt, Felix <felix.willgerodt@intel.com>; gdb-
> patches@sourceware.org
> Subject: Re: [PATCH v8 05/10] python: Introduce gdb.RecordAuxiliary class.
> 
> On 3/21/23 11:46, Felix Willgerodt via Gdb-patches wrote:
> > @@ -163,6 +165,14 @@ btpy_insn_or_gap_new (thread_info *tinfo,
> Py_ssize_t number)
> >        return recpy_gap_new (err_code, err_string, number);
> >      }
> >
> > +  const struct btrace_insn *insn = btrace_insn_get (&iter);
> > +  gdb_assert (insn != nullptr);
> > +
> > +  if (insn->iclass == BTRACE_INSN_AUX)
> > +    return recpy_aux_new
> > +      (iter.btinfo->aux_data.at (insn->aux_data_index).c_str (), number);
> 
> Not really important, but recpy_aux_new could take a
> `const std::string &`, it would simplify callers a bit.

Thanks, I will do that.

> > +
> > +
> 
> Remove one newline.

Done.

> > +/* Implementation of Auxiliary.data [str].  */
> > +
> > +static PyObject *
> > +recpy_aux_data (PyObject *self, void *closure)
> > +{
> > +  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
> > +
> > +  return PyUnicode_FromString (obj->data);
> > +}
> 
> Nothing new with this patch, since this is the same pattern used for
> other object, but just wondering: obj->data seems to be borrowed from
> the btrace backend.  Are there some lifetime issues if you do:
> 
> 1. Create a gdb.RecordAuxiliary object "b"
> 2. Issue a gdb command to clear the btrace data
> 3. Access "b.data"
> 
> ?  It seems to me like obj->data might point to freed data.

About the pattern:
There is already "maint btrace clear" so I could test and debug this with
master. On master GDB prints an error before accessing any of these objects,
as we check if the trace is empty before accessing anything.

With my patch I could however see a nullptr access. But not at this part of the code.
I think I need to rethink the modelling of Auxiliary objects as new objects.
We model them as a class of instruction in btrace.c.
And the python lists we put them in are designed to be a list of a specific type of object.
So I can either change the way lists are for btrace recordings or model Auxiliaries
as an instruction. I will think about it and post a new revision that addresses this.

Thanks,
Felix
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Simon Marchi April 3, 2023, 7:06 p.m. UTC | #3
On 3/31/23 06:58, Willgerodt, Felix wrote:
>> -----Original Message-----
>> From: Simon Marchi <simark@simark.ca>
>> Sent: Freitag, 24. März 2023 15:28
>> To: Willgerodt, Felix <felix.willgerodt@intel.com>; gdb-
>> patches@sourceware.org
>> Subject: Re: [PATCH v8 05/10] python: Introduce gdb.RecordAuxiliary class.
>>
>> On 3/21/23 11:46, Felix Willgerodt via Gdb-patches wrote:
>>> @@ -163,6 +165,14 @@ btpy_insn_or_gap_new (thread_info *tinfo,
>> Py_ssize_t number)
>>>        return recpy_gap_new (err_code, err_string, number);
>>>      }
>>>
>>> +  const struct btrace_insn *insn = btrace_insn_get (&iter);
>>> +  gdb_assert (insn != nullptr);
>>> +
>>> +  if (insn->iclass == BTRACE_INSN_AUX)
>>> +    return recpy_aux_new
>>> +      (iter.btinfo->aux_data.at (insn->aux_data_index).c_str (), number);
>>
>> Not really important, but recpy_aux_new could take a
>> `const std::string &`, it would simplify callers a bit.
> 
> Thanks, I will do that.
> 
>>> +
>>> +
>>
>> Remove one newline.
> 
> Done.
> 
>>> +/* Implementation of Auxiliary.data [str].  */
>>> +
>>> +static PyObject *
>>> +recpy_aux_data (PyObject *self, void *closure)
>>> +{
>>> +  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
>>> +
>>> +  return PyUnicode_FromString (obj->data);
>>> +}
>>
>> Nothing new with this patch, since this is the same pattern used for
>> other object, but just wondering: obj->data seems to be borrowed from
>> the btrace backend.  Are there some lifetime issues if you do:
>>
>> 1. Create a gdb.RecordAuxiliary object "b"
>> 2. Issue a gdb command to clear the btrace data
>> 3. Access "b.data"
>>
>> ?  It seems to me like obj->data might point to freed data.
> 
> About the pattern:
> There is already "maint btrace clear" so I could test and debug this with
> master. On master GDB prints an error before accessing any of these objects,
> as we check if the trace is empty before accessing anything.

Then, maybe if you record something after clearing the data?  Something
like:

 1. Create a gdb.RecordAuxiliary object "b"
 2. Issue "maint btrace clear"
 3. Do one step (to make the trace non-empty again)
 4. Access "b.data"

> With my patch I could however see a nullptr access. But not at this part of the code.
> I think I need to rethink the modelling of Auxiliary objects as new objects.
> We model them as a class of instruction in btrace.c.
> And the python lists we put them in are designed to be a list of a specific type of object.

Which list are you referring to?

Simon
  
Terekhov, Mikhail via Gdb-patches April 4, 2023, 6:57 a.m. UTC | #4
>>>> +/* Implementation of Auxiliary.data [str].  */
>>>> +
>>>> +static PyObject *
>>>> +recpy_aux_data (PyObject *self, void *closure)
>>>> +{
>>>> +  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
>>>> +
>>>> +  return PyUnicode_FromString (obj->data);
>>>> +}
>>>
>>> Nothing new with this patch, since this is the same pattern used for
>>> other object, but just wondering: obj->data seems to be borrowed from
>>> the btrace backend.  Are there some lifetime issues if you do:
>>>
>>> 1. Create a gdb.RecordAuxiliary object "b"
>>> 2. Issue a gdb command to clear the btrace data
>>> 3. Access "b.data"
>>>
>>> ?  It seems to me like obj->data might point to freed data.
>>
>> About the pattern:
>> There is already "maint btrace clear" so I could test and debug this with
>> master. On master GDB prints an error before accessing any of these objects,
>> as we check if the trace is empty before accessing anything.
>
>Then, maybe if you record something after clearing the data?  Something
>like:
>
> 1. Create a gdb.RecordAuxiliary object "b"
> 2. Issue "maint btrace clear"
> 3. Do one step (to make the trace non-empty again)
> 4. Access "b.data"

I added the 'maint btrace clear' command to force re-decoding of the trace
for debugging purposes.  Any CLI command that uses btrace will trigger a
trace fetch and decode.

For ptwrite filters, the use-case is to replace the filter and re-decode with
the new filter in place (and the old filter removed).  I'm not against chaining
filters and decorating their aux contribution with the filter name.  IIRC this
was considered more complicated and we couldn't really find a good use-case.

The filter needs to know about the source or binary instrumentation that
added those ptwrite instructions.  It's job is to aggregate the raw data and
the IP of the ptwrite instruction that emitted that data and turn it into
something more useful than the raw hex values.

The use case for filter chaining would be independent instrumentations
active at the same time and interpreted by their respective ptwrite filter.

To the topic at hand, I'd say that record instruction or aux objects should
behave like, say, frame objects.  You cannot clear the frames to force a
re-unwind AFAIK but you can step or finish to turn a frame invalid.

Regards,
Markus.
Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  
Simon Marchi April 4, 2023, 2:17 p.m. UTC | #5
On 4/4/23 02:57, Metzger, Markus T wrote:
> 
>>>>> +/* Implementation of Auxiliary.data [str].  */
>>>>> +
>>>>> +static PyObject *
>>>>> +recpy_aux_data (PyObject *self, void *closure)
>>>>> +{
>>>>> +  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
>>>>> +
>>>>> +  return PyUnicode_FromString (obj->data);
>>>>> +}
>>>>
>>>> Nothing new with this patch, since this is the same pattern used for
>>>> other object, but just wondering: obj->data seems to be borrowed from
>>>> the btrace backend.  Are there some lifetime issues if you do:
>>>>
>>>> 1. Create a gdb.RecordAuxiliary object "b"
>>>> 2. Issue a gdb command to clear the btrace data
>>>> 3. Access "b.data"
>>>>
>>>> ?  It seems to me like obj->data might point to freed data.
>>>
>>> About the pattern:
>>> There is already "maint btrace clear" so I could test and debug this with
>>> master. On master GDB prints an error before accessing any of these objects,
>>> as we check if the trace is empty before accessing anything.
>>
>> Then, maybe if you record something after clearing the data?  Something
>> like:
>>
>> 1. Create a gdb.RecordAuxiliary object "b"
>> 2. Issue "maint btrace clear"
>> 3. Do one step (to make the trace non-empty again)
>> 4. Access "b.data"
> 
> I added the 'maint btrace clear' command to force re-decoding of the trace
> for debugging purposes.  Any CLI command that uses btrace will trigger a
> trace fetch and decode.

Ack.

> For ptwrite filters, the use-case is to replace the filter and re-decode with
> the new filter in place (and the old filter removed).

Yes, that makes sense.

> I'm not against chaining
> filters and decorating their aux contribution with the filter name.  IIRC this
> was considered more complicated and we couldn't really find a good use-case.

Just to be clear, I didn't suggest to add that.

> The filter needs to know about the source or binary instrumentation that
> added those ptwrite instructions.  It's job is to aggregate the raw data and
> the IP of the ptwrite instruction that emitted that data and turn it into
> something more useful than the raw hex values.
> 
> The use case for filter chaining would be independent instrumentations
> active at the same time and interpreted by their respective ptwrite filter.

Ack.  Or some kind of top-level filter that delegates.

> To the topic at hand, I'd say that record instruction or aux objects should
> behave like, say, frame objects.  You cannot clear the frames to force a
> re-unwind AFAIK but you can step or finish to turn a frame invalid.

Yes, that is my worry.  It seems possible for the btrace Python objects
to outlive the data they are wrapping, and I don't see any measures
taken to avoid accessing the stale data.  Usually that can be done with
an observer that will mark the Python objects invalid.

Another case: if you "continue" and a thread exits, is the btrace record
data for that thread deleted?

Simon
  
Terekhov, Mikhail via Gdb-patches April 4, 2023, 2:26 p.m. UTC | #6
> -----Original Message-----
> From: Simon Marchi <simark@simark.ca>
> Sent: Dienstag, 4. April 2023 16:17
> To: Metzger, Markus T <markus.t.metzger@intel.com>; Willgerodt, Felix
> <felix.willgerodt@intel.com>
> Cc: gdb-patches@sourceware.org
> Subject: Re: [PATCH v8 05/10] python: Introduce gdb.RecordAuxiliary class.
> 
> On 4/4/23 02:57, Metzger, Markus T wrote:
> >
> >>>>> +/* Implementation of Auxiliary.data [str].  */
> >>>>> +
> >>>>> +static PyObject *
> >>>>> +recpy_aux_data (PyObject *self, void *closure)
> >>>>> +{
> >>>>> +  const recpy_aux_object * const obj = (const recpy_aux_object *)
> self;
> >>>>> +
> >>>>> +  return PyUnicode_FromString (obj->data);
> >>>>> +}
> >>>>
> >>>> Nothing new with this patch, since this is the same pattern used for
> >>>> other object, but just wondering: obj->data seems to be borrowed
> from
> >>>> the btrace backend.  Are there some lifetime issues if you do:
> >>>>
> >>>> 1. Create a gdb.RecordAuxiliary object "b"
> >>>> 2. Issue a gdb command to clear the btrace data
> >>>> 3. Access "b.data"
> >>>>
> >>>> ?  It seems to me like obj->data might point to freed data.
> >>>
> >>> About the pattern:
> >>> There is already "maint btrace clear" so I could test and debug this with
> >>> master. On master GDB prints an error before accessing any of these
> objects,
> >>> as we check if the trace is empty before accessing anything.
> >>
> >> Then, maybe if you record something after clearing the data?  Something
> >> like:
> >>
> >> 1. Create a gdb.RecordAuxiliary object "b"
> >> 2. Issue "maint btrace clear"
> >> 3. Do one step (to make the trace non-empty again)
> >> 4. Access "b.data"
> >
> > I added the 'maint btrace clear' command to force re-decoding of the trace
> > for debugging purposes.  Any CLI command that uses btrace will trigger a
> > trace fetch and decode.
> 
> Ack.
> 
> > For ptwrite filters, the use-case is to replace the filter and re-decode with
> > the new filter in place (and the old filter removed).
> 
> Yes, that makes sense.
> 
> > I'm not against chaining
> > filters and decorating their aux contribution with the filter name.  IIRC this
> > was considered more complicated and we couldn't really find a good use-
> case.
> 
> Just to be clear, I didn't suggest to add that.
> 
> > The filter needs to know about the source or binary instrumentation that
> > added those ptwrite instructions.  It's job is to aggregate the raw data and
> > the IP of the ptwrite instruction that emitted that data and turn it into
> > something more useful than the raw hex values.
> >
> > The use case for filter chaining would be independent instrumentations
> > active at the same time and interpreted by their respective ptwrite filter.
> 
> Ack.  Or some kind of top-level filter that delegates.
> 
> > To the topic at hand, I'd say that record instruction or aux objects should
> > behave like, say, frame objects.  You cannot clear the frames to force a
> > re-unwind AFAIK but you can step or finish to turn a frame invalid.
> 
> Yes, that is my worry.  It seems possible for the btrace Python objects
> to outlive the data they are wrapping, and I don't see any measures
> taken to avoid accessing the stale data.  Usually that can be done with
> an observer that will mark the Python objects invalid.
> 
> Another case: if you "continue" and a thread exits, is the btrace record
> data for that thread deleted?
> 
> Simon

I looked at it a bit more yesterday.

The gap objects get a string literal (const char *). Which have a guaranteed
lifetime over the full program afaik and therefore pointers are never dangling.
I didn't check if we wouldn't error out first, as I don't know how to reliably
record gaps.

The insn and func objects cannot be accessed after clear.
Any commands will show:

"gdb.error: No such function segment."
or
"gdb.error: No such instruction."

Even if you "save" it in a separate python variable, e.g. like "b" in Simon's list.

For auxiliaries in my patch however, Simon is right. We just pass a pointer
to an element in a vector of strings (aux_data) that is allocated in btrace.c.
We clear that vector with clear. I debugged GDB, in that case we do still point
to the same address and just read a string from it. No matter what is at that
address now:

>>> r = gdb.current_recording()
>>> i = r.instruction_history
>>> a = i[9]
>>> a.data

Thread 1 "gdb-up" hit Breakpoint 1, recpy_aux_data (
    self=<gdb.RecordAuxiliary at remote 0x7fffe8152710>, closure=0x0)
    at gdb/gdb/python/py-record.c:547
547       const recpy_aux_object * const obj = (const recpy_aux_object *) self;
(gdb) n
549       return PyUnicode_FromString (obj->data);
(gdb) p obj.data
$1 = 0x2d1ade0 "42"
(gdb) c
Continuing.
'42'
>>> r.clear()
>>> a.data

Thread 1 "gdb-up" hit Breakpoint 1, recpy_aux_data (
    self=<gdb.RecordAuxiliary at remote 0x7fffe8152710>, closure=0x0)
    at gdb/gdb/python/py-record.c:547
547       const recpy_aux_object * const obj = (const recpy_aux_object *) self;
(gdb) n
549       return PyUnicode_FromString (obj->data);
(gdb) p obj.data
$2 = 0x2d1ade0 "42"
(gdb) c
Continuing.
'42'

So I should really create a copy or error out if the trace
was cleared instead. Probably the latter to be consistent.

Thanks,
Felix


Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  

Patch

diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 54d5660543a..31243b8ec47 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3914,6 +3914,19 @@  the current recording method.
 A human readable string with the reason for the gap.
 @end defvar
 
+Some @value{GDBN} features write auxiliary information into the execution
+history.  This information is represented by a @code{gdb.RecordAuxiliary} object
+in the instruction list.  It has the following attributes:
+
+@defvar RecordAuxiliary.number
+An integer identifying this auxiliary.  @code{number} corresponds to the numbers
+seen in @code{record instruction-history} (@pxref{Process Record and Replay}).
+@end defvar
+
+@defvar RecordAuxiliary.data
+A string representation of the auxiliary data.
+@end defvar
+
 A @code{gdb.RecordFunctionSegment} object has the following attributes:
 
 @defvar RecordFunctionSegment.number
diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c
index 4af86672d26..16925eadd7a 100644
--- a/gdb/python/py-record-btrace.c
+++ b/gdb/python/py-record-btrace.c
@@ -45,7 +45,8 @@  struct btpy_list_object {
   /* Stride size.  */
   Py_ssize_t step;
 
-  /* Either &BTPY_CALL_TYPE or &RECPY_INSN_TYPE.  */
+  /* Either &recpy_func_type, &recpy_insn_type, &recpy_aux_type or
+     &recpy_gap_type.  */
   PyTypeObject* element_type;
 };
 
@@ -141,10 +142,11 @@  btrace_func_from_recpy_func (const PyObject * const pyobject)
 }
 
 /* Looks at the recorded item with the number NUMBER and create a
-   gdb.RecordInstruction or gdb.RecordGap object for it accordingly.  */
+   gdb.RecordInstruction, gdb.RecordGap or gdb.RecordAuxiliary object
+   for it accordingly.  */
 
 static PyObject *
-btpy_insn_or_gap_new (thread_info *tinfo, Py_ssize_t number)
+btpy_item_new (thread_info *tinfo, Py_ssize_t number)
 {
   btrace_insn_iterator iter;
   int err_code;
@@ -163,6 +165,14 @@  btpy_insn_or_gap_new (thread_info *tinfo, Py_ssize_t number)
       return recpy_gap_new (err_code, err_string, number);
     }
 
+  const struct btrace_insn *insn = btrace_insn_get (&iter);
+  gdb_assert (insn != nullptr);
+
+  if (insn->iclass == BTRACE_INSN_AUX)
+    return recpy_aux_new
+      (iter.btinfo->aux_data.at (insn->aux_data_index).c_str (), number);
+
+
   return recpy_insn_new (tinfo, RECORD_METHOD_BTRACE, number);
 }
 
@@ -441,8 +451,10 @@  btpy_list_length (PyObject *self)
 }
 
 /* Implementation of
-   BtraceList.__getitem__ (self, key) -> BtraceInstruction and
-   BtraceList.__getitem__ (self, key) -> BtraceFunctionCall.  */
+   BtraceList.__getitem__ (self, key) -> BtraceInstruction,
+   BtraceList.__getitem__ (self, key) -> BtraceFunctionCall,
+   BtraceList.__getitem__ (self, key) -> BtraceAuxilliary and
+   BtraceList.__getitem__ (self, key) -> BtraceGap.  */
 
 static PyObject *
 btpy_list_item (PyObject *self, Py_ssize_t index)
@@ -456,10 +468,10 @@  btpy_list_item (PyObject *self, Py_ssize_t index)
 
   number = obj->first + (obj->step * index);
 
-  if (obj->element_type == &recpy_insn_type)
-    return recpy_insn_new (obj->thread, RECORD_METHOD_BTRACE, number);
-  else
+  if (obj->element_type == &recpy_func_type)
     return recpy_func_new (obj->thread, RECORD_METHOD_BTRACE, number);
+  else
+    return btpy_item_new (obj->thread, number);
 }
 
 /* Implementation of BtraceList.__getitem__ (self, slice) -> BtraceList.  */
@@ -646,8 +658,7 @@  recpy_bt_replay_position (PyObject *self, void *closure)
   if (tinfo->btrace.replay == NULL)
     Py_RETURN_NONE;
 
-  return btpy_insn_or_gap_new (tinfo,
-			       btrace_insn_number (tinfo->btrace.replay));
+  return btpy_item_new (tinfo, btrace_insn_number (tinfo->btrace.replay));
 }
 
 /* Implementation of
@@ -669,7 +680,7 @@  recpy_bt_begin (PyObject *self, void *closure)
     Py_RETURN_NONE;
 
   btrace_insn_begin (&iterator, &tinfo->btrace);
-  return btpy_insn_or_gap_new (tinfo, btrace_insn_number (&iterator));
+  return btpy_item_new (tinfo, btrace_insn_number (&iterator));
 }
 
 /* Implementation of
@@ -691,7 +702,7 @@  recpy_bt_end (PyObject *self, void *closure)
     Py_RETURN_NONE;
 
   btrace_insn_end (&iterator, &tinfo->btrace);
-  return btpy_insn_or_gap_new (tinfo, btrace_insn_number (&iterator));
+  return btpy_item_new (tinfo, btrace_insn_number (&iterator));
 }
 
 /* Implementation of
diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c
index 1e40f2cded0..1afa0f25275 100644
--- a/gdb/python/py-record.c
+++ b/gdb/python/py-record.c
@@ -49,6 +49,12 @@  static PyTypeObject recpy_gap_type = {
   PyVarObject_HEAD_INIT (NULL, 0)
 };
 
+/* Python RecordAuxiliary type.  */
+
+PyTypeObject recpy_aux_type = {
+  PyVarObject_HEAD_INIT (nullptr, 0)
+};
+
 /* Python RecordGap object.  */
 struct recpy_gap_object
 {
@@ -64,6 +70,18 @@  struct recpy_gap_object
   Py_ssize_t number;
 };
 
+/* Python RecordAuxiliary object.  */
+typedef struct
+{
+  PyObject_HEAD
+
+  /* Auxiliary data.  */
+  const char *data;
+
+  /* Element number.  */
+  Py_ssize_t number;
+} recpy_aux_object;
+
 /* Implementation of record.method.  */
 
 static PyObject *
@@ -477,6 +495,43 @@  recpy_gap_reason_string (PyObject *self, void *closure)
   return PyUnicode_FromString (obj->reason_string);
 }
 
+/* Create a new gdb.Auxiliary object.  */
+
+PyObject *
+recpy_aux_new (const char *data, Py_ssize_t number)
+{
+  recpy_aux_object * const obj = PyObject_New (recpy_aux_object,
+					       &recpy_aux_type);
+
+  if (obj == nullptr)
+   return nullptr;
+
+  obj->data = data;
+  obj->number = number;
+
+  return (PyObject *) obj;
+}
+
+/* Implementation of Auxiliary.number [int].  */
+
+static PyObject *
+recpy_aux_number (PyObject *self, void *closure)
+{
+  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
+
+  return gdb_py_object_from_longest (obj->number).release ();
+}
+
+/* Implementation of Auxiliary.data [str].  */
+
+static PyObject *
+recpy_aux_data (PyObject *self, void *closure)
+{
+  const recpy_aux_object * const obj = (const recpy_aux_object *) self;
+
+  return PyUnicode_FromString (obj->data);
+}
+
 /* Record method list.  */
 
 static PyMethodDef recpy_record_methods[] = {
@@ -542,6 +597,14 @@  static gdb_PyGetSetDef recpy_gap_getset[] = {
   { NULL }
 };
 
+/* RecordAuxiliary member list.  */
+
+static gdb_PyGetSetDef recpy_aux_getset[] = {
+  { "number", recpy_aux_number, nullptr, "element number", nullptr},
+  { "data", recpy_aux_data, nullptr, "data", nullptr},
+  { nullptr }
+};
+
 /* Sets up the record API in the gdb module.  */
 
 int
@@ -581,10 +644,18 @@  gdbpy_initialize_record (void)
   recpy_gap_type.tp_doc = "GDB recorded gap object";
   recpy_gap_type.tp_getset = recpy_gap_getset;
 
+  recpy_aux_type.tp_new = PyType_GenericNew;
+  recpy_aux_type.tp_flags = Py_TPFLAGS_DEFAULT;
+  recpy_aux_type.tp_basicsize = sizeof (recpy_aux_object);
+  recpy_aux_type.tp_name = "gdb.RecordAuxiliary";
+  recpy_aux_type.tp_doc = "GDB recorded auxiliary object";
+  recpy_aux_type.tp_getset = recpy_aux_getset;
+
   if (PyType_Ready (&recpy_record_type) < 0
       || PyType_Ready (&recpy_insn_type) < 0
       || PyType_Ready (&recpy_func_type) < 0
-      || PyType_Ready (&recpy_gap_type) < 0)
+      || PyType_Ready (&recpy_gap_type) < 0
+      || PyType_Ready (&recpy_aux_type) < 0)
     return -1;
   else
     return 0;
diff --git a/gdb/python/py-record.h b/gdb/python/py-record.h
index 6eec71e06e7..75bf6fae935 100644
--- a/gdb/python/py-record.h
+++ b/gdb/python/py-record.h
@@ -71,4 +71,7 @@  extern PyObject *recpy_func_new (thread_info *thread, enum record_method method,
 extern PyObject *recpy_gap_new (int reason_code, const char *reason_string,
 				Py_ssize_t number);
 
+/* Create a new gdb.RecordGap object.  */
+extern PyObject *recpy_aux_new (const char *data, Py_ssize_t number);
+
 #endif /* PYTHON_PY_RECORD_H */