aboutsummaryrefslogtreecommitdiff
path: root/gdb/nat/linux-btrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/nat/linux-btrace.c')
-rw-r--r--gdb/nat/linux-btrace.c377
1 files changed, 376 insertions, 1 deletions
diff --git a/gdb/nat/linux-btrace.c b/gdb/nat/linux-btrace.c
index cd7f3d1..ddd79a3 100644
--- a/gdb/nat/linux-btrace.c
+++ b/gdb/nat/linux-btrace.c
@@ -106,6 +106,87 @@ perf_event_new_data (const struct perf_event_buffer *pev)
This is the same as the size of a pointer for the inferior process
except when a 32-bit inferior is running on a 64-bit OS. */
+/* Copy the last SIZE bytes from PEV ending at DATA_HEAD and return a pointer
+ to the memory holding the copy.
+ The caller is responsible for freeing the memory. */
+
+static gdb_byte *
+perf_event_read (const struct perf_event_buffer *pev, unsigned long data_head,
+ unsigned long size)
+{
+ const gdb_byte *begin, *end, *start, *stop;
+ gdb_byte *buffer;
+ unsigned long data_tail, buffer_size;
+
+ if (size == 0)
+ return NULL;
+
+ gdb_assert (size <= data_head);
+ data_tail = data_head - size;
+
+ buffer_size = pev->size;
+ begin = pev->mem;
+ start = begin + data_tail % buffer_size;
+ stop = begin + data_head % buffer_size;
+
+ buffer = xmalloc (size);
+
+ if (start < stop)
+ memcpy (buffer, start, stop - start);
+ else
+ {
+ end = begin + buffer_size;
+
+ memcpy (buffer, start, end - start);
+ memcpy (buffer + (end - start), begin, stop - begin);
+ }
+
+ return buffer;
+}
+
+/* Copy the perf event buffer data from PEV.
+ Store a pointer to the copy into DATA and its size in SIZE. */
+
+static void
+perf_event_read_all (struct perf_event_buffer *pev, gdb_byte **data,
+ unsigned long *psize)
+{
+ unsigned long data_head, size;
+
+ data_head = *pev->data_head;
+
+ size = pev->size;
+ if (data_head < size)
+ size = data_head;
+
+ *data = perf_event_read (pev, data_head, size);
+ *psize = size;
+
+ pev->last_head = data_head;
+}
+
+/* Determine the event type.
+ Returns zero on success and fills in TYPE; returns -1 otherwise. */
+
+static int
+perf_event_pt_event_type (int *type)
+{
+ FILE *file;
+ int found;
+
+ file = fopen ("/sys/bus/event_source/devices/intel_pt/type", "r");
+ if (file == NULL)
+ return -1;
+
+ found = fscanf (file, "%d", type);
+
+ fclose (file);
+
+ if (found == 1)
+ return 0;
+ return -1;
+}
+
static int
linux_determine_kernel_ptr_bits (void)
{
@@ -358,6 +439,93 @@ kernel_supports_bts (void)
}
}
+/* Check whether the kernel supports Intel(R) Processor Trace. */
+
+static int
+kernel_supports_pt (void)
+{
+ struct perf_event_attr attr;
+ pid_t child, pid;
+ int status, file, type;
+
+ errno = 0;
+ child = fork ();
+ switch (child)
+ {
+ case -1:
+ warning (_("test pt: cannot fork: %s."), strerror (errno));
+ return 0;
+
+ case 0:
+ status = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+ if (status != 0)
+ {
+ warning (_("test pt: cannot PTRACE_TRACEME: %s."),
+ strerror (errno));
+ _exit (1);
+ }
+
+ status = raise (SIGTRAP);
+ if (status != 0)
+ {
+ warning (_("test pt: cannot raise SIGTRAP: %s."),
+ strerror (errno));
+ _exit (1);
+ }
+
+ _exit (1);
+
+ default:
+ pid = waitpid (child, &status, 0);
+ if (pid != child)
+ {
+ warning (_("test pt: bad pid %ld, error: %s."),
+ (long) pid, strerror (errno));
+ return 0;
+ }
+
+ if (!WIFSTOPPED (status))
+ {
+ warning (_("test pt: expected stop. status: %d."),
+ status);
+ return 0;
+ }
+
+ status = perf_event_pt_event_type (&type);
+ if (status != 0)
+ file = -1;
+ else
+ {
+ memset (&attr, 0, sizeof (attr));
+
+ attr.size = sizeof (attr);
+ attr.type = type;
+ attr.exclude_kernel = 1;
+ attr.exclude_hv = 1;
+ attr.exclude_idle = 1;
+
+ file = syscall (SYS_perf_event_open, &attr, child, -1, -1, 0);
+ if (file >= 0)
+ close (file);
+ }
+
+ kill (child, SIGKILL);
+ ptrace (PTRACE_KILL, child, NULL, NULL);
+
+ pid = waitpid (child, &status, 0);
+ if (pid != child)
+ {
+ warning (_("test pt: bad pid %ld, error: %s."),
+ (long) pid, strerror (errno));
+ if (!WIFSIGNALED (status))
+ warning (_("test pt: expected killed. status: %d."),
+ status);
+ }
+
+ return (file >= 0);
+ }
+}
+
/* Check whether an Intel cpu supports BTS. */
static int
@@ -428,6 +596,24 @@ linux_supports_bts (void)
return cached > 0;
}
+/* Check whether the linux target supports Intel(R) Processor Trace. */
+
+static int
+linux_supports_pt (void)
+{
+ static int cached;
+
+ if (cached == 0)
+ {
+ if (!kernel_supports_pt ())
+ cached = -1;
+ else
+ cached = 1;
+ }
+
+ return cached > 0;
+}
+
/* See linux-btrace.h. */
int
@@ -440,6 +626,9 @@ linux_supports_btrace (struct target_ops *ops, enum btrace_format format)
case BTRACE_FORMAT_BTS:
return linux_supports_bts ();
+
+ case BTRACE_FORMAT_PT:
+ return linux_supports_pt ();
}
internal_error (__FILE__, __LINE__, _("Unknown branch trace format"));
@@ -482,7 +671,7 @@ linux_enable_bts (ptid_t ptid, const struct btrace_config_bts *conf)
errno = 0;
bts->file = syscall (SYS_perf_event_open, &bts->attr, pid, -1, -1, 0);
if (bts->file < 0)
- goto err;
+ goto err_out;
/* Convert the requested size in bytes to pages (rounding up). */
pages = (((unsigned long long) conf->size) + PAGE_SIZE - 1) / PAGE_SIZE;
@@ -531,11 +720,127 @@ linux_enable_bts (ptid_t ptid, const struct btrace_config_bts *conf)
/* We were not able to allocate any buffer. */
close (bts->file);
+ err_out:
+ xfree (tinfo);
+ return NULL;
+}
+
+#if defined (PERF_ATTR_SIZE_VER5)
+
+/* Enable branch tracing in Intel(R) Processor Trace format. */
+
+static struct btrace_target_info *
+linux_enable_pt (ptid_t ptid, const struct btrace_config_pt *conf)
+{
+ struct perf_event_mmap_page *header;
+ struct btrace_target_info *tinfo;
+ struct btrace_tinfo_pt *pt;
+ unsigned long long pages, size;
+ int pid, pg, errcode, type;
+
+ if (conf->size == 0)
+ return NULL;
+
+ errcode = perf_event_pt_event_type (&type);
+ if (errcode != 0)
+ return NULL;
+
+ pid = ptid_get_lwp (ptid);
+ if (pid == 0)
+ pid = ptid_get_pid (ptid);
+
+ tinfo = xzalloc (sizeof (*tinfo));
+ tinfo->ptid = ptid;
+ tinfo->ptr_bits = 0;
+
+ tinfo->conf.format = BTRACE_FORMAT_PT;
+ pt = &tinfo->variant.pt;
+
+ pt->attr.size = sizeof (pt->attr);
+ pt->attr.type = type;
+
+ pt->attr.exclude_kernel = 1;
+ pt->attr.exclude_hv = 1;
+ pt->attr.exclude_idle = 1;
+
+ errno = 0;
+ pt->file = syscall (SYS_perf_event_open, &pt->attr, pid, -1, -1, 0);
+ if (pt->file < 0)
+ goto err;
+
+ /* Allocate the configuration page. */
+ header = mmap (NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+ pt->file, 0);
+ if (header == MAP_FAILED)
+ goto err_file;
+
+ header->aux_offset = header->data_offset + header->data_size;
+
+ /* Convert the requested size in bytes to pages (rounding up). */
+ pages = (((unsigned long long) conf->size) + PAGE_SIZE - 1) / PAGE_SIZE;
+ /* We need at least one page. */
+ if (pages == 0)
+ pages = 1;
+
+ /* The buffer size can be requested in powers of two pages. Adjust PAGES
+ to the next power of two. */
+ for (pg = 0; pages != (1u << pg); ++pg)
+ if ((pages & (1u << pg)) != 0)
+ pages += (1u << pg);
+
+ /* We try to allocate the requested size.
+ If that fails, try to get as much as we can. */
+ for (; pages > 0; pages >>= 1)
+ {
+ size_t length;
+
+ size = pages * PAGE_SIZE;
+ length = size;
+
+ /* Check for overflows. */
+ if ((unsigned long long) length < size)
+ continue;
+
+ header->aux_size = size;
+
+ pt->pt.mem = mmap (NULL, length, PROT_READ, MAP_SHARED, pt->file,
+ header->aux_offset);
+ if (pt->pt.mem != MAP_FAILED)
+ break;
+ }
+
+ if (pages == 0)
+ goto err_conf;
+
+ pt->header = header;
+ pt->pt.size = size;
+ pt->pt.data_head = &header->aux_head;
+
+ tinfo->conf.pt.size = size;
+ return tinfo;
+
+ err_conf:
+ munmap((void *) header, PAGE_SIZE);
+
+ err_file:
+ close (pt->file);
+
err:
xfree (tinfo);
return NULL;
}
+#else /* !defined (PERF_ATTR_SIZE_VER5) */
+
+static struct btrace_target_info *
+linux_enable_pt (ptid_t ptid, const struct btrace_config_pt *conf)
+{
+ errno = EOPNOTSUPP;
+ return NULL;
+}
+
+#endif /* !defined (PERF_ATTR_SIZE_VER5) */
+
/* See linux-btrace.h. */
struct btrace_target_info *
@@ -552,6 +857,10 @@ linux_enable_btrace (ptid_t ptid, const struct btrace_config *conf)
case BTRACE_FORMAT_BTS:
tinfo = linux_enable_bts (ptid, &conf->bts);
break;
+
+ case BTRACE_FORMAT_PT:
+ tinfo = linux_enable_pt (ptid, &conf->pt);
+ break;
}
return tinfo;
@@ -568,6 +877,18 @@ linux_disable_bts (struct btrace_tinfo_bts *tinfo)
return BTRACE_ERR_NONE;
}
+/* Disable Intel(R) Processor Trace tracing. */
+
+static enum btrace_error
+linux_disable_pt (struct btrace_tinfo_pt *tinfo)
+{
+ munmap((void *) tinfo->pt.mem, tinfo->pt.size);
+ munmap((void *) tinfo->header, PAGE_SIZE);
+ close (tinfo->file);
+
+ return BTRACE_ERR_NONE;
+}
+
/* See linux-btrace.h. */
enum btrace_error
@@ -584,6 +905,10 @@ linux_disable_btrace (struct btrace_target_info *tinfo)
case BTRACE_FORMAT_BTS:
errcode = linux_disable_bts (&tinfo->variant.bts);
break;
+
+ case BTRACE_FORMAT_PT:
+ errcode = linux_disable_pt (&tinfo->variant.pt);
+ break;
}
if (errcode == BTRACE_ERR_NONE)
@@ -681,6 +1006,48 @@ linux_read_bts (struct btrace_data_bts *btrace,
return BTRACE_ERR_NONE;
}
+/* Fill in the Intel(R) Processor Trace configuration information. */
+
+static void
+linux_fill_btrace_pt_config (struct btrace_data_pt_config *conf)
+{
+ conf->cpu = btrace_this_cpu ();
+}
+
+/* Read branch trace data in Intel(R) Processor Trace format for the thread
+ given by TINFO into BTRACE using the TYPE reading method. */
+
+static enum btrace_error
+linux_read_pt (struct btrace_data_pt *btrace,
+ struct btrace_target_info *tinfo,
+ enum btrace_read_type type)
+{
+ struct perf_event_buffer *pt;
+
+ pt = &tinfo->variant.pt.pt;
+
+ linux_fill_btrace_pt_config (&btrace->config);
+
+ switch (type)
+ {
+ case BTRACE_READ_DELTA:
+ /* We don't support delta reads. The data head (i.e. aux_head) wraps
+ around to stay inside the aux buffer. */
+ return BTRACE_ERR_NOT_SUPPORTED;
+
+ case BTRACE_READ_NEW:
+ if (!perf_event_new_data (pt))
+ return BTRACE_ERR_NONE;
+
+ /* Fall through. */
+ case BTRACE_READ_ALL:
+ perf_event_read_all (pt, &btrace->data, &btrace->size);
+ return BTRACE_ERR_NONE;
+ }
+
+ internal_error (__FILE__, __LINE__, _("Unkown btrace read type."));
+}
+
/* See linux-btrace.h. */
enum btrace_error
@@ -699,6 +1066,14 @@ linux_read_btrace (struct btrace_data *btrace,
btrace->variant.bts.blocks = NULL;
return linux_read_bts (&btrace->variant.bts, tinfo, type);
+
+ case BTRACE_FORMAT_PT:
+ /* We read btrace in Intel(R) Processor Trace format. */
+ btrace->format = BTRACE_FORMAT_PT;
+ btrace->variant.pt.data = NULL;
+ btrace->variant.pt.size = 0;
+
+ return linux_read_pt (&btrace->variant.pt, tinfo, type);
}
internal_error (__FILE__, __LINE__, _("Unkown branch trace format."));