diff options
Diffstat (limited to 'gdb/nat/linux-btrace.c')
-rw-r--r-- | gdb/nat/linux-btrace.c | 377 |
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.")); |