[v2,12/12] btrace: Extend ptwrite event decoding.

Message ID 20210614145411.689277-8-felix.willgerodt@intel.com
State Superseded
Headers show
Series
  • Extensions for PTWRITE
Related show

Commit Message

Simon Marchi via Gdb-patches June 14, 2021, 2:54 p.m.
Call the ptwrite listener function whenever a ptwrite event is decoded.
The returned string is written to the aux_data string table and a
corresponding auxiliary instruction is appended to the function segment.

gdb/ChangeLog:
2021-06-14  Felix Willgerodt  <felix.willgerodt@intel.com>

	* btrace.c (handle_pt_insn_events): Handle ptev_ptwrite.

gdb/testsuite/ChangeLog:
2021-06-14  Felix Willgerodt  <felix.willgerodt@intel.com>

	* gdb.btrace/ptwrite.c: New file.
	* gdb.btrace/ptwrite.exp: New file.
	* gdb.btrace/x86_64-ptwrite.S: New file.
	* lib/gdb.exp (skip_btrace_ptw_tests): New function.

gdb/doc/ChangeLog:
2021-06-14  Felix Willgerodt  <felix.willgerodt@intel.com>

	* python.texi (gdb.ptwrite): New documentation.
---
 gdb/NEWS                                  |   6 +
 gdb/btrace.c                              |  52 +++
 gdb/doc/python.texi                       | 132 ++++++
 gdb/testsuite/gdb.btrace/ptwrite.c        |  39 ++
 gdb/testsuite/gdb.btrace/ptwrite.exp      | 194 +++++++++
 gdb/testsuite/gdb.btrace/x86_64-ptwrite.S | 479 ++++++++++++++++++++++
 gdb/testsuite/lib/gdb.exp                 |  82 ++++
 7 files changed, 984 insertions(+)
 create mode 100644 gdb/testsuite/gdb.btrace/ptwrite.c
 create mode 100644 gdb/testsuite/gdb.btrace/ptwrite.exp
 create mode 100644 gdb/testsuite/gdb.btrace/x86_64-ptwrite.S

-- 
2.25.4

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

Comments

Simon Marchi via Gdb-patches June 15, 2021, 11:50 a.m. | #1
> Date: Mon, 14 Jun 2021 16:54:11 +0200

> From: Felix Willgerodt via Gdb-patches <gdb-patches@sourceware.org>

> 

> --- a/gdb/NEWS

> +++ b/gdb/NEWS

> @@ -3,6 +3,12 @@

>  

>  *** Changes since GDB 10

>  

> +* GDB now supports printing of ptwrite payloads from the Intel Processor

> +  Trace during 'record instruction-history', 'record function-call-history',

> +  all stepping commands and in  Python.  Printing is customizable via a

> +  ptwrite listener function in  Python.  By default, the raw ptwrite

> +  payload is printed for each ptwrite that is encountered.

> +

>  * The 'set disassembler-options' command now supports specifying options

>    for the ARC target.


This part is OK.

> +This module provides additional functionality for recording programs that make

> +use of the PTWRITE instruction.  PTWRITE is a x86 instruction that allows to

> +write values into the Intel Processor Trace (@pxref{Process Record and Replay}).


Should PTWRITE be in @code or in @sc?

> +The @code{gdb.ptwrite} module allows customizing the default output of ptwrite

> +auxiliary information.  A custom Python function can be registered via

> +@code{gdb.ptwrite.register_listener()} as the ptwrite listener function.

                                      ^^
Please remove the parentheses there.  Otherwise, this looks like a
call to the function with no arguments, which is not what you want.

> +@smallexample

> +@group

> +(gdb) python-interactive

> +>>> class my_listener(object):

> +...    def __init__(self):

> +...        self.variable = 0

> +...    def __call__(self, payload, ip):

> +...        if gdb.selected_thread().global_num == 1:

> +...            self.variable += 1

> +...            return "payload: @{@}, ip: @{:#x@}".format(self.variable, ip)

                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This line is too long for @smallexample, it will cause overful hbox in
the printed version of the manual.

> +This GDB feature is dependent on hardware and operating system support and

        ^^^
@value{GDBN}

Thanks.
Simon Marchi via Gdb-patches June 15, 2021, 1:40 p.m. | #2
>>  * The 'set disassembler-options' command now supports specifying options

>>    for the ARC target.


Eli> This part is OK.

Thanks!

>> +This module provides additional functionality for recording programs 

>> +that make use of the PTWRITE instruction.  PTWRITE is a x86 

>> +instruction that allows to write values into the Intel Processor Trace (@pxref{Process Record and Replay}).


Eli> Should PTWRITE be in @code or in @sc?

I don't know, I couldn't find any convention for instructions in text
paragraphs when searching the docs. I changed it locally to @code,
as that looked cleaner to me.

>> +The @code{gdb.ptwrite} module allows customizing the default output 

>> +of ptwrite auxiliary information.  A custom Python function can be 

>> +registered via @code{gdb.ptwrite.register_listener()} as the ptwrite listener function.


Eli>                                      ^^ Please remove the parentheses there.  Otherwise, this looks like a
Eli> call to the function with no arguments, which is not what you want.

Fixed it locally for the next revision.

>> +@smallexample

>> +@group

