#include "common-regcache.h"
#include "gdb_wait.h"
#include "x86-cpuid.h"
+#include "filestuff.h"
+
+#include <inttypes.h>
#ifdef HAVE_SYS_SYSCALL_H
#include <sys/syscall.h>
#endif
#if HAVE_LINUX_PERF_EVENT_H && defined(SYS_perf_event_open)
-
-#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/user.h>
-#include <sys/ptrace.h>
+#include "nat/gdb_ptrace.h"
#include <sys/types.h>
#include <signal.h>
struct perf_event_bts bts;
};
-/* Get the perf_event header. */
+/* Identify the cpu we're running on. */
+static struct btrace_cpu
+btrace_this_cpu (void)
+{
+ struct btrace_cpu cpu;
+ unsigned int eax, ebx, ecx, edx;
+ int ok;
+
+ memset (&cpu, 0, sizeof (cpu));
+
+ ok = x86_cpuid (0, &eax, &ebx, &ecx, &edx);
+ if (ok != 0)
+ {
+ if (ebx == signature_INTEL_ebx && ecx == signature_INTEL_ecx
+ && edx == signature_INTEL_edx)
+ {
+ unsigned int cpuid, ignore;
+
+ ok = x86_cpuid (1, &cpuid, &ignore, &ignore, &ignore);
+ if (ok != 0)
+ {
+ cpu.vendor = CV_INTEL;
+
+ cpu.family = (cpuid >> 8) & 0xf;
+ cpu.model = (cpuid >> 4) & 0xf;
-static inline volatile struct perf_event_mmap_page *
-perf_event_header (struct btrace_target_info* tinfo)
+ if (cpu.family == 0x6)
+ cpu.model += (cpuid >> 12) & 0xf0;
+ }
+ }
+ }
+
+ return cpu;
+}
+
+/* Return non-zero if there is new data in PEVENT; zero otherwise. */
+
+static int
+perf_event_new_data (const struct perf_event_buffer *pev)
{
- return tinfo->buffer;
+ return *pev->data_head != pev->last_head;
}
-/* Get the size of the perf_event mmap buffer. */
+/* Try to determine the size of a pointer in bits for the OS.
+
+ 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. */
-static inline size_t
-perf_event_mmap_size (const struct btrace_target_info *tinfo)
+/* 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, __u64 data_head,
+ size_t size)
{
- /* The branch trace buffer is preceded by a configuration page. */
- return (tinfo->size + 1) * PAGE_SIZE;
+ const gdb_byte *begin, *end, *start, *stop;
+ gdb_byte *buffer;
+ size_t buffer_size;
+ __u64 data_tail;
+
+ 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 = (gdb_byte *) 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;
}
-/* Get the size of the perf_event buffer. */
+/* Copy the perf event buffer data from PEV.
+ Store a pointer to the copy into DATA and its size in SIZE. */
-static inline size_t
-perf_event_buffer_size (struct btrace_target_info* tinfo)
+static void
+perf_event_read_all (struct perf_event_buffer *pev, gdb_byte **data,
+ size_t *psize)
{
- return tinfo->size * PAGE_SIZE;
+ size_t size;
+ __u64 data_head;
+
+ data_head = *pev->data_head;
+
+ size = pev->size;
+ if (data_head < size)
+ size = (size_t) data_head;
+
+ *data = perf_event_read (pev, data_head, size);
+ *psize = size;
+
+ pev->last_head = data_head;
}
-/* Get the start address of the perf_event buffer. */
+/* Determine the event type.
+ Returns zero on success and fills in TYPE; returns -1 otherwise. */
-static inline const uint8_t *
-perf_event_buffer_begin (struct btrace_target_info* tinfo)
+static int
+perf_event_pt_event_type (int *type)
{
- return ((const uint8_t *) tinfo->buffer) + PAGE_SIZE;
+ 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;
}
-/* Get the end address of the perf_event buffer. */
+/* Try to determine the start address of the Linux kernel. */
-static inline const uint8_t *
-perf_event_buffer_end (struct btrace_target_info* tinfo)
+static uint64_t
+linux_determine_kernel_start (void)
{
- return perf_event_buffer_begin (tinfo) + perf_event_buffer_size (tinfo);
+ static uint64_t kernel_start;
+ static int cached;
+ FILE *file;
+
+ if (cached != 0)
+ return kernel_start;
+
+ cached = 1;
+
+ file = gdb_fopen_cloexec ("/proc/kallsyms", "r");
+ if (file == NULL)
+ return kernel_start;
+
+ while (!feof (file))
+ {
+ char buffer[1024], symbol[8], *line;
+ uint64_t addr;
+ int match;
+
+ line = fgets (buffer, sizeof (buffer), file);
+ if (line == NULL)
+ break;
+
+ match = sscanf (line, "%" SCNx64 " %*[tT] %7s", &addr, symbol);
+ if (match != 2)
+ continue;
+
+ if (strcmp (symbol, "_text") == 0)
+ {
+ kernel_start = addr;
+ break;
+ }
+ }
+
+ fclose (file);
+
+ return kernel_start;
}
/* Check whether an address is in the kernel. */
static inline int
-perf_event_is_kernel_addr (const struct btrace_target_info *tinfo,
- uint64_t addr)
+perf_event_is_kernel_addr (uint64_t addr)
{
- uint64_t mask;
+ uint64_t kernel_start;
- /* If we don't know the size of a pointer, we can't check. Let's assume it's
- not a kernel address in this case. */
- if (tinfo->ptr_bits == 0)
- return 0;
+ kernel_start = linux_determine_kernel_start ();
+ if (kernel_start != 0ull)
+ return (addr >= kernel_start);
- /* A bit mask for the most significant bit in an address. */
- mask = (uint64_t) 1 << (tinfo->ptr_bits - 1);
-
- /* Check whether the most significant bit in the address is set. */
- return (addr & mask) != 0;
+ /* If we don't know the kernel's start address, let's check the most
+ significant bit. This will work at least for 64-bit kernels. */
+ return ((addr & (1ull << 63)) != 0);
}
/* Check whether a perf event record should be skipped. */
static inline int
-perf_event_skip_record (const struct btrace_target_info *tinfo,
- const struct perf_event_bts *bts)
+perf_event_skip_bts_record (const struct perf_event_bts *bts)
{
/* The hardware may report branches from kernel into user space. Branches
from user into kernel space will be suppressed. We filter the former to
provide a consistent branch trace excluding kernel. */
- return perf_event_is_kernel_addr (tinfo, bts->from);
+ return perf_event_is_kernel_addr (bts->from);
}
/* Perform a few consistency checks on a perf event sample record. This is
break;
}
- if (perf_event_skip_record (tinfo, &psample->bts))
+ if (perf_event_skip_bts_record (&psample->bts))
continue;
/* We found a valid sample, so we can complete the current block. */
return btrace;
}
-/* Check whether the kernel supports branch tracing. */
+/* Check whether the kernel supports BTS. */
static int
-kernel_supports_btrace (void)
+kernel_supports_bts (void)
{
struct perf_event_attr attr;
pid_t child, pid;
switch (child)
{
case -1:
- warning (_("test branch tracing: cannot fork: %s."), strerror (errno));
+ warning (_("test bts: cannot fork: %s."), safe_strerror (errno));
return 0;
case 0:
status = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
if (status != 0)
{
- warning (_("test branch tracing: cannot PTRACE_TRACEME: %s."),
- strerror (errno));
+ warning (_("test bts: cannot PTRACE_TRACEME: %s."),
+ safe_strerror (errno));
_exit (1);
}
status = raise (SIGTRAP);
if (status != 0)
{
- warning (_("test branch tracing: cannot raise SIGTRAP: %s."),
- strerror (errno));
+ warning (_("test bts: cannot raise SIGTRAP: %s."),
+ safe_strerror (errno));
_exit (1);
}
pid = waitpid (child, &status, 0);
if (pid != child)
{
- warning (_("test branch tracing: bad pid %ld, error: %s."),
- (long) pid, strerror (errno));
+ warning (_("test bts: bad pid %ld, error: %s."),
+ (long) pid, safe_strerror (errno));
return 0;
}
if (!WIFSTOPPED (status))
{
- warning (_("test branch tracing: expected stop. status: %d."),
+ warning (_("test bts: expected stop. status: %d."),
status);
return 0;
}
pid = waitpid (child, &status, 0);
if (pid != child)
{
- warning (_("test branch tracing: bad pid %ld, error: %s."),
- (long) pid, strerror (errno));
+ warning (_("test bts: bad pid %ld, error: %s."),
+ (long) pid, safe_strerror (errno));
if (!WIFSIGNALED (status))
- warning (_("test branch tracing: expected killed. status: %d."),
+ warning (_("test bts: expected killed. status: %d."),
status);
}
}
}
-/* Check whether an Intel cpu supports branch tracing. */
+/* Check whether the kernel supports Intel(R) Processor Trace. */
static int
-intel_supports_btrace (void)
+kernel_supports_pt (void)
{
- unsigned int cpuid, model, family;
+ struct perf_event_attr attr;
+ pid_t child, pid;
+ int status, file, type;
- if (!x86_cpuid (1, &cpuid, NULL, NULL, NULL))
- return 0;
+ errno = 0;
+ child = fork ();
+ switch (child)
+ {
+ case -1:
+ warning (_("test pt: cannot fork: %s."), safe_strerror (errno));
+ return 0;
- family = (cpuid >> 8) & 0xf;
- model = (cpuid >> 4) & 0xf;
+ case 0:
+ status = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
+ if (status != 0)
+ {
+ warning (_("test pt: cannot PTRACE_TRACEME: %s."),
+ safe_strerror (errno));
+ _exit (1);
+ }
- switch (family)
+ status = raise (SIGTRAP);
+ if (status != 0)
+ {
+ warning (_("test pt: cannot raise SIGTRAP: %s."),
+ safe_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, safe_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, safe_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
+intel_supports_bts (const struct btrace_cpu *cpu)
+{
+ switch (cpu->family)
{
case 0x6:
- model += (cpuid >> 12) & 0xf0;
-
- switch (model)
+ switch (cpu->model)
{
case 0x1a: /* Nehalem */
case 0x1f:
return 1;
}
-/* Check whether the cpu supports branch tracing. */
+/* Check whether the cpu supports BTS. */
static int
-cpu_supports_btrace (void)
+cpu_supports_bts (void)
{
- unsigned int ebx, ecx, edx;
+ struct btrace_cpu cpu;
- if (!x86_cpuid (0, NULL, &ebx, &ecx, &edx))
- return 0;
-
- if (ebx == signature_INTEL_ebx && ecx == signature_INTEL_ecx
- && edx == signature_INTEL_edx)
- return intel_supports_btrace ();
+ cpu = btrace_this_cpu ();
+ switch (cpu.vendor)
+ {
+ default:
+ /* Don't know about others. Let's assume they do. */
+ return 1;
- /* Don't know about others. Let's assume they do. */
- return 1;
+ case CV_INTEL:
+ return intel_supports_bts (&cpu);
+ }
}
-/* See linux-btrace.h. */
+/* Check whether the linux target supports BTS. */
-int
-linux_supports_btrace (struct target_ops *ops)
+static int
+linux_supports_bts (void)
{
static int cached;
if (cached == 0)
{
- if (!kernel_supports_btrace ())
+ if (!kernel_supports_bts ())
cached = -1;
- else if (!cpu_supports_btrace ())
+ else if (!cpu_supports_bts ())
+ cached = -1;
+ else
+ cached = 1;
+ }
+
+ 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;
/* See linux-btrace.h. */
-struct btrace_target_info *
-linux_enable_btrace (ptid_t ptid)
+int
+linux_supports_btrace (struct target_ops *ops, enum btrace_format format)
+{
+ switch (format)
+ {
+ case BTRACE_FORMAT_NONE:
+ return 0;
+
+ case BTRACE_FORMAT_BTS:
+ return linux_supports_bts ();
+
+ case BTRACE_FORMAT_PT:
+ return linux_supports_pt ();
+ }
+
+ internal_error (__FILE__, __LINE__, _("Unknown branch trace format"));
+}
+
+/* Enable branch tracing in BTS format. */
+
+static struct btrace_target_info *
+linux_enable_bts (ptid_t ptid, const struct btrace_config_bts *conf)
{
+ struct perf_event_mmap_page *header;
struct btrace_target_info *tinfo;
+ struct btrace_tinfo_bts *bts;
+ size_t size, pages;
+ __u64 data_offset;
int pid, pg;
- tinfo = xzalloc (sizeof (*tinfo));
+ tinfo = XCNEW (struct btrace_target_info);
tinfo->ptid = ptid;
- tinfo->attr.size = sizeof (tinfo->attr);
- tinfo->attr.type = PERF_TYPE_HARDWARE;
- tinfo->attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
- tinfo->attr.sample_period = 1;
+ tinfo->conf.format = BTRACE_FORMAT_BTS;
+ bts = &tinfo->variant.bts;
+
+ bts->attr.size = sizeof (bts->attr);
+ bts->attr.type = PERF_TYPE_HARDWARE;
+ bts->attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+ bts->attr.sample_period = 1;
/* We sample from and to address. */
- tinfo->attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR;
+ bts->attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR;
+
+ bts->attr.exclude_kernel = 1;
+ bts->attr.exclude_hv = 1;
+ bts->attr.exclude_idle = 1;
+
+ pid = ptid_get_lwp (ptid);
+ if (pid == 0)
+ pid = ptid_get_pid (ptid);
+
+ errno = 0;
+ bts->file = syscall (SYS_perf_event_open, &bts->attr, pid, -1, -1, 0);
+ if (bts->file < 0)
+ goto err_out;
+
+ /* Convert the requested size in bytes to pages (rounding up). */
+ pages = ((size_t) conf->size / PAGE_SIZE
+ + ((conf->size % PAGE_SIZE) == 0 ? 0 : 1));
+ /* 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 != ((size_t) 1 << pg); ++pg)
+ if ((pages & ((size_t) 1 << pg)) != 0)
+ pages += ((size_t) 1 << 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;
+ __u64 data_size;
+
+ data_size = (__u64) pages * PAGE_SIZE;
+
+ /* Don't ask for more than we can represent in the configuration. */
+ if ((__u64) UINT_MAX < data_size)
+ continue;
+
+ size = (size_t) data_size;
+ length = size + PAGE_SIZE;
- tinfo->attr.exclude_kernel = 1;
- tinfo->attr.exclude_hv = 1;
- tinfo->attr.exclude_idle = 1;
+ /* Check for overflows. */
+ if ((__u64) length != data_size + PAGE_SIZE)
+ continue;
+
+ /* The number of pages we request needs to be a power of two. */
+ header = ((struct perf_event_mmap_page *)
+ mmap (NULL, length, PROT_READ, MAP_SHARED, bts->file, 0));
+ if (header != MAP_FAILED)
+ break;
+ }
+
+ if (pages == 0)
+ goto err_file;
+
+ data_offset = PAGE_SIZE;
+
+#if defined (PERF_ATTR_SIZE_VER5)
+ if (offsetof (struct perf_event_mmap_page, data_size) <= header->size)
+ {
+ __u64 data_size;
+
+ data_offset = header->data_offset;
+ data_size = header->data_size;
- tinfo->ptr_bits = 0;
+ size = (unsigned int) data_size;
+
+ /* Check for overflows. */
+ if ((__u64) size != data_size)
+ {
+ munmap ((void *) header, size + PAGE_SIZE);
+ goto err_file;
+ }
+ }
+#endif /* defined (PERF_ATTR_SIZE_VER5) */
+
+ bts->header = header;
+ bts->bts.mem = ((const uint8_t *) header) + data_offset;
+ bts->bts.size = size;
+ bts->bts.data_head = &header->data_head;
+ bts->bts.last_head = 0ull;
+
+ tinfo->conf.bts.size = (unsigned int) size;
+ return tinfo;
+
+ err_file:
+ /* 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;
+ size_t 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 = XCNEW (struct btrace_target_info);
+ tinfo->ptid = ptid;
+
+ 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;
- tinfo->file = syscall (SYS_perf_event_open, &tinfo->attr, pid, -1, -1, 0);
- if (tinfo->file < 0)
+ pt->file = syscall (SYS_perf_event_open, &pt->attr, pid, -1, -1, 0);
+ if (pt->file < 0)
goto err;
- /* We try to allocate as much buffer as we can get.
- We could allow the user to specify the size of the buffer, but then
- we'd leave this search for the maximum buffer size to him. */
- for (pg = 4; pg >= 0; --pg)
+ /* 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 = ((size_t) conf->size / PAGE_SIZE
+ + ((conf->size % PAGE_SIZE) == 0 ? 0 : 1));
+ /* 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 != ((size_t) 1 << pg); ++pg)
+ if ((pages & ((size_t) 1 << pg)) != 0)
+ pages += ((size_t) 1 << pg);
+
+ /* We try to allocate the requested size.
+ If that fails, try to get as much as we can. */
+ for (; pages > 0; pages >>= 1)
{
- /* The number of pages we request needs to be a power of two. */
- tinfo->size = 1 << pg;
- tinfo->buffer = mmap (NULL, perf_event_mmap_size (tinfo),
- PROT_READ, MAP_SHARED, tinfo->file, 0);
- if (tinfo->buffer == MAP_FAILED)
+ size_t length;
+ __u64 data_size;
+
+ data_size = (__u64) pages * PAGE_SIZE;
+
+ /* Don't ask for more than we can represent in the configuration. */
+ if ((__u64) UINT_MAX < data_size)
continue;
- return tinfo;
+ size = (size_t) data_size;
+
+ /* Check for overflows. */
+ if ((__u64) size != data_size)
+ continue;
+
+ header->aux_size = data_size;
+ length = size;
+
+ pt->pt.mem = mmap (NULL, length, PROT_READ, MAP_SHARED, pt->file,
+ header->aux_offset);
+ if (pt->pt.mem != MAP_FAILED)
+ break;
}
- /* We were not able to allocate any buffer. */
- close (tinfo->file);
+ if (pages == 0)
+ goto err_conf;
+
+ pt->header = header;
+ pt->pt.size = size;
+ pt->pt.data_head = &header->aux_head;
+
+ tinfo->conf.pt.size = (unsigned int) 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. */
-enum btrace_error
-linux_disable_btrace (struct btrace_target_info *tinfo)
+struct btrace_target_info *
+linux_enable_btrace (ptid_t ptid, const struct btrace_config *conf)
{
- int errcode;
+ struct btrace_target_info *tinfo;
- errno = 0;
- errcode = munmap (tinfo->buffer, perf_event_mmap_size (tinfo));
- if (errcode != 0)
- return BTRACE_ERR_UNKNOWN;
+ tinfo = NULL;
+ switch (conf->format)
+ {
+ case BTRACE_FORMAT_NONE:
+ break;
+
+ 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;
+}
+
+/* Disable BTS tracing. */
+
+static enum btrace_error
+linux_disable_bts (struct btrace_tinfo_bts *tinfo)
+{
+ munmap((void *) tinfo->header, tinfo->bts.size + PAGE_SIZE);
close (tinfo->file);
- xfree (tinfo);
return BTRACE_ERR_NONE;
}
-/* Check whether the branch trace has changed. */
+/* Disable Intel(R) Processor Trace tracing. */
-static int
-linux_btrace_has_changed (struct btrace_target_info *tinfo)
+static enum btrace_error
+linux_disable_pt (struct btrace_tinfo_pt *tinfo)
{
- volatile struct perf_event_mmap_page *header = perf_event_header (tinfo);
+ munmap((void *) tinfo->pt.mem, tinfo->pt.size);
+ munmap((void *) tinfo->header, PAGE_SIZE);
+ close (tinfo->file);
- return header->data_head != tinfo->data_head;
+ return BTRACE_ERR_NONE;
}
/* See linux-btrace.h. */
enum btrace_error
-linux_read_btrace (VEC (btrace_block_s) **btrace,
- struct btrace_target_info *tinfo,
- enum btrace_read_type type)
+linux_disable_btrace (struct btrace_target_info *tinfo)
+{
+ enum btrace_error errcode;
+
+ errcode = BTRACE_ERR_NOT_SUPPORTED;
+ switch (tinfo->conf.format)
+ {
+ case BTRACE_FORMAT_NONE:
+ break;
+
+ 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)
+ xfree (tinfo);
+
+ return errcode;
+}
+
+/* Read branch trace data in BTS format for the thread given by TINFO into
+ BTRACE using the TYPE reading method. */
+
+static enum btrace_error
+linux_read_bts (struct btrace_data_bts *btrace,
+ struct btrace_target_info *tinfo,
+ enum btrace_read_type type)
{
- volatile struct perf_event_mmap_page *header;
+ struct perf_event_buffer *pevent;
const uint8_t *begin, *end, *start;
- unsigned long data_head, data_tail, retries = 5;
size_t buffer_size, size;
+ __u64 data_head, data_tail;
+ unsigned int retries = 5;
+
+ pevent = &tinfo->variant.bts.bts;
/* For delta reads, we return at least the partial last block containing
the current PC. */
- if (type == BTRACE_READ_NEW && !linux_btrace_has_changed (tinfo))
+ if (type == BTRACE_READ_NEW && !perf_event_new_data (pevent))
return BTRACE_ERR_NONE;
- header = perf_event_header (tinfo);
- buffer_size = perf_event_buffer_size (tinfo);
- data_tail = tinfo->data_head;
+ buffer_size = pevent->size;
+ data_tail = pevent->last_head;
/* We may need to retry reading the trace. See below. */
while (retries--)
{
- data_head = header->data_head;
+ data_head = *pevent->data_head;
/* Delete any leftover trace from the previous iteration. */
- VEC_free (btrace_block_s, *btrace);
+ VEC_free (btrace_block_s, btrace->blocks);
if (type == BTRACE_READ_DELTA)
{
+ __u64 data_size;
+
/* Determine the number of bytes to read and check for buffer
overflows. */
return BTRACE_ERR_OVERFLOW;
/* If the buffer is smaller than the trace delta, we overflowed. */
- size = data_head - data_tail;
- if (buffer_size < size)
+ data_size = data_head - data_tail;
+ if (buffer_size < data_size)
return BTRACE_ERR_OVERFLOW;
+
+ /* DATA_SIZE <= BUFFER_SIZE and therefore fits into a size_t. */
+ size = (size_t) data_size;
}
else
{
/* Adjust the size if the buffer has not overflowed, yet. */
if (data_head < size)
- size = data_head;
+ size = (size_t) data_head;
}
/* Data_head keeps growing; the buffer itself is circular. */
- begin = perf_event_buffer_begin (tinfo);
+ begin = pevent->mem;
start = begin + data_head % buffer_size;
if (data_head <= buffer_size)
end = start;
else
- end = perf_event_buffer_end (tinfo);
+ end = begin + pevent->size;
- *btrace = perf_event_read_bts (tinfo, begin, end, start, size);
+ btrace->blocks = perf_event_read_bts (tinfo, begin, end, start, size);
/* The stopping thread notifies its ptracer before it is scheduled out.
On multi-core systems, the debugger might therefore run while the
kernel might be writing the last branch trace records.
Let's check whether the data head moved while we read the trace. */
- if (data_head == header->data_head)
+ if (data_head == *pevent->data_head)
break;
}
- tinfo->data_head = data_head;
+ pevent->last_head = data_head;
/* Prune the incomplete last block (i.e. the first one of inferior execution)
if we're not doing a delta read. There is no way of filling in its zeroed
BEGIN element. */
- if (!VEC_empty (btrace_block_s, *btrace) && type != BTRACE_READ_DELTA)
- VEC_pop (btrace_block_s, *btrace);
+ if (!VEC_empty (btrace_block_s, btrace->blocks)
+ && type != BTRACE_READ_DELTA)
+ VEC_pop (btrace_block_s, btrace->blocks);
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
+linux_read_btrace (struct btrace_data *btrace,
+ struct btrace_target_info *tinfo,
+ enum btrace_read_type type)
+{
+ switch (tinfo->conf.format)
+ {
+ case BTRACE_FORMAT_NONE:
+ return BTRACE_ERR_NOT_SUPPORTED;
+
+ case BTRACE_FORMAT_BTS:
+ /* We read btrace in BTS format. */
+ btrace->format = BTRACE_FORMAT_BTS;
+ 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."));
+}
+
+/* See linux-btrace.h. */
+
+const struct btrace_config *
+linux_btrace_conf (const struct btrace_target_info *tinfo)
+{
+ return &tinfo->conf;
+}
+
#else /* !HAVE_LINUX_PERF_EVENT_H */
/* See linux-btrace.h. */
int
-linux_supports_btrace (struct target_ops *ops)
+linux_supports_btrace (struct target_ops *ops, enum btrace_format format)
{
return 0;
}
/* See linux-btrace.h. */
struct btrace_target_info *
-linux_enable_btrace (ptid_t ptid)
+linux_enable_btrace (ptid_t ptid, const struct btrace_config *conf)
{
return NULL;
}
/* See linux-btrace.h. */
enum btrace_error
-linux_read_btrace (VEC (btrace_block_s) **btrace,
+linux_read_btrace (struct btrace_data *btrace,
struct btrace_target_info *tinfo,
enum btrace_read_type type)
{
return BTRACE_ERR_NOT_SUPPORTED;
}
+/* See linux-btrace.h. */
+
+const struct btrace_config *
+linux_btrace_conf (const struct btrace_target_info *tinfo)
+{
+ return NULL;
+}
+
#endif /* !HAVE_LINUX_PERF_EVENT_H */