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

Message ID 20210616074205.1129553-13-felix.willgerodt@intel.com
State New
Headers show
Series
  • Extensions for PTWRITE
Related show

Commit Message

Weimin Pan via Gdb-patches June 16, 2021, 7:42 a.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                       | 134 ++++++
 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, 986 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

Weimin Pan via Gdb-patches June 17, 2021, 7 a.m. | #1
> Date: Wed, 16 Jun 2021 09:42:05 +0200

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

> 

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


Here and elsewhere in the patch, you use @group..@end group in a
sub-optimal manner.  That directive is intended to allow TeX to break
long @example stretches into several parts that can be printed on
different pages.  That's because by default each @example will be
typeset entirely on a single page (leaving empty space before and
after as needed) -- TeX won't break any @example between pages.  So
the correct use of @group is to subdivide the @example into separate
independent groups and mark each one of them with @group.  For
example, the above could be rewritten as

  @smallexample
  @group
  void
  ptwrite64 (unsigned long long value)
  @{
    __builtin_ia32_ptwrite64 (value);
  @}
  @end group
  
  @group
  int
  main (void)
  @{
    ptwrite64 (0x42);
    return 0; /* break here.  */
  @}
  @end group
  @end smallexample

That is, you want to have each function on one page, but there can be
a page break between the functions.

> +@smallexample

> +@group

> +(gdb) python-interactive

> +>>> class my_listener(object):

> +...    def __init__(self):

> +...        self.var = 0

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

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

> +...            self.var += 1

> +...            return "counter: @{@}, ip: @{:#x@}".format(self.var, 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)

> +          [counter: 1, ip: 0x401156]

> +63    task(void*)

> +64    ptwrite32(unsigned int)

> +          [counter: 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


Likewise here: the Python code would be one @group, and then each GDB
command that follows, with its results, would be a separate @group.

Otherwise, the documentation part is OK.  Since the changes I request
are mechanical, you don't need to post the documentation part for an
additional review, provided that you make those changes in the final
commit.

Thanks.
Weimin Pan via Gdb-patches June 17, 2021, 11:51 a.m. | #2
>Likewise here: the Python code would be one @group, and then each GDB

>command that follows, with its results, would be a separate @group.

>

>Otherwise, the documentation part is OK.  Since the changes I request

>are mechanical, you don't need to post the documentation part for an

>additional review, provided that you make those changes in the final

>commit.

>

>Thanks.


Thanks, I changed it in my local version as you proposed. I will only repost once
there have been more comments.

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
Weimin Pan via Gdb-patches Aug. 13, 2021, 1:36 p.m. | #3
Thanks, Felix,

>+	case ptev_ptwrite:

>+	  {

>+	    uint64_t *ip = nullptr;

>+	    gdb::unique_xmalloc_ptr<char> ptw_string = nullptr;

>+	    struct btrace_insn ptw_insn;

>+	    btrace_insn_flags flags;


We're meanwhile declaring variables where they are initialized and used.  The rest of the code has not been updated but new code should follow this.

>+

>+	    /* 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;


This isn't necessary; libipt will fill in the IP.  It suffices to check for EVENT.IP_SUPPRESSED.

>+

>+	    if (btinfo->ptw_callback_fun != nullptr)


This is probably the first check we should do, i.e. if it is nullptr, break right at the beginning.

>+	      ptw_string = btinfo->ptw_callback_fun (

>+					  &event.variant.ptwrite.payload, ip,

>+					  btinfo->ptw_listener);


The indentation looks a bit odd.  You may break before '= btinfo->...'  if the line is getting too long, otherwise.

>+

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


We should probably start by memset()ing ptw_insn to all-zero.

>+If an inferior uses the instruction, @value{GDBN} inserts the raw payload value


Maybe add 'by default' to stress that this behavior can be overwritten, which is documented below.

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

>+}


We shouldn't need to check the patch version.  See comments on a previous patch regarding feature checking.  We can probably combine it with skip_btrace_ptw_tests.

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


You may use with_test_prefix to add the "Default: " prefix to the test output.  This groups tests and makes it a bit more readable IMHO.  For the above group, the first two wouldn't need any additional test name string.  And if you combined the latter two into "next 2", it wouldn't either.

>+# 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\\\].*"


Between [42] and [43] we wouldn't get any other output we need to ignore, would we?  There's multi_line to capture multiple lines of output.

>+# 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.*" \

>+  ]


Could we inspect the auxiliary records to see if we get the correct strings?  Also, it would be nice to print the surrounding instructions instead of ignoring all the output.

If we just printed the instruction_history, wouldn't we get output very similar to the CLI?


>+### 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\]+>:.*" \


What output do we need to ignore here at the beginning of every other line?


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


See above comments.  I think we should simply record a single ptwrite inside main() and check that GDB can decode the trace without errors - and check 'info record' that we actually decoded something.

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

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..3fe5b80e01b 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,136 @@  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 @code{PTWRITE} instruction.  @code{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         [42]
+14         0x0000000000400751 <ptwrite64+21>:	mov    -0x8(%rbp),%rbx
+(gdb) record function-call-history
+1       main
+2       ptwrite64
+                [42]
+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.var = 0
+...    def __call__(self, payload, ip):
+...        if gdb.selected_thread().global_num == 1:
+...            self.var += 1
+...            return "counter: @{@}, ip: @{:#x@}".format(self.var, 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)
+          [counter: 1, ip: 0x401156]
+63    task(void*)
+64    ptwrite32(unsigned int)
+          [counter: 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 @value{GDBN} 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 a060b1767e5..8ac16f83849 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.