>> +(gdb) python-interactive

>> +>>> class my_listener(object):

>> +...    def __init__(self):

>> +...        self.variable = 0

>> +...    def __call__(self, payload, ip):

>> +...        if gdb.selected_thread().global_num == 1:

>> +...            self.variable += 1

>> +...            return "payload: @{@}, ip: @{:#x@}".format(self.variable, ip)


Eli> This line is too long for @smallexample, it will cause overful hbox in the
Eli> printed version of the manual.

Are you sure? I can't find any overfull hbox message for this line in the output
of 'make pdf' or the resulting gdb.log. When I shorten the line significantly
and diff the output of 'make pdf' (and gdb.log) with the long version,
I see no real difference. There are only underful messages for python.texi for me.
I have locally changed the variable name to "self.var" to shorten this anyway.

>> +This GDB feature is dependent on hardware and operating system 

>> +support and


Eli> @value{GDBN}

Fixed it locally for the next revision.

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 via Gdb-patches June 15, 2021, 1:52 p.m. | #3
> From: "Willgerodt, Felix" <felix.willgerodt@intel.com>

> CC: "Metzger, Markus T" <markus.t.metzger@intel.com>,

> 	"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>

> Date: Tue, 15 Jun 2021 13:40:40 +0000

> 

> >> +@smallexample

> >> +@group

> >> +(gdb) python-interactive

> >> +>>> class my_listener(object):

> >> +...    def __init__(self):

> >> +...        self.variable = 0

> >> +...    def __call__(self, payload, ip):

> >> +...        if gdb.selected_thread().global_num == 1:

> >> +...            self.variable += 1

> >> +...            return "payload: @{@}, ip: @{:#x@}".format(self.variable, ip)

> 

> Eli> This line is too long for @smallexample, it will cause overful hbox in the

> Eli> printed version of the manual.

> 

> Are you sure? I can't find any overfull hbox message for this line in the output

> of 'make pdf' or the resulting gdb.log. When I shorten the line significantly

> and diff the output of 'make pdf' (and gdb.log) with the long version,

> I see no real difference. There are only underful messages for python.texi for me.


In general, any line longer than about 66 characters in @example and
76 in @smallexample are too long, AFAIR.

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 56743fc9aea..c920a5cb8ec 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,12 @@ 
 
 *** Changes since GDB 10
 
+* GDB now supports printing of ptwrite payloads from the Intel Processor
+  Trace during 'record instruction-history', 'record function-call-history',
+  all stepping commands and in  Python.  Printing is customizable via a
+  ptwrite listener function in  Python.  By default, the raw ptwrite
+  payload is printed for each ptwrite that is encountered.
+
 * The 'set disassembler-options' command now supports specifying options
   for the ARC target.
 
diff --git a/gdb/btrace.c b/gdb/btrace.c
index d66c3e21f11..d276de723c0 100644
--- a/gdb/btrace.c
+++ b/gdb/btrace.c
@@ -1247,6 +1247,58 @@  handle_pt_insn_events (struct btrace_thread_info *btinfo,
 		   bfun->insn_offset - 1, offset);
 
 	  break;
+
+	case ptev_ptwrite:
+	  {
+	    uint64_t *ip = nullptr;
+	    gdb::unique_xmalloc_ptr<char> ptw_string = nullptr;
+	    struct btrace_insn ptw_insn;
+	    btrace_insn_flags flags;
+
+	    /* Lookup the ip if available.  */
+	    if (event.ip_suppressed == 0)
+	      ip = &event.variant.ptwrite.ip;
+	    else if (!btinfo->functions.empty ()
+		     && !btinfo->functions.back ().insn.empty ())
+	      ip = &btinfo->functions.back ().insn.back ().pc;
+
+	    if (btinfo->ptw_callback_fun != nullptr)
+	      ptw_string = btinfo->ptw_callback_fun (
+					  &event.variant.ptwrite.payload, ip,
+					  btinfo->ptw_listener);
+
+	    if (ptw_string == nullptr)
+	      break;
+
+	    btinfo->aux_data.emplace_back (ptw_string.get ());
+
+	    if (!btinfo->functions.empty ()
+		&& !btinfo->functions.back ().insn.empty ())
+	      flags = btinfo->functions.back ().insn.back ().flags;
+	    else
+	      flags = 0;
+
+	    /* Update insn list with ptw payload insn.  */
+	    ptw_insn.aux_data_index = btinfo->aux_data.size () - 1;
+	    ptw_insn.flags = flags;
+	    ptw_insn.iclass = BTRACE_INSN_AUX;
+	    ptw_insn.size = 0;
+
+	    if (ip != nullptr)
+	      bfun = ftrace_update_function (btinfo, *ip);
+	    else
+	      {
+		if (btinfo->functions.empty ())
+		  bfun = ftrace_new_function (btinfo, NULL, NULL);
+		else
+		  bfun = &btinfo->functions.back ();
+	      }
+
+	    bfun->flags |= BFUN_AUX_DECODED;
+	    ftrace_update_insns (bfun, ptw_insn);
+
+	    break;
+	  }
 	}
     }
 #endif /* defined (HAVE_PT_INSN_EVENT) */
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index fa1c69a2283..4436aaa79f8 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -6110,6 +6110,7 @@  registering objfile-specific pretty-printers and frame-filters.
 * gdb.printing::       Building and registering pretty-printers.
 * gdb.types::          Utilities for working with types.
 * gdb.prompt::         Utilities for prompt value substitution.
+* gdb.ptwrite::        Utilities for ptwrite listener registration.
 @end menu
 
 @node gdb.printing
@@ -6300,3 +6301,134 @@  substitute_prompt ("frame: \f, args: \p@{print frame-arguments@}")
 "frame: main, args: scalars"
 @end smallexample
 @end table
+
+@node gdb.ptwrite
+@subsubsection gdb.ptwrite
+@cindex gdb.ptwrite
+
+This module provides additional functionality for recording programs that make
+use of the PTWRITE instruction.  PTWRITE is a x86 instruction that allows to
+write values into the Intel Processor Trace (@pxref{Process Record and Replay}).
+The @value{NGCC} built-in functions for it are:
+@smallexample
+void __builtin_ia32_ptwrite32 (unsigned);
+void __builtin_ia32_ptwrite64 (unsigned long long);
+@end smallexample
+
+If an inferior uses the instruction, @value{GDBN} inserts the raw payload value
+as auxiliary information into the execution history.  Auxiliary information
+is by default printed during @code{record instruction-history},
+@code{record function-call-history}, and all stepping commands and is accessible
+in Python as a @code{RecordAuxiliary} object. 
+
+@exdent Sample program:
+@smallexample
+@group
+void
+ptwrite64 (unsigned long long value)
+@{
+  __builtin_ia32_ptwrite64 (value);
+@}
+
+int
+main (void)
+@{
+  ptwrite64 (0x42);
+  return 0; /* break here.  */
+@}
+@end group
+@end smallexample
+
+
+@exdent @value{GDBN} output after recording the sample program in pt format:
+@smallexample
+@group
+(gdb) record instruction-history 12,14
+12         0x000000000040074c <ptwrite64+16>:	ptwrite %rbx
+13         [0x42]
+14         0x0000000000400751 <ptwrite64+21>:	mov    -0x8(%rbp),%rbx
+(gdb) record function-call-history
+1       main
+2       ptwrite64
+                [0x42]
+3       main
+@end group
+@end smallexample
+
+The @code{gdb.ptwrite} module allows customizing the default output of ptwrite
+auxiliary information.  A custom Python function can be registered via
+@code{gdb.ptwrite.register_listener()} as the ptwrite listener function.
+This function will be called with the ptwrite payload and PC as arguments
+during trace decoding.
+
+@findex gdb.ptwrite.register_listener
+@defun register_listener (@var{listener})
+Used to register the ptwrite listener.  The listener can be any callable
+object that accepts two arguments.  It can return a string, which will be
+printed by @value{GDBN} during the aforementioned commands, or @code{None},
+resulting in no output.  @code{None} can also be registered to deactivate
+printing.
+@end defun
+
+@findex gdb.ptwrite.get_listener
+@defun get_listener ()
+Returns the currently active ptwrite listener function.
+@end defun
+
+@findex gdb.ptwrite.default_listener
+@defun default_listener (@var{payload}, @var{ip})
+The listener function active by default.
+@end defun
+
+@value{GDBN} creates a new copy of the listener function for each thread to
+allow for independent internal states.  There is no support for registering
+different listeners for different threads.  The listener can however
+distinguish between multiple threads with the help of
+@code{gdb.selected_thread().global_num} (@pxref{Threads In Python}) or
+similar.  For example:
+
+@smallexample
+@group
+(gdb) python-interactive
+>>> class my_listener(object):
+...    def __init__(self):
+...        self.variable = 0
+...    def __call__(self, payload, ip):
+...        if gdb.selected_thread().global_num == 1:
+...            self.variable += 1
+...            return "payload: @{@}, ip: @{:#x@}".format(self.variable, ip)
+...        else:
+...            return None
+...
+>>> import gdb.ptwrite
+>>> gdb.ptwrite.register_listener(my_listener())
+>>>
+(gdb) record function-call-history 59,64
+59    pthread_create@@GLIBC_2.2.5
+60    job()
+61    task(void*)
+62    ptwrite64(unsigned long)
+          [payload: 1, ip: 0x401156]
+63    task(void*)
+64    ptwrite32(unsigned int)
+          [payload: 2, ip: 0x40116c]
+(gdb) info threads 
+* 1    Thread 0x7ffff7fd8740 (LWP 25796) "ptwrite_threads" task (arg=0x0)
+    at bin/ptwrite/ptwrite_threads.c:45
+  2    Thread 0x7ffff6eb8700 (LWP 25797) "ptwrite_threads" task (arg=0x0)
+    at bin/ptwrite/ptwrite_threads.c:45
+(gdb) thread 2
+[Switching to thread 2 (Thread 0x7ffff6eb8700 (LWP 25797))]
+#0  task (arg=0x0) at ptwrite_threads.c:45
+45	  return NULL;
+(gdb) record function-call-history 10,14
+10    start_thread
+11    task(void*)
+12    ptwrite64(unsigned long)
+13    task(void*)
+14    ptwrite32(unsigned int)
+@end group
+@end smallexample
+
+This GDB feature is dependent on hardware and operating system support and
+requires the Intel Processor Trace decoder library in version 2.0.0 or newer.
diff --git a/gdb/testsuite/gdb.btrace/ptwrite.c b/gdb/testsuite/gdb.btrace/ptwrite.c
new file mode 100644
index 00000000000..f5ead4e7d89
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/ptwrite.c
@@ -0,0 +1,39 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2021 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <stdint.h>
+
+void
+ptwrite64 (uint64_t value)
+{
+  asm volatile ("PTWRITE %0;" : : "b" (value));
+}
+
+void
+ptwrite32 (uint32_t value)
+{
+  asm volatile ("PTWRITE %0;" : : "b" (value));
+}
+
+int
+main (void)
+{
+  ptwrite64 (0x42);
+  ptwrite32 (0x43);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.btrace/ptwrite.exp b/gdb/testsuite/gdb.btrace/ptwrite.exp
new file mode 100644
index 00000000000..2bbf0f41d3c
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/ptwrite.exp
@@ -0,0 +1,194 @@ 
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+load_lib gdb-python.exp
+
+if { [skip_btrace_pt_tests] } {
+    unsupported "Target does not support record btrace pt."
+    return -1
+}
+
+if { [skip_btrace_ptw_tests] } {
+    unsupported "Hardware does not support ptwrite instructions."
+    return -1
+}
+
+# Test libipt version (must be >= 2.0.0).
+if {[require_libipt_version 2 0 0]} {
+    unsupported "Libipt doesn't support ptwrite decoding."
+    return -1
+}
+
+set opts {}
+
+if [info exists COMPILE] {
+    # make check RUNTESTFLAGS="gdb.btrace/ptwrite.exp COMPILE=1"
+    standard_testfile ptwrite.c
+    lappend opts debug
+} else {
+	if {[is_amd64_regs_target]} {
+		standard_testfile x86_64-ptwrite.S
+	} else {
+		unsupported "target architecture not supported"
+		return -1
+	}
+}
+
+if [prepare_for_testing "failed to prepare" $testfile $srcfile $opts] {
+    return -1
+}
+
+if { ![runto_main] } {
+    untested "failed to run to main"
+    return -1
+}
+
+# This needs to be after runto_main
+if { [skip_python_tests] } {
+    unsupported "Configuration doesn't support python scripting."
+    return -1
+}
+
+
+### 1. Default testrun
+
+# Setup recording
+gdb_test_no_output "set record instruction-history-size unlimited" "Default: set unlimited"
+gdb_test_no_output "record btrace pt" "Default: record btrace pt"
+gdb_test "next" ".*" "Default: first next"
+gdb_test "next" ".*" "Default: second next"
+
+# Test record instruction-history
+gdb_test "record instruction-history 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[42\\\]" \
+  ".*\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[43\\\].*" \
+  ] "Default: record instruction-history 1"
+
+gdb_test "record instruction-history /a 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  ".*\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+.*" \
+  ] "Default: record instruction-history /a 1"
+
+# Test function call history
+gdb_test "record function-call-history 1,4" [multi_line \
+  "1\tmain" \
+  "2\tptwrite64" \
+  "\t\t\\\[42\\\]" \
+  "3\tmain" \
+  "4\tptwrite32" \
+  "\t\t\\\[43\\\]" \
+  ] "Default: record function-call-history 1,4"
+
+gdb_test "record function-call-history /a 1,4" [multi_line \
+  "1\tmain" \
+  "2\tptwrite64" \
+  "3\tmain" \
+  "4\tptwrite32" \
+  ] "Default: record function-call-history /a 1,4"
+
+# Test payload printing during stepping
+gdb_test "record goto 10" "No such instruction\."
+gdb_test "record goto 9" ".*ptwrite64.* at .*ptwrite.c:23.*"
+gdb_test "stepi" ".*\\\[42\\\].*"
+gdb_test "reverse-stepi" ".*\\\[42\\\].*"
+gdb_test "continue" ".*\\\[42\\\].*\\\[43\\\].*"
+gdb_test "reverse-continue" ".*\\\[43\\\].*\\\[42\\\].*"
+
+# Test auxiliary type in python
+gdb_test_multiline "auxiliary type in python" \
+  "python" "" \
+  "h = gdb.current_recording().instruction_history" "" \
+  "for insn in h: print(insn)" "" \
+  "end" [multi_line \
+  ".*RecordAuxiliary.*" \
+  ".*RecordAuxiliary.*" \
+  ]
+
+
+### 2. Test listener registration
+### 2.1 Custom listener
+
+gdb_test_multiline "Custom: register listener in python" \
+  "python" "" \
+  "def my_listener(payload, ip):" "" \
+  "    if  payload == 66:" "" \
+  "        return \"payload: {0}, ip: {1:#x}\".format(payload, ip)" "" \
+  "    else:" "" \
+  "        return None" "" \
+  "import gdb.ptwrite" "" \
+  "gdb.ptwrite.register_listener(my_listener)" "" \
+  "end" ""
+
+gdb_test "record instruction-history 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[payload: 66, ip: $hex\\\]" \
+  ".*\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:.*" \
+  ] "Custom: record instruction-history 1"
+
+### 2.2 None as listener
+
+gdb_test_multiline "None: register listener in python" \
+  "python" "" \
+  "import gdb.ptwrite" "" \
+  "gdb.ptwrite.register_listener(None)" "" \
+  "end" ""
+
+gdb_test "record instruction-history 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:.*" \
+  "\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:.*" \
+  ] "None: record instruction-history 1"
+
+### 2.3 Lambdas as listener
+
+gdb_test_multiline "Lambdas: register listener in python" \
+  "python" "" \
+  "import gdb.ptwrite" "" \
+  "gdb.ptwrite.register_listener(lambda payload, ip: \"{}\".format(payload + 2))" "" \
+  "end" ""
+
+gdb_test "record instruction-history 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[68\\\]" \
+  ".*\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[69\\\].*" \
+  ] "Lambdas: record instruction-history 1"
+
+### 2.4 Functors as listener
+
+gdb_test_multiline "Functors: register listener in python" \
+  "python" "" \
+  "import gdb.ptwrite" "" \
+  "class foobar(object):" "" \
+  "    def __init__(self):" "" \
+  "        self.variable = 0" "" \
+  "    def __call__(self, payload, ip):" "" \
+  "        self.variable += 1" "" \
+  "        return \"{}, {}\".format(self.variable, payload)" "" \
+  "gdb.ptwrite.register_listener(foobar())" "" \
+  "end" ""
+
+gdb_test "record instruction-history 1" [multi_line \
+  ".*\[0-9\]+\t   $hex <ptwrite64\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[1, 66\\\]" \
+  ".*\[0-9\]+\t   $hex <ptwrite32\\+\[0-9\]+>:\tptwrite %\[a-z\]+" \
+  "\[0-9\]+\t   \\\[2, 67\\\].*" \
+  ] "Functors: record instruction-history 1"
diff --git a/gdb/testsuite/gdb.btrace/x86_64-ptwrite.S b/gdb/testsuite/gdb.btrace/x86_64-ptwrite.S
new file mode 100644
index 00000000000..0411870eca4
--- /dev/null
+++ b/gdb/testsuite/gdb.btrace/x86_64-ptwrite.S
@@ -0,0 +1,479 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+   This file has been generated using gcc version 8.1.1 20180502:
+   gcc -S -dA -g ptwrite.c -o x86_64-ptwrite.S.  */
+
+	.file	"ptwrite.c"
+	.text
+.Ltext0:
+	.globl	ptwrite64
+	.type	ptwrite64, @function
+ptwrite64:
+.LFB0:
+	.file 1 "ptwrite.c"
+	# ptwrite.c:22:1
+	.loc 1 22 1
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	pushq	%rbx
+	.cfi_offset 3, -24
+	movq	%rdi, -16(%rbp)
+	# ptwrite.c:23:3
+	.loc 1 23 3
+	movq	-16(%rbp), %rax
+	movq	%rax, %rbx
+#APP
+# 23 "ptwrite.c" 1
+	PTWRITE %rbx;
+# 0 "" 2
+	# ptwrite.c:24:1
+	.loc 1 24 1
+#NO_APP
+	nop
+	popq	%rbx
+	popq	%rbp
+	.cfi_def_cfa 7, 8
+# SUCC: EXIT [always] 
+	ret
+	.cfi_endproc
+.LFE0:
+	.size	ptwrite64, .-ptwrite64
+	.globl	ptwrite32
+	.type	ptwrite32, @function
+ptwrite32:
+.LFB1:
+	# ptwrite.c:28:1
+	.loc 1 28 1
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	pushq	%rbx
+	.cfi_offset 3, -24
+	movl	%edi, -12(%rbp)
+	# ptwrite.c:29:3
+	.loc 1 29 3
+	movl	-12(%rbp), %eax
+	movl	%eax, %ebx
+#APP
+# 29 "ptwrite.c" 1
+	PTWRITE %ebx;
+# 0 "" 2
+	# ptwrite.c:30:1
+	.loc 1 30 1
+#NO_APP
+	nop
+	popq	%rbx
+	popq	%rbp
+	.cfi_def_cfa 7, 8
+# SUCC: EXIT [always] 
+	ret
+	.cfi_endproc
+.LFE1:
+	.size	ptwrite32, .-ptwrite32
+	.globl	main
+	.type	main, @function
+main:
+.LFB2:
+	# ptwrite.c:34:1
+	.loc 1 34 1
+	.cfi_startproc
+# BLOCK 2 seq:0
+# PRED: ENTRY (FALLTHRU)
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	# ptwrite.c:36:3
+	.loc 1 36 3
+	movl	$66, %edi
+	call	ptwrite64
+	# ptwrite.c:37:3
+	.loc 1 37 3
+	movl	$67, %edi
+	call	ptwrite32
+	# ptwrite.c:39:10
+	.loc 1 39 10
+	movl	$0, %eax
+	# ptwrite.c:40:1
+	.loc 1 40 1
+	popq	%rbp
+	.cfi_def_cfa 7, 8
+# SUCC: EXIT [always] 
+	ret
+	.cfi_endproc
+.LFE2:
+	.size	main, .-main
+.Letext0:
+	.file 2 "/usr/include/bits/types.h"
+	.file 3 "/usr/include/bits/stdint-uintn.h"
+	.section	.debug_info,"",@progbits
+.Ldebug_info0:
+	.long	0x10f	# Length of Compilation Unit Info
+	.value	0x4	# DWARF version number
+	.long	.Ldebug_abbrev0	# Offset Into Abbrev. Section
+	.byte	0x8	# Pointer Size (in bytes)
+	.uleb128 0x1	# (DIE (0xb) DW_TAG_compile_unit)
+	.long	.LASF13	# DW_AT_producer: "GNU C17 8.1.1 20180502 (Red Hat 8.1.1-1) -mtune=generic -march=x86-64 -g"
+	.byte	0xc	# DW_AT_language
+	.long	.LASF14	# DW_AT_name: "ptwrite.c"
+	.long	.LASF15	# DW_AT_comp_dir: "gdb/gdb/testsuite/gdb.btrace"
+	.quad	.Ltext0	# DW_AT_low_pc
+	.quad	.Letext0-.Ltext0	# DW_AT_high_pc
+	.long	.Ldebug_line0	# DW_AT_stmt_list
+	.uleb128 0x2	# (DIE (0x2d) DW_TAG_base_type)
+	.byte	0x1	# DW_AT_byte_size
+	.byte	0x8	# DW_AT_encoding
+	.long	.LASF0	# DW_AT_name: "unsigned char"
+	.uleb128 0x2	# (DIE (0x34) DW_TAG_base_type)
+	.byte	0x2	# DW_AT_byte_size
+	.byte	0x7	# DW_AT_encoding
+	.long	.LASF1	# DW_AT_name: "short unsigned int"
+	.uleb128 0x2	# (DIE (0x3b) DW_TAG_base_type)
+	.byte	0x4	# DW_AT_byte_size
+	.byte	0x7	# DW_AT_encoding
+	.long	.LASF2	# DW_AT_name: "unsigned int"
+	.uleb128 0x2	# (DIE (0x42) DW_TAG_base_type)
+	.byte	0x8	# DW_AT_byte_size
+	.byte	0x7	# DW_AT_encoding
+	.long	.LASF3	# DW_AT_name: "long unsigned int"
+	.uleb128 0x2	# (DIE (0x49) DW_TAG_base_type)
+	.byte	0x1	# DW_AT_byte_size
+	.byte	0x6	# DW_AT_encoding
+	.long	.LASF4	# DW_AT_name: "signed char"
+	.uleb128 0x2	# (DIE (0x50) DW_TAG_base_type)
+	.byte	0x2	# DW_AT_byte_size
+	.byte	0x5	# DW_AT_encoding
+	.long	.LASF5	# DW_AT_name: "short int"
+	.uleb128 0x3	# (DIE (0x57) DW_TAG_base_type)
+	.byte	0x4	# DW_AT_byte_size
+	.byte	0x5	# DW_AT_encoding
+	.ascii "int\0"	# DW_AT_name
+	.uleb128 0x4	# (DIE (0x5e) DW_TAG_typedef)
+	.long	.LASF7	# DW_AT_name: "__uint32_t"
+	.byte	0x2	# DW_AT_decl_file (/usr/include/bits/types.h)
+	.byte	0x29	# DW_AT_decl_line
+	.byte	0x16	# DW_AT_decl_column
+	.long	0x3b	# DW_AT_type
+	.uleb128 0x2	# (DIE (0x6a) DW_TAG_base_type)
+	.byte	0x8	# DW_AT_byte_size
+	.byte	0x5	# DW_AT_encoding
+	.long	.LASF6	# DW_AT_name: "long int"
+	.uleb128 0x4	# (DIE (0x71) DW_TAG_typedef)
+	.long	.LASF8	# DW_AT_name: "__uint64_t"
+	.byte	0x2	# DW_AT_decl_file (/usr/include/bits/types.h)
+	.byte	0x2c	# DW_AT_decl_line
+	.byte	0x1b	# DW_AT_decl_column
+	.long	0x42	# DW_AT_type
+	.uleb128 0x2	# (DIE (0x7d) DW_TAG_base_type)
+	.byte	0x1	# DW_AT_byte_size
+	.byte	0x6	# DW_AT_encoding
+	.long	.LASF9	# DW_AT_name: "char"
+	.uleb128 0x4	# (DIE (0x84) DW_TAG_typedef)
+	.long	.LASF10	# DW_AT_name: "uint32_t"
+	.byte	0x3	# DW_AT_decl_file (/usr/include/bits/stdint-uintn.h)
+	.byte	0x1a	# DW_AT_decl_line
+	.byte	0x14	# DW_AT_decl_column
+	.long	0x5e	# DW_AT_type
+	.uleb128 0x4	# (DIE (0x90) DW_TAG_typedef)
+	.long	.LASF11	# DW_AT_name: "uint64_t"
+	.byte	0x3	# DW_AT_decl_file (/usr/include/bits/stdint-uintn.h)
+	.byte	0x1b	# DW_AT_decl_line
+	.byte	0x14	# DW_AT_decl_column
+	.long	0x71	# DW_AT_type
+	.uleb128 0x5	# (DIE (0x9c) DW_TAG_subprogram)
+			# DW_AT_external
+	.long	.LASF16	# DW_AT_name: "main"
+	.byte	0x1	# DW_AT_decl_file (ptwrite.c)
+	.byte	0x21	# DW_AT_decl_line
+	.byte	0x1	# DW_AT_decl_column
+			# DW_AT_prototyped
+	.long	0x57	# DW_AT_type
+	.quad	.LFB2	# DW_AT_low_pc
+	.quad	.LFE2-.LFB2	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_tail_call_sites
+	.uleb128 0x6	# (DIE (0xba) DW_TAG_subprogram)
+			# DW_AT_external
+	.long	.LASF17	# DW_AT_name: "ptwrite32"
+	.byte	0x1	# DW_AT_decl_file (ptwrite.c)
+	.byte	0x1b	# DW_AT_decl_line
+	.byte	0x1	# DW_AT_decl_column
+			# DW_AT_prototyped
+	.quad	.LFB1	# DW_AT_low_pc
+	.quad	.LFE1-.LFB1	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_call_sites
+	.long	0xe8	# DW_AT_sibling
+	.uleb128 0x7	# (DIE (0xd8) DW_TAG_formal_parameter)
+	.long	.LASF12	# DW_AT_name: "value"
+	.byte	0x1	# DW_AT_decl_file (ptwrite.c)
+	.byte	0x1b	# DW_AT_decl_line
+	.byte	0x15	# DW_AT_decl_column
+	.long	0x84	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 -28
+	.byte	0	# end of children of DIE 0xba
+	.uleb128 0x8	# (DIE (0xe8) DW_TAG_subprogram)
+			# DW_AT_external
+	.long	.LASF18	# DW_AT_name: "ptwrite64"
+	.byte	0x1	# DW_AT_decl_file (ptwrite.c)
+	.byte	0x15	# DW_AT_decl_line
+	.byte	0x1	# DW_AT_decl_column
+			# DW_AT_prototyped
+	.quad	.LFB0	# DW_AT_low_pc
+	.quad	.LFE0-.LFB0	# DW_AT_high_pc
+	.uleb128 0x1	# DW_AT_frame_base
+	.byte	0x9c	# DW_OP_call_frame_cfa
+			# DW_AT_GNU_all_call_sites
+	.uleb128 0x7	# (DIE (0x102) DW_TAG_formal_parameter)
+	.long	.LASF12	# DW_AT_name: "value"
+	.byte	0x1	# DW_AT_decl_file (ptwrite.c)
+	.byte	0x15	# DW_AT_decl_line
+	.byte	0x15	# DW_AT_decl_column
+	.long	0x90	# DW_AT_type
+	.uleb128 0x2	# DW_AT_location
+	.byte	0x91	# DW_OP_fbreg
+	.sleb128 -32
+	.byte	0	# end of children of DIE 0xe8
+	.byte	0	# end of children of DIE 0xb
+	.section	.debug_abbrev,"",@progbits
+.Ldebug_abbrev0:
+	.uleb128 0x1	# (abbrev code)
+	.uleb128 0x11	# (TAG: DW_TAG_compile_unit)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x25	# (DW_AT_producer)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x13	# (DW_AT_language)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x1b	# (DW_AT_comp_dir)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x10	# (DW_AT_stmt_list)
+	.uleb128 0x17	# (DW_FORM_sec_offset)
+	.byte	0
+	.byte	0
+	.uleb128 0x2	# (abbrev code)
+	.uleb128 0x24	# (TAG: DW_TAG_base_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3e	# (DW_AT_encoding)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.byte	0
+	.byte	0
+	.uleb128 0x3	# (abbrev code)
+	.uleb128 0x24	# (TAG: DW_TAG_base_type)
+	.byte	0	# DW_children_no
+	.uleb128 0xb	# (DW_AT_byte_size)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3e	# (DW_AT_encoding)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0x8	# (DW_FORM_string)
+	.byte	0
+	.byte	0
+	.uleb128 0x4	# (abbrev code)
+	.uleb128 0x16	# (TAG: DW_TAG_typedef)
+	.byte	0	# DW_children_no
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x39	# (DW_AT_decl_column)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x5	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0	# DW_children_no
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x39	# (DW_AT_decl_column)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2116	# (DW_AT_GNU_all_tail_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.byte	0
+	.byte	0
+	.uleb128 0x6	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x39	# (DW_AT_decl_column)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2117	# (DW_AT_GNU_all_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x1	# (DW_AT_sibling)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.byte	0
+	.byte	0
+	.uleb128 0x7	# (abbrev code)
+	.uleb128 0x5	# (TAG: DW_TAG_formal_parameter)
+	.byte	0	# DW_children_no
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x39	# (DW_AT_decl_column)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x49	# (DW_AT_type)
+	.uleb128 0x13	# (DW_FORM_ref4)
+	.uleb128 0x2	# (DW_AT_location)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.byte	0
+	.byte	0
+	.uleb128 0x8	# (abbrev code)
+	.uleb128 0x2e	# (TAG: DW_TAG_subprogram)
+	.byte	0x1	# DW_children_yes
+	.uleb128 0x3f	# (DW_AT_external)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x3	# (DW_AT_name)
+	.uleb128 0xe	# (DW_FORM_strp)
+	.uleb128 0x3a	# (DW_AT_decl_file)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x3b	# (DW_AT_decl_line)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x39	# (DW_AT_decl_column)
+	.uleb128 0xb	# (DW_FORM_data1)
+	.uleb128 0x27	# (DW_AT_prototyped)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.uleb128 0x11	# (DW_AT_low_pc)
+	.uleb128 0x1	# (DW_FORM_addr)
+	.uleb128 0x12	# (DW_AT_high_pc)
+	.uleb128 0x7	# (DW_FORM_data8)
+	.uleb128 0x40	# (DW_AT_frame_base)
+	.uleb128 0x18	# (DW_FORM_exprloc)
+	.uleb128 0x2117	# (DW_AT_GNU_all_call_sites)
+	.uleb128 0x19	# (DW_FORM_flag_present)
+	.byte	0
+	.byte	0
+	.byte	0
+	.section	.debug_aranges,"",@progbits
+	.long	0x2c	# Length of Address Ranges Info
+	.value	0x2	# DWARF aranges version
+	.long	.Ldebug_info0	# Offset of Compilation Unit Info
+	.byte	0x8	# Size of Address
+	.byte	0	# Size of Segment Descriptor
+	.value	0	# Pad to 16 byte boundary
+	.value	0
+	.quad	.Ltext0	# Address
+	.quad	.Letext0-.Ltext0	# Length
+	.quad	0
+	.quad	0
+	.section	.debug_line,"",@progbits
+.Ldebug_line0:
+	.section	.debug_str,"MS",@progbits,1
+.LASF2:
+	.string	"unsigned int"
+.LASF7:
+	.string	"__uint32_t"
+.LASF15:
+	.string	"gdb/gdb/testsuite/gdb.btrace"
+.LASF14:
+	.string	"ptwrite.c"
+.LASF18:
+	.string	"ptwrite64"
+.LASF3:
+	.string	"long unsigned int"
+.LASF11:
+	.string	"uint64_t"
+.LASF0:
+	.string	"unsigned char"
+.LASF16:
+	.string	"main"
+.LASF10:
+	.string	"uint32_t"
+.LASF6:
+	.string	"long int"
+.LASF8:
+	.string	"__uint64_t"
+.LASF13:
+	.string	"GNU C17 8.1.1 20180502 (Red Hat 8.1.1-1) -mtune=generic -march=x86-64 -g"
+.LASF1:
+	.string	"short unsigned int"
+.LASF4:
+	.string	"signed char"
+.LASF12:
+	.string	"value"
+.LASF5:
+	.string	"short int"
+.LASF17:
+	.string	"ptwrite32"
+.LASF9:
+	.string	"char"
+	.ident	"GCC: (GNU) 8.1.1 20180502 (Red Hat 8.1.1-1)"
+	.section	.note.GNU-stack,"",@progbits
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index d77fac5ae3a..04eed432e03 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -3457,6 +3457,88 @@  gdb_caching_proc skip_btrace_pt_tests {
     return $skip_btrace_tests
 }
 
+# Run a test on the target to see if it supports ptwrite instructions.
+# Return 0 if so, 1 if it does not.  Based on 'check_vmx_hw_available'
+# from the GCC testsuite.
+
+gdb_caching_proc skip_btrace_ptw_tests {
+    global srcdir subdir gdb_prompt inferior_exited_re
+    set me "skip_ptw_tests"
+
+    set src {
+	#include <stdbool.h>
+	#include <stdio.h>
+
+	bool
+	linux_supports_ptwrite ()
+	{
+	  static const char filename[]
+	      = "/sys/bus/event_source/devices/intel_pt/caps/ptwrite";
+	  FILE *file = fopen (filename, "r");
+
+	  if (file == NULL)
+	    return false;
+
+	  int status, found = fscanf (file, "%d", &status);
+	  fclose (file);
+
+	  if (found != 1)
+	    return false;
+
+	  return status == 1;
+	}
+
+	int
+	main ()
+	{
+	  if (linux_supports_ptwrite ())
+	    {
+	      asm volatile("PTWRITE %0;" : : "b"(0x42));
+	      return 1;
+	    };
+
+	  return 0;
+	}
+    }
+
+    if {![gdb_simple_compile $me $src executable]} {
+        return 1
+    }
+
+    # No error message, compilation succeeded so now run it via gdb.
+
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+    gdb_load "$obj"
+    gdb_run_cmd
+
+    gdb_expect {
+        -re ".*Illegal instruction.*${gdb_prompt} $" {
+            verbose -log "$me:  ptwrite support not detected."
+            set skip_ptw_tests 1
+        }
+        -re ".*$inferior_exited_re normally.*${gdb_prompt} $" {
+            verbose -log "$me:  ptwrite support not detected."
+            set skip_ptw_tests 1
+        }
+        -re ".*exited with code 01.*${gdb_prompt} $" {
+            verbose -log "$me:  ptwrite support detected."
+            set skip_ptw_tests 0
+        }
+        default {
+            warning "\n$me:  default case taken."
+            set skip_ptw_tests 1
+        }
+    }
+
+    gdb_exit
+    remote_file build delete $obj
+
+    verbose "$me:  returning $skip_ptw_tests" 2
+    return $skip_ptw_tests
+}
+
 # Run a test on the target to see if we have a minimum libipt version.
 # Return 0 if so, 1 if it does not.