Index: usr.sbin/Makefile.aarch64 =================================================================== --- usr.sbin/Makefile.aarch64 +++ usr.sbin/Makefile.aarch64 @@ -1,6 +1,7 @@ .if ${MK_ACPI} != "no" SUBDIR+= acpi .endif +SUBDIR+= hwt .if ${MK_BHYVE} != "no" SUBDIR+= bhyve SUBDIR+= bhyvectl Index: usr.sbin/Makefile.amd64 =================================================================== --- usr.sbin/Makefile.amd64 +++ usr.sbin/Makefile.amd64 @@ -20,3 +20,4 @@ SUBDIR+= mptable SUBDIR+= spkrtest SUBDIR+= zzz +SUBDIR+= hwt Index: usr.sbin/hwt/Makefile =================================================================== --- /dev/null +++ usr.sbin/hwt/Makefile @@ -0,0 +1,24 @@ +# $FreeBSD$ + +PROG_CXX= hwt +MAN= + +CFLAGS+= -I${SRCTOP}/lib/libpmcstat -I${SRCTOP}/sys + +LIBADD= elf pmcstat xo util + +SRCS= hwt.c \ + hwt_elf.c \ + hwt_process.c \ + hwt_record.c \ + libpmcstat_stubs.c + +.if ${MACHINE_CPUARCH} == "aarch64" +SRCS+= hwt_coresight.c hwt_spe.c +LIBADD+= opencsd +.elif ${MACHINE_CPUARCH} == "amd64" +SRCS+= hwt_pt.c +LIBADD+= ipt +.endif + +.include Index: usr.sbin/hwt/hwt.h =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt.h @@ -0,0 +1,140 @@ +/*- + * Copyright (c) 2023 Ruslan Bukin + * All rights reserved. + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HWTVAR_H_ +#define _HWTVAR_H_ + +#define TC_MAX_ADDR_RANGES 16 + +struct trace_context; +struct hwt_exec_img; +struct pmcstat_image; + +struct trace_dev_methods { + int (*init)(struct trace_context *tc); + void (*shutdown)(struct trace_context *tc); + + /* + * Called whenever a tracing buffer needs to mapped into hwt. + */ + int (*mmap)(struct trace_context *tc, + struct hwt_record_user_entry *entry); + int (*set_config)(struct trace_context *tc); + + /* + * Called whenever the traced process's address space layout changes. + */ + int ( + *image_load_cb)(struct trace_context *tc, struct hwt_exec_img *img); + + /* + * Trace data processing methods. + * + * An hwt backend must specify one of the two processing methods. + * If both methods are specified, the 'process_buffer' method will + * always take precedence. + * + * If present, the 'process' method should implement a complete + * replacement for hwt's default processing loop. This means that it is + * responsible for fetching trace data, fetching records, and processing + * the data on its own. + * + * The 'process_buffer' method gets invoked from hwt's default + * processing loop whenever an HWT_BUFFER_RECORD is received and is + * tasked with decoding tracing buffer data. This is suitable for + * backends whose tracing hardware issues interrupts when the tracing + * buffer is ready to be processed. + */ + int (*process)(struct trace_context *tc); + int (*process_buffer)(struct trace_context *tc, int buf_id, int curpage, + vm_offset_t offset); +}; + +struct trace_dev { + const char *name; + const char *fullname; + struct trace_dev_methods *methods; +}; + +struct trace_context { + struct trace_dev *trace_dev; + struct pmcstat_process *pp; + struct hwt_record_user_entry *records; + void *base; + size_t bufsize; + int attach; + int pid; + cpuset_t cpu_map; + int fd; + int thr_fd; + int terminate; + int kqueue_fd; + + int thread_id; + int ident; + + /* Address range filtering. */ + int suspend_on_mmap; + char *image_name; + char *func_name; + uintptr_t addr_ranges[TC_MAX_ADDR_RANGES * 2]; + int nranges; + + /* Backend-specific config. */ + void *config; + int flag_format; + + /* Raw trace. */ + int raw; + FILE *raw_f; + uint64_t bytes_written; + + /* Trace file. */ + char *filename; + + int mode; + const char *fs_root; +}; + +struct pmcstat_process *hwt_process_alloc(void); +int hwt_process_create(int *sockpair, char **cmd, char **env, int *pid0); +int hwt_process_start(int *sockpair); +int hwt_record_fetch(struct trace_context *tc, int *nrecords, int wait); +void hwt_procexit(pid_t pid, int status); +void hwt_sleep(int msec); +int hwt_find_sym(struct trace_context *tc); +struct pmcstat_symbol *hwt_sym_lookup(const struct trace_context *tc, + uint64_t ip, struct pmcstat_image **img, uint64_t *newpc0); +int hwt_start_tracing(struct trace_context *tc); +int hwt_stop_tracing(struct trace_context *tc); +int hwt_mmap_received(struct trace_context *tc, + struct hwt_record_user_entry *entry); +int hwt_ncpu(void); + +#endif /* !_HWTVAR_H_ */ Index: usr.sbin/hwt/hwt.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt.c @@ -0,0 +1,793 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libpmcstat_stubs.h" +#include +#include +#include + +#include "hwt.h" +#include "hwt_elf.h" + +#if defined(__aarch64__) +#include "hwt_coresight.h" +#include "hwt_spe.h" +#endif + +#if defined(__amd64__) +#include "hwt_pt.h" +#endif + + +#define HWT_DEBUG +#undef HWT_DEBUG + +#ifdef HWT_DEBUG +#define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define dprintf(fmt, ...) +#endif + +#define PARENTSOCKET 0 +#define CHILDSOCKET 1 +#define NSOCKPAIRFD 2 + +static struct trace_context tcs; + +static struct trace_dev trace_devs[] = { +#if defined(__aarch64__) + { "coresight", "ARM Coresight", &cs_methods }, + { "spe", "ARM Statistical Profiling Extension", &spe_methods }, +#endif +#if defined(__amd64__) + { "pt", "Intel PT", &pt_methods}, +#endif + { NULL, NULL, NULL } +}; + +void +hwt_sleep(int msec) +{ + struct timespec time_to_sleep; + + time_to_sleep.tv_sec = 0; + time_to_sleep.tv_nsec = msec * 1000000; + + nanosleep(&time_to_sleep, &time_to_sleep); +} + +void +hwt_procexit(pid_t pid, int exit_status __unused) +{ + struct trace_context *tc; + + tc = &tcs; + + if (tc->pid == pid) + tc->terminate = 1; +} + +static int +hwt_unsuspend_proc(struct trace_context *tc) +{ + struct hwt_wakeup w; + int error; + + error = ioctl(tc->thr_fd, HWT_IOC_WAKEUP, &w); + + return (error); +} + +int +hwt_mmap_received(struct trace_context *tc, + struct hwt_record_user_entry *entry __unused) +{ + int error; + + assert(tc->mode == HWT_MODE_THREAD); + + if (!tc->suspend_on_mmap) + return (0); + + if (tc->func_name == NULL) + return (0); + + error = hwt_find_sym(tc); + if (error != 0) { + hwt_unsuspend_proc(tc); + return (-1); + } + + tc->suspend_on_mmap = 0; + + error = tc->trace_dev->methods->set_config(tc); + if (error) + return (-2); + + error = hwt_start_tracing(tc); + if (error) + return (-2); + + printf("%s: tracing started\n", __func__); + + hwt_unsuspend_proc(tc); + + return (0); +} + +static int +hwt_ctx_alloc(struct trace_context *tc) +{ + struct hwt_alloc al; + cpuset_t cpu_map; + int error = 0; + + if (tc->trace_dev->methods->init != NULL){ + error = tc->trace_dev->methods->init(tc); + if (error) + return (error); + } + + CPU_ZERO(&cpu_map); + + memset(&al, 0, sizeof(struct hwt_alloc)); + + al.mode = tc->mode; + if (tc->mode == HWT_MODE_THREAD) + al.pid = tc->pid; + else { + al.cpu_map = &tc->cpu_map; + al.cpusetsize = sizeof(cpuset_t); + } + + al.bufsize = tc->bufsize; + al.backend_name = tc->trace_dev->name; + al.ident = &tc->ident; + al.kqueue_fd = tc->kqueue_fd; + + error = ioctl(tc->fd, HWT_IOC_ALLOC, &al); + + return (error); +} + +int +hwt_ncpu(void) +{ + int ncpu; + + ncpu = sysconf(_SC_NPROCESSORS_CONF); + + return (ncpu); +} + +static int +hwt_get_records(struct trace_context *tc, uint32_t *nrec, int wait) +{ + int nrecords; + int error; + + error = hwt_record_fetch(tc, &nrecords, wait); + if (error) + return (error); + + *nrec = nrecords; + + return (0); +} + +struct pmcstat_symbol * +hwt_sym_lookup(const struct trace_context *tc, uint64_t ip, + struct pmcstat_image **img, uint64_t *newpc0) +{ + struct pmcstat_image *image; + struct pmcstat_symbol *sym; + struct pmcstat_pcmap *map; + uint64_t newpc; + + map = pmcstat_process_find_map(tc->pp, ip); + if (map != NULL) { + image = map->ppm_image; + newpc = ip - + ((unsigned long)map->ppm_lowpc + + (image->pi_vaddr - image->pi_start)); + sym = pmcstat_symbol_search(image, newpc); /* Could be NULL. */ + newpc += image->pi_vaddr; + + *img = image; + *newpc0 = newpc; + + return (sym); + } else + *img = NULL; + + return (NULL); +} + +int +hwt_find_sym(struct trace_context *tc) +{ + struct pmcstat_symbol *sym; + uintptr_t addr_start; + uintptr_t addr_end; + + sym = pmcstat_symbol_search_by_name(tc->pp, tc->image_name, + tc->func_name, &addr_start, &addr_end); + if (sym) { + printf("sym found, start end %lx %lx\n", (uint64_t)addr_start, + (uint64_t)addr_end); + tc->addr_ranges[tc->nranges] = addr_start; + tc->addr_ranges[tc->nranges + 1] = addr_end; + tc->nranges += 1; + return (0); + } + + return (ENOENT); +} + +int +hwt_start_tracing(struct trace_context *tc) +{ + struct hwt_start s; + int error; + + error = ioctl(tc->thr_fd, HWT_IOC_START, &s); + + return (error); +} + +int +hwt_stop_tracing(struct trace_context *tc) +{ + struct hwt_stop s; + int error; + + error = ioctl(tc->thr_fd, HWT_IOC_STOP, &s); + + return (error); +} + +static void +usage(void) +{ + + errx(EX_USAGE, + "hwt [-s cpu_id] [-c devname] [-b bufsize] [-t id] [-g]" + " [-r] [-w file] [-i name]" + " [-f name] [path to executable]\n" + "\t -s\tcpu_id\t\tCPU (kernel) mode.\n" + "\t -c\tname\t\tName of tracing device, e.g. 'coresight'.\n" + "\t -b\tbufsize\t\tSize of trace buffer (per each thread/cpu)\n" + "\t\t\t\tin bytes. Must be a multiple of page size\n" + "\t\t\t\te.g. 4096.\n" + "\t -t\tid\t\tThread index of application passed to decoder.\n" + "\t -r\t\t\tRaw flag. Do not decode results.\n" + "\t -w\tfilename\tStore results into file.\n" +#if defined(__aarch64__) + "\t -g\t\t\tFormat flag.\n" +#endif + "\t -i\tname\t\tFilter by dynamic library, executable name,\n" + "\t\t\t\tkernel module name or 'kernel'.\n" + "\t -f\tname\t\tFilter by function name." + ); +} + +static int +hwt_process_loop(struct trace_context *tc) +{ + int status; + int nrec, error; + + xo_open_container("trace"); + xo_open_list("entries"); + + printf("Decoder started. Press ctrl+c to stop.\n"); + + while (1) { + waitpid(tc->pid, &status, WNOHANG); + if (WIFEXITED(status)) { + tc->terminate = 1; + } + if (!tc->terminate) { + printf("%s: waiting for new records\n", __func__); + error = hwt_record_fetch(tc, &nrec, 1); + if (error != 0 || nrec == 0) + break; + } + if (errno == EINTR || tc->terminate) { + printf("%s: tracing terminated - exiting\n", __func__); + /* Fetch any remaining records */ + hwt_record_fetch(tc, &nrec, 0); + if (tc->trace_dev->methods->shutdown != NULL) + tc->trace_dev->methods->shutdown(tc); + return (0); + } + } + + xo_close_list("file"); + xo_close_container("wc"); + if (xo_finish() < 0) + xo_err(EXIT_FAILURE, "stdout"); + + return (0); +} + +static int +hwt_process(struct trace_context *tc) +{ + int error; + + if (tc->trace_dev->methods->process_buffer != NULL) { + error = hwt_process_loop(tc); + if (error) { + printf("Can't process data, error %d.\n", error); + return (error); + } + } else { + if (tc->trace_dev->methods->process == NULL) + errx(EX_SOFTWARE, + "Backend has no data processing methods specified\n"); + error = tc->trace_dev->methods->process(tc); + if (error) { + printf("Can't process data, error %d.\n", error); + return (error); + } + } + + return (0); +} + +static int +hwt_mode_cpu(struct trace_context *tc) +{ + uint32_t nrec; + int error; + + if (strcmp(tc->trace_dev->name, "coresight") == 0 && + (tc->image_name == NULL || tc->func_name == NULL)) + errx(EX_USAGE, "IP range filtering must be setup for CPU" + " tracing"); + + error = hwt_ctx_alloc(tc); + if (error) { + printf("%s: failed to alloc cpu-mode ctx, error %d errno %d\n", + __func__, error, errno); + if (errno == EPERM) + printf("Permission denied"); + else if (errno == EINVAL) + printf("Invalid argument: buffer size is not a multiple" + " of page size, or is too small/large"); + printf("\n"); + return (error); + } + + error = tc->trace_dev->methods->mmap(tc, NULL); + if (error) { + printf("%s: cant map memory, error %d\n", __func__, error); + return (error); + } + + tc->pp->pp_pid = -1; + + error = hwt_get_records(tc, &nrec, 0); + if (error != 0) + return (error); + + printf("Received %d kernel mappings\n", nrec); + + if(tc->image_name || tc->func_name) { + error = hwt_find_sym(tc); + if (error) + errx(EX_USAGE, "could not find symbol"); + } + + error = tc->trace_dev->methods->set_config(tc); + if (error != 0) + errx(EX_DATAERR, "can't set config"); + + error = hwt_start_tracing(tc); + if (error) + errx(EX_SOFTWARE, "failed to start tracing, error %d\n", error); + + return (hwt_process(tc)); +} + +static int +hwt_new_proc(struct trace_context *tc, int *sockpair, char **cmd, char **env, + uint32_t *nlibs0) +{ + struct stat st; + uint32_t nlibs; + int error; + + error = stat(*cmd, &st); + if (error) { + printf("Could not find target executable" + " error %d.\n", error); + return (error); + } + + error = hwt_elf_count_libs(*cmd, &nlibs); + if (error != 0) { + printf("Could not count libs in the executable provided.\n"); + return (error); + } + + nlibs += 1; /* add binary itself. */ + + dprintf("cmd is %s, nlibs %d\n", *cmd, nlibs); + + error = hwt_process_create(sockpair, cmd, env, &tc->pid); + if (error != 0) + return (error); + + printf("%s: process pid %d created\n", __func__, tc->pid); + + *nlibs0 = nlibs; + + return (0); +} + +static int +hwt_get_vmmap(struct trace_context *tc) +{ + struct kinfo_vmentry *vmmap, *kve; + int cnt; + int i, j; + + pmcstat_interned_string path; + struct pmcstat_image *image; + struct pmc_plugins plugins; + struct pmcstat_args args; + unsigned long addr; + + memset(&plugins, 0, sizeof(struct pmc_plugins)); + memset(&args, 0, sizeof(struct pmcstat_args)); + args.pa_fsroot = "/"; + + vmmap = kinfo_getvmmap(tc->pid, &cnt); + if (vmmap == NULL) + return (ENXIO); + + printf("vmmap cnt %d\n", cnt); + + for (i = 0, j = 0; i < cnt; i++) { + kve = &vmmap[i]; + if ((kve->kve_protection & KVME_PROT_EXEC) == 0) + continue; + if (*kve->kve_path == '\0') + continue; + + path = pmcstat_string_intern(kve->kve_path); + image = pmcstat_image_from_path(path, 0, &args, &plugins); + if (image == NULL) + continue; + + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_determine_type(image, &args); + + addr = (unsigned long)kve->kve_start & ~1; + addr -= (image->pi_start - image->pi_vaddr); + pmcstat_image_link(tc->pp, image, addr); + + printf(" lib #%d: path %s addr %lx\n", j++, + kve->kve_path, (unsigned long)kve->kve_start); + } + + return (0); +} + +static int +hwt_mode_thread(struct trace_context *tc, char **cmd, char **env) +{ + uint32_t tot_rec; + uint32_t nrec; + uint32_t nlibs; + int sockpair[NSOCKPAIRFD]; + int timeout; + int error; + + nlibs = 0; + + if (tc->attach == 0) { + error = hwt_new_proc(tc, sockpair, cmd, env, &nlibs); + if (error) + return (error); + + if (tc->func_name != NULL) + tc->suspend_on_mmap = 1; + } + + tc->pp->pp_pid = tc->pid; + + error = hwt_ctx_alloc(tc); + if (error) { + printf("%s: failed to alloc thread-mode ctx " + "error %d errno %d %s\n", + __func__, error, errno, strerror(errno)); + if (errno == EPERM) + printf("Permission denied"); + else if (errno == EINVAL) + printf("Invalid argument: buffer size is not a multiple" + " of page size, or is too small/large"); + printf("\n"); + return (error); + } + error = tc->trace_dev->methods->mmap(tc, NULL); + if (error) { + printf("%s: failed to map trace buffer for first thread, %d\n", + __func__, error); + return (error); + } + + error = tc->trace_dev->methods->set_config(tc); + if (error != 0) + errx(EX_DATAERR, "can't set config, errno %d", errno); + + if (tc->attach) + hwt_get_vmmap(tc); + + if (tc->attach || tc->suspend_on_mmap == 0) { + /* No address range filtering. Start tracing immediately. */ + error = hwt_start_tracing(tc); + if (error) + errx(EX_SOFTWARE, "failed to start tracing, error %d\n", + error); + } + + if (tc->attach == 0) { + error = hwt_process_start(sockpair); + if (error != 0) + return (error); + } + + printf("Expect at least %d records.\n", nlibs); + + /* + * Ensure we got expected amount of mmap/interp records so that + * mapping tables constructed before we do symbol lookup. + * + * Also receive any records about threads that were created in the + * meantime. + */ + timeout = 10; + tot_rec = 0; + do { + error = hwt_get_records(tc, &nrec, 0); + if (error != 0) + break; + if (nrec == 0) + timeout--; + tot_rec += nrec; + hwt_sleep(10); + } while (timeout); + + if (tot_rec < nlibs) { + errx(EX_DATAERR, + "Failed to receive expected amount of HWT records."); + } + + return (hwt_process(tc)); +} + +static int +hwt_get_cpumask(const char *arg, cpuset_t *cpumask) +{ + const char *start; + int cpu_id; + char *end; + + CPU_ZERO(cpumask); + + start = arg; + + while (*start) { + cpu_id = strtol(start, &end, 0); + if (cpu_id < 0) + return (-1); + + if (end == start) + return (-2); + + CPU_SET(cpu_id, cpumask); + + start = end + strspn(end, ", \t"); + }; + + return (0); +} + +int +main(int argc, char **argv, char **env) +{ + struct trace_context *tc; + char *trace_dev_name; + int error; + int option; + int found; + int thread_id_specified; + int i; + + tc = &tcs; + + memset(tc, 0, sizeof(struct trace_context)); + + /* Defaults */ + tc->bufsize = 128 * 1024 * 1024; + + /* First available is default trace device. */ + tc->trace_dev = &trace_devs[0]; + if (tc->trace_dev->name == NULL) { + printf("No trace devices available\n"); + return (1); + } + + tc->mode = HWT_MODE_THREAD; + tc->fs_root = "/"; + tc->thread_id = 0; + tc->attach = 0; + thread_id_specified = 0; + + argc = xo_parse_args(argc, argv); + if (argc < 0) + exit(EXIT_FAILURE); + + while ((option = getopt(argc, argv, "P:R:gs:hc:b:rw:t:i:f:")) != -1) + switch (option) { + case 'P': + tc->attach = 1; + tc->pid = atol(optarg); + break; + case 's': + tc->mode = HWT_MODE_CPU; + hwt_get_cpumask(optarg, &tc->cpu_map); + break; + case 'R': + tc->fs_root = optarg; + break; + case 'c': + trace_dev_name = strdup(optarg); + found = 0; + for (i = 0; trace_devs[i].name != NULL; i++) { + if (strcmp(trace_devs[i].name, + trace_dev_name) == 0) { + tc->trace_dev = &trace_devs[i]; + found = 1; + break; + } + } + if (!found) { + printf("Trace device \"%s\" not available.\n", + trace_dev_name); + return (ENOENT); + } + break; + case 'b': + tc->bufsize = atol(optarg); + break; + case 'r': + /* Do not decode trace. */ + tc->raw = 1; + break; + case 'w': + /* Store trace into a file. */ + tc->filename = strdup(optarg); + break; + case 'i': + /* + * Name of dynamic lib or main executable for IP + * address range filtering. + */ + tc->image_name = strdup(basename(optarg)); + if (*tc->image_name == '.' || *tc->image_name == '/') + err(EX_USAGE, + "Invalid executable path provided"); + break; + case 'f': + /* Name of the func to trace. */ + tc->func_name = strdup(optarg); + break; + case 't': + tc->thread_id = atoi(optarg); + thread_id_specified = 1; + break; + case 'g': + tc->flag_format = 1; + break; + case 'h': + usage(); + break; + default: + break; + } + + if (tc->mode == HWT_MODE_CPU && thread_id_specified) { + printf("Thread ID to decode used in THREAD mode only.\n"); + exit(1); + } + + if (tc->raw != 0 && tc->filename == NULL) { + printf("Filename must be specified for the raw data.\n"); + exit(1); + } + + if ((tc->image_name == NULL && tc->func_name != NULL) || + (tc->image_name != NULL && tc->func_name == NULL)) + errx(EX_USAGE, "For address range tracing specify both image " + "and func, or none of them."); + + tc->fd = open("/dev/hwt", O_RDWR); + if (tc->fd < 0) { + printf("Can't open /dev/hwt\n"); + return (-1); + } + + tc->pp = hwt_process_alloc(); + tc->pp->pp_isactive = 1; + + argc += optind; + argv += optind; + + if (tc->mode == HWT_MODE_THREAD) { + if (*argv == NULL && tc->attach == 0) + usage(); + error = hwt_mode_thread(tc, argv, env); + } else { + if (*argv != NULL) + usage(); + error = hwt_mode_cpu(tc); + } + + close(tc->fd); + + return (error); +} Index: usr.sbin/hwt/hwt_coresight.h =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_coresight.h @@ -0,0 +1,284 @@ +/*- + * Copyright (c) 2023 Ruslan Bukin + * All rights reserved. + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HWT_CORESIGHT_H_ +#define _HWT_CORESIGHT_H_ + +#define TRCPRGCTLR 0x004 /* Trace Programming Control Register */ +#define TRCPRGCTLR_EN (1 << 0) /* Trace unit enable bit */ +#define TRCPROCSELR 0x008 /* Trace PE Select Control Register */ +#define TRCSTATR 0x00C /* Trace Trace Status Register */ +#define TRCSTATR_PMSTABLE (1 << 1) /* The programmers' model is stable. */ +#define TRCSTATR_IDLE (1 << 0) /* The trace unit is idle. */ +#define TRCCONFIGR 0x010 /* Trace Trace Configuration Register */ +#define TRCCONFIGR_DV (1 << 17) /* Data value tracing is enabled when INSTP0 is not 0b00 */ +#define TRCCONFIGR_DA (1 << 16) /* Data address tracing is enabled when INSTP0 is not 0b00. */ +#define TRCCONFIGR_VMIDOPT (1 << 15) /* Control bit to configure the Virtual context identifier value */ +#define TRCCONFIGR_QE_S 13 /* Q element enable field */ +#define TRCCONFIGR_QE_M (0x3 << TRCCONFIGR_QE_S) +#define TRCCONFIGR_RS (1 << 12) /* Return stack enable bit */ +#define TRCCONFIGR_TS (1 << 11) /* Global timestamp tracing is enabled. */ +#define TRCCONFIGR_COND_S 8 /* Conditional instruction tracing bit. */ +#define TRCCONFIGR_COND_M (0x7 << TRCCONFIGR_COND_S) +#define TRCCONFIGR_COND_DIS 0 +#define TRCCONFIGR_COND_LDR (1 << TRCCONFIGR_COND_S) /* Conditional load instructions are traced. */ +#define TRCCONFIGR_COND_STR (2 << TRCCONFIGR_COND_S) /* Conditional store instructions are traced. */ +#define TRCCONFIGR_COND_LDRSTR (3 << TRCCONFIGR_COND_S) /* Conditional load and store instructions are traced. */ +#define TRCCONFIGR_COND_ALL (7 << TRCCONFIGR_COND_S) /* All conditional instructions are traced. */ +#define TRCCONFIGR_VMID (1 << 7) /* Virtual context identifier tracing is enabled. */ +#define TRCCONFIGR_CID (1 << 6) /* Context ID tracing is enabled. */ +#define TRCCONFIGR_CCI (1 << 4) /* Cycle counting in the instruction trace is enabled. */ +#define TRCCONFIGR_BB (1 << 3) /* Branch broadcast mode is enabled. */ +#define TRCCONFIGR_INSTP0_S 1 /* Instruction P0 field. */ +#define TRCCONFIGR_INSTP0_M (0x3 << TRCCONFIGR_INSTP0_S) +#define TRCCONFIGR_INSTP0_NONE 0 /* Do not trace load and store instructions as P0 instructions. */ +#define TRCCONFIGR_INSTP0_LDR (1 << TRCCONFIGR_INSTP0_S) /* Trace load instructions as P0 instructions. */ +#define TRCCONFIGR_INSTP0_STR (2 << TRCCONFIGR_INSTP0_S) /* Trace store instructions as P0 instructions. */ +#define TRCCONFIGR_INSTP0_LDRSTR (3 << TRCCONFIGR_INSTP0_S) /* Trace load and store instructions as P0 instr. */ +#define TRCAUXCTLR 0x018 /* Trace Auxiliary Control Register */ +#define TRCEVENTCTL0R 0x020 /* Trace Event Control 0 Register */ +#define TRCEVENTCTL1R 0x024 /* Trace Event Control 1 Register */ +#define TRCSTALLCTLR 0x02C /* Trace Stall Control Register */ +#define TRCTSCTLR 0x030 /* Trace Global Timestamp Control Register */ +#define TRCSYNCPR 0x034 /* Trace Synchronization Period Register */ +#define TRCSYNCPR_PERIOD_S 0 +#define TRCSYNCPR_PERIOD_M 0x1f +#define TRCSYNCPR_1K (10 << TRCSYNCPR_PERIOD_S) +#define TRCSYNCPR_2K (11 << TRCSYNCPR_PERIOD_S) +#define TRCSYNCPR_4K (12 << TRCSYNCPR_PERIOD_S) +#define TRCCCCTLR 0x038 /* Trace Cycle Count Control Register */ +#define TRCBBCTLR 0x03C /* Trace Branch Broadcast Control Register */ +#define TRCTRACEIDR 0x040 /* Trace Trace ID Register */ +#define TRCQCTLR 0x044 /* Trace Q Element Control Register */ +#define TRCQCTLR_MODE_INC (1 << 8) /* Include mode. */ +#define TRCVICTLR 0x080 /* Trace ViewInst Main Control Register */ +#define TRCVICTLR_SSSTATUS (1 << 9) /* The start/stop logic is in the started state. */ +#define TRCVICTLR_EXLEVEL_NS_S 20 +#define TRCVICTLR_EXLEVEL_NS_M (0xf << TRCVICTLR_EXLEVEL_NS_S) +#define TRCVICTLR_EXLEVEL_NS(n) (0x1 << ((n) + TRCVICTLR_EXLEVEL_NS_S)) +#define TRCVICTLR_EXLEVEL_S_S 16 +#define TRCVICTLR_EXLEVEL_S_M (0xf << TRCVICTLR_EXLEVEL_S_S) +#define TRCVICTLR_EXLEVEL_S(n) (0x1 << ((n) + TRCVICTLR_EXLEVEL_S_S)) +#define EVENT_SEL_S 0 +#define EVENT_SEL_M (0x1f << EVENT_SEL_S) +#define TRCVIIECTLR 0x084 /* Trace ViewInst Include/Exclude Control Register */ +#define TRCVIIECTLR_INCLUDE_S 0 +#define TRCVISSCTLR 0x088 /* Trace ViewInst Start/Stop Control Register */ +#define TRCVIPCSSCTLR 0x08C /* Trace ViewInst Start/Stop PE Comparator Control Register */ +#define TRCVDCTLR 0x0A0 /* Trace ViewData Main Control Register */ +#define TRCVDCTLR_TRCEXDATA (1 << 12) /* Exception and exception return data transfers are traced */ +#define TRCVDCTLR_TBI (1 << 11) /* The trace unit assigns bits[63:56] to have the same value as bits[63:56] of the data address. */ +#define TRCVDCTLR_PCREL (1 << 10) /* The trace unit does not trace the address or value portions of PC-relative transfers. */ +#define TRCVDCTLR_SPREL_S 8 +#define TRCVDCTLR_SPREL_M (0x3 << TRCVDCTLR_SPREL_S) +#define TRCVDCTLR_EVENT_S 0 +#define TRCVDCTLR_EVENT_M (0xff << TRCVDCTLR_EVENT_S) +#define TRCVDSACCTLR 0x0A4 /* Trace ViewData Include/Exclude Single Address Comparator Control Register */ +#define TRCVDARCCTLR 0x0A8 /* Trace ViewData Include/Exclude Address Range Comparator Control Register */ +#define TRCSEQEVR(n) (0x100 + (n) * 0x4) /* Trace Sequencer State Transition Control Register [n=0-2] */ +#define TRCSEQRSTEVR 0x118 /* Trace Sequencer Reset Control Register */ +#define TRCSEQSTR 0x11C /* Trace Sequencer State Register */ +#define TRCEXTINSELR 0x120 /* Trace External Input Select Register */ +#define TRCCNTRLDVR(n) (0x140 + (n) * 0x4) /* 32 Trace Counter Reload Value Register [n=0-3] */ +#define TRCCNTCTLR(n) (0x150 + (n) * 0x4) /* 32 Trace Counter Control Register [n=0-3] */ +#define TRCCNTVR(n) (0x160 + (n) * 0x4) /* 32 Trace Counter Value Register [n=0-3] */ +#define TRCIMSPEC(n) (0x1C0 + (n) * 0x4) /* Trace IMPLEMENTATION DEFINED register [n=0-7] */ + +#define TRCIDR0(n) (0x1E0 + 0x4 * (n)) +#define TRCIDR8(n) (0x180 + 0x4 * (n)) +#define TRCIDR(n) ((n > 7) ? TRCIDR8(n) : TRCIDR0(n)) +#define TRCIDR1_TRCARCHMAJ_S 8 +#define TRCIDR1_TRCARCHMAJ_M (0xf << TRCIDR1_TRCARCHMAJ_S) +#define TRCIDR1_TRCARCHMIN_S 4 +#define TRCIDR1_TRCARCHMIN_M (0xf << TRCIDR1_TRCARCHMIN_S) + +#define TRCRSCTLR(n) (0x200 + (n) * 0x4) /* Trace Resource Selection Control Register [n=2-31] */ +#define TRCSSCCR(n) (0x280 + (n) * 0x4) /* Trace Single-shot Comparator Control Register [n=0-7] */ +#define TRCSSCSR(n) (0x2A0 + (n) * 0x4) /* Trace Single-shot Comparator Status Register [n=0-7] */ +#define TRCSSPCICR(n) (0x2C0 + (n) * 0x4) /* Trace Single-shot PE Comparator Input Control [n=0-7] */ +#define TRCOSLAR 0x300 /* Management OS Lock Access Register */ +#define TRCOSLSR 0x304 /* Management OS Lock Status Register */ +#define TRCPDCR 0x310 /* Management PowerDown Control Register */ +#define TRCPDSR 0x314 /* Management PowerDown Status Register */ +#define TRCACVR(n) (0x400 + (n) * 0x8) /* Trace Address Comparator Value Register [n=0-15] */ +#define TRCACATR(n) (0x480 + (n) * 0x8) /* Trace Address Comparator Access Type Register [n=0-15] */ +#define TRCACATR_DTBM (1 << 21) +#define TRCACATR_DATARANGE (1 << 20) +#define TRCACATR_DATASIZE_S 18 +#define TRCACATR_DATASIZE_M (0x3 << TRCACATR_DATASIZE_S) +#define TRCACATR_DATASIZE_B (0x0 << TRCACATR_DATASIZE_S) +#define TRCACATR_DATASIZE_HW (0x1 << TRCACATR_DATASIZE_S) +#define TRCACATR_DATASIZE_W (0x2 << TRCACATR_DATASIZE_S) +#define TRCACATR_DATASIZE_DW (0x3 << TRCACATR_DATASIZE_S) +#define TRCACATR_DATAMATCH_S 16 +#define TRCACATR_DATAMATCH_M (0x3 << TRCACATR_DATAMATCH_S) +#define TRCACATR_EXLEVEL_S_S 8 +#define TRCACATR_EXLEVEL_S_M (0xf << TRCACATR_EXLEVEL_S_S) +#define TRCACATR_EXLEVEL_S(n) (0x1 << ((n) + TRCACATR_EXLEVEL_S_S)) +#define TRCACATR_EXLEVEL_NS_S 12 +#define TRCACATR_EXLEVEL_NS_M (0xf << TRCACATR_EXLEVEL_NS_S) +#define TRCACATR_EXLEVEL_NS(n) (0x1 << ((n) + TRCACATR_EXLEVEL_NS_S)) +#define TRCDVCVR(n) (0x500 + (n) * 0x8) /* Trace Data Value Comparator Value Register [n=0-7] */ +#define TRCDVCMR(n) (0x580 + (n) * 0x8) /* Trace Data Value Comparator Mask Register [n=0-7] */ +#define TRCCIDCVR(n) (0x600 + (n) * 0x8) /* Trace Context ID Comparator Value Register [n=0-7] */ +#define TRCVMIDCVR(n) (0x640 + (n) * 0x8) /* Trace Virtual context identifier Comparator Value [n=0-7] */ +#define TRCCIDCCTLR0 0x680 /* Trace Context ID Comparator Control Register 0 */ +#define TRCCIDCCTLR1 0x684 /* Trace Context ID Comparator Control Register 1 */ +#define TRCVMIDCCTLR0 0x688 /* Trace Virtual context identifier Comparator Control Register 0 */ +#define TRCVMIDCCTLR1 0x68C /* Trace Virtual context identifier Comparator Control Register 1 */ +#define TRCITCTRL 0xF00 /* Management Integration Mode Control register */ +#define TRCCLAIMSET 0xFA0 /* Trace Claim Tag Set register */ +#define TRCCLAIMCLR 0xFA4 /* Trace Claim Tag Clear register */ +#define TRCDEVAFF0 0xFA8 /* Management Device Affinity register 0 */ +#define TRCDEVAFF1 0xFAC /* Management Device Affinity register 1 */ +#define TRCLAR 0xFB0 /* Management Software Lock Access Register */ +#define TRCLSR 0xFB4 /* Management Software Lock Status Register */ +#define TRCAUTHSTATUS 0xFB8 /* Management Authentication Status register */ +#define TRCDEVARCH 0xFBC /* Management Device Architecture register */ +#define TRCDEVID 0xFC8 /* Management Device ID register */ +#define TRCDEVTYPE 0xFCC /* Management Device Type register */ +#define TRCPIDR4 0xFD0 /* Management Peripheral ID4 Register */ +#define TRCPIDR(n) (0xFE0 + (n) * 0x4) /* Management Peripheral IDn Register [n=0-3] */ +#define TRCPIDR567(n) (0xFD4 + ((n) - 5) * 0x4) /* Management Peripheral ID5 to Peripheral ID7 Registers */ +#define TRCCIDR(n) (0xFF0 + (n) * 0x4) /* Management Component IDn Register [n=0-4] */ + +/* ETMv4 resources */ +#define ETM_MAX_NR_PE 8 +#define ETMv4_MAX_CNTR 4 +#define ETM_MAX_SEQ_STATES 4 +#define ETM_MAX_EXT_INP_SEL 4 +#define ETM_MAX_EXT_INP 256 +#define ETM_MAX_EXT_OUT 4 +#define ETM_MAX_SINGLE_ADDR_CMP 16 +#define ETM_MAX_ADDR_RANGE_CMP (ETM_MAX_SINGLE_ADDR_CMP / 2) +#define ETM_MAX_DATA_VAL_CMP 8 +#define ETMv4_MAX_CTXID_CMP 8 +#define ETM_MAX_VMID_CMP 8 +#define ETM_MAX_PE_CMP 8 +#define ETM_MAX_RES_SEL 32 +#define ETM_MAX_SS_CMP 8 + +/** + * struct etmv4_config - configuration information related to an ETMv4 + * @mode: Controls various modes supported by this ETM. + * @pe_sel: Controls which PE to trace. + * @cfg: Controls the tracing options. + * @eventctrl0: Controls the tracing of arbitrary events. + * @eventctrl1: Controls the behavior of the events that @event_ctrl0 selects. + * @stallctl: If functionality that prevents trace unit buffer overflows + * is available. + * @ts_ctrl: Controls the insertion of global timestamps in the + * trace streams. + * @syncfreq: Controls how often trace synchronization requests occur. + * the TRCCCCTLR register. + * @ccctlr: Sets the threshold value for cycle counting. + * @vinst_ctrl: Controls instruction trace filtering. + * @viiectlr: Set or read, the address range comparators. + * @vissctlr: Set, or read, the single address comparators that control the + * ViewInst start-stop logic. + * @vipcssctlr: Set, or read, which PE comparator inputs can control the + * ViewInst start-stop logic. + * @seq_idx: Sequencor index selector. + * @seq_ctrl: Control for the sequencer state transition control register. + * @seq_rst: Moves the sequencer to state 0 when a programmed event occurs. + * @seq_state: Set, or read the sequencer state. + * @cntr_idx: Counter index seletor. + * @cntrldvr: Sets or returns the reload count value for a counter. + * @cntr_ctrl: Controls the operation of a counter. + * @cntr_val: Sets or returns the value for a counter. + * @res_idx: Resource index selector. + * @res_ctrl: Controls the selection of the resources in the trace unit. + * @ss_idx: Single-shot index selector. + * @ss_ctrl: Controls the corresponding single-shot comparator resource. + * @ss_status: The status of the corresponding single-shot comparator. + * @ss_pe_cmp: Selects the PE comparator inputs for Single-shot control. + * @addr_idx: Address comparator index selector. + * @addr_val: Value for address comparator. + * @addr_acc: Address comparator access type. + * @addr_type: Current status of the comparator register. + * @ctxid_idx: Context ID index selector. + * @ctxid_pid: Value of the context ID comparator. + * @ctxid_mask0:Context ID comparator mask for comparator 0-3. + * @ctxid_mask1:Context ID comparator mask for comparator 4-7. + * @vmid_idx: VM ID index selector. + * @vmid_val: Value of the VM ID comparator. + * @vmid_mask0: VM ID comparator mask for comparator 0-3. + * @vmid_mask1: VM ID comparator mask for comparator 4-7. + * @ext_inp: External input selection. + * @s_ex_level: Secure ELs where tracing is supported. + */ + +struct etmv4_config { + uint32_t mode; + uint32_t pe_sel; + uint32_t cfg; + uint32_t eventctrl0; + uint32_t eventctrl1; + uint32_t stall_ctrl; + uint32_t ts_ctrl; + uint32_t syncfreq; + uint32_t ccctlr; + uint32_t bb_ctrl; + uint32_t vinst_ctrl; + uint32_t viiectlr; + uint32_t vissctlr; + uint32_t vipcssctlr; + uint8_t seq_idx; + uint32_t seq_ctrl[ETM_MAX_SEQ_STATES]; + uint32_t seq_rst; + uint32_t seq_state; + uint8_t cntr_idx; + uint32_t cntrldvr[ETMv4_MAX_CNTR]; + uint32_t cntr_ctrl[ETMv4_MAX_CNTR]; + uint32_t cntr_val[ETMv4_MAX_CNTR]; + uint8_t res_idx; + uint32_t res_ctrl[ETM_MAX_RES_SEL]; + uint8_t ss_idx; + uint32_t ss_ctrl[ETM_MAX_SS_CMP]; + uint32_t ss_status[ETM_MAX_SS_CMP]; + uint32_t ss_pe_cmp[ETM_MAX_SS_CMP]; + uint8_t addr_idx; + uint64_t addr_val[ETM_MAX_SINGLE_ADDR_CMP]; + uint64_t addr_acc[ETM_MAX_SINGLE_ADDR_CMP]; + uint8_t addr_type[ETM_MAX_SINGLE_ADDR_CMP]; + uint8_t ctxid_idx; + uint64_t ctxid_pid[ETMv4_MAX_CTXID_CMP]; + uint32_t ctxid_mask0; + uint32_t ctxid_mask1; + uint8_t vmid_idx; + uint64_t vmid_val[ETM_MAX_VMID_CMP]; + uint32_t vmid_mask0; + uint32_t vmid_mask1; + uint32_t ext_inp; + uint8_t s_ex_level; +}; + +extern struct trace_dev_methods cs_methods; + +#endif /* !_HWT_CORESIGHT_H_ */ Index: usr.sbin/hwt/hwt_coresight.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_coresight.c @@ -0,0 +1,865 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* ARM CoreSight tracing unit. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "hwt.h" +#include "hwt_coresight.h" + +#include "libpmcstat_stubs.h" +#include +#include + +#define HWT_CORESIGHT_DEBUG +#undef HWT_CORESIGHT_DEBUG + +#ifdef HWT_CORESIGHT_DEBUG +#define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define dprintf(fmt, ...) +#endif + +static int cs_flags = 0; +#define FLAG_FORMAT (1 << 0) +#define FLAG_FRAME_RAW_UNPACKED (1 << 1) +#define FLAG_FRAME_RAW_PACKED (1 << 2) + +#define PACKET_STR_LEN 1024 +static char packet_str[PACKET_STR_LEN]; + +struct cs_decoder { + dcd_tree_handle_t dcdtree_handle; + struct trace_context *tc; + int dp_ret; + int cpu_id; + FILE *out; + xo_handle_t *xop; +}; + +static int +hwt_coresight_mmap(struct trace_context *tc, + struct hwt_record_user_entry *entry __unused) +{ + char filename[32]; + int tid; + + /* Coresight maps memory only from the first CPU */ + if (tc->mode == HWT_MODE_CPU) + tid = CPU_FFS(&tc->cpu_map) - 1; + else /* HWT_MODE_THREAD */ + tid = 0; + + sprintf(filename, "/dev/hwt_%d_%d", tc->ident, tid); + + tc->thr_fd = open(filename, O_RDONLY); + if (tc->thr_fd < 0) { + printf("Can't open %s\n", filename); + return (-1); + } + + tc->base = mmap(NULL, tc->bufsize, PROT_READ, MAP_SHARED, tc->thr_fd, + 0); + if (tc->base == MAP_FAILED) { + printf("mmap failed: err %d\n", errno); + return (-1); + } + + printf("%s: tc->base %p\n", __func__, tc->base); + + return (0); +} + +static ocsd_err_t +attach_raw_printers(dcd_tree_handle_t dcd_tree_h) +{ + ocsd_err_t err; + int flags; + + flags = 0; + err = OCSD_OK; + + if (cs_flags & FLAG_FRAME_RAW_UNPACKED) + flags |= OCSD_DFRMTR_UNPACKED_RAW_OUT; + + if (cs_flags & FLAG_FRAME_RAW_PACKED) + flags |= OCSD_DFRMTR_PACKED_RAW_OUT; + + if (flags) + err = ocsd_dt_set_raw_frame_printer(dcd_tree_h, flags); + + return err; +} + +static int +print_data_array(const uint8_t *p_array, const int array_size, + char *p_buffer, int buf_size) +{ + int bytes_processed; + int chars_printed; + + chars_printed = 0; + p_buffer[0] = 0; + + if (buf_size > 9) { + strcat(p_buffer, "[ "); + chars_printed += 2; + + for (bytes_processed = 0; bytes_processed < array_size; + bytes_processed++) { + sprintf(p_buffer + chars_printed, "0x%02X ", + p_array[bytes_processed]); + chars_printed += 5; + if ((chars_printed + 5) > buf_size) + break; + } + + strcat(p_buffer, "];"); + chars_printed += 2; + } else if (buf_size >= 4) { + sprintf(p_buffer, "[];"); + chars_printed += 3; + } + + return (chars_printed); +} + +static void +packet_monitor(void *context __unused, + const ocsd_datapath_op_t op, + const ocsd_trc_index_t index_sop, + const void *p_packet_in, + const uint32_t size, + const uint8_t *p_data) +{ + int offset; + + offset = 0; + + switch (op) { + case OCSD_OP_DATA: + sprintf(packet_str, "Idx:%" OCSD_TRC_IDX_STR ";", index_sop); + offset = strlen(packet_str); + offset += print_data_array(p_data, size, packet_str + offset, + PACKET_STR_LEN - offset); + + /* + * Got a packet -- convert to string and use the libraries' + * message output to print to file and stdoout + */ + + if (ocsd_pkt_str(OCSD_PROTOCOL_ETMV4I, p_packet_in, + packet_str + offset, PACKET_STR_LEN - offset) == OCSD_OK) { + /* add in */ + if (strlen(packet_str) == PACKET_STR_LEN - 1)/*maxlen*/ + packet_str[PACKET_STR_LEN - 2] = '\n'; + else + strcat(packet_str,"\n"); + + /* print it using the library output logger. */ + ocsd_def_errlog_msgout(packet_str); + } + break; + + case OCSD_OP_EOT: + sprintf(packet_str,"**** END OF TRACE ****\n"); + ocsd_def_errlog_msgout(packet_str); + break; + default: + printf("%s: unknown op %d\n", __func__, op); + break; + } +} + +static ocsd_err_t +create_test_memory_acc(dcd_tree_handle_t handle, struct trace_context *tc) +{ + ocsd_vaddr_t address; + uint8_t *p_mem_buffer; + uint32_t mem_length; +#ifdef HWT_CORESIGHT_DEBUG + uint64_t *t; +#endif + int ret; + + dprintf("%s\n", __func__); + + address = (ocsd_vaddr_t)tc->base; + +#ifdef HWT_CORESIGHT_DEBUG + t = (uint64_t *)tc->base; + printf("%lx %lx %lx %lx\n", t[0], t[1], t[2], t[3]); +#endif + + p_mem_buffer = (uint8_t *)tc->base; + mem_length = tc->bufsize; + + ret = ocsd_dt_add_buffer_mem_acc(handle, address, + OCSD_MEM_SPACE_ANY, p_mem_buffer, mem_length); + if (ret != OCSD_OK) { + printf("%s: can't create memory accessor: ret %d\n", + __func__, ret); + return (ENXIO); + } + + return (ret); +} + +static ocsd_err_t +create_generic_decoder(dcd_tree_handle_t handle, const char *p_name, + const void *p_cfg, const void *p_context __unused, + struct trace_context *tc) +{ + ocsd_err_t ret; + uint8_t CSID; + + CSID = 0; + + dprintf("%s\n", __func__); + + ret = ocsd_dt_create_decoder(handle, p_name, + OCSD_CREATE_FLG_FULL_DECODER, p_cfg, &CSID); + if (ret != OCSD_OK) + return (-1); + + printf("%s: CSID to decode: %d.\n", __func__, CSID); + + if (cs_flags & FLAG_FORMAT) { + ret = ocsd_dt_attach_packet_callback(handle, CSID, + OCSD_C_API_CB_PKT_MON, packet_monitor, p_context); + if (ret != OCSD_OK) + return (-1); + } + + /* attach a memory accessor */ + ret = create_test_memory_acc(handle, tc); + if (ret != OCSD_OK) { + ocsd_dt_remove_decoder(handle, CSID); + return (ENXIO); + } + + return (ret); +} + +static ocsd_err_t +create_decoder_etmv4(struct trace_context *tc, dcd_tree_handle_t dcd_tree_h, + int thread_id) +{ + struct etmv4_config *config; + ocsd_etmv4_cfg trace_config; + uint32_t id_regs[14]; + size_t regsize; + ocsd_err_t ret; + char name[32]; + int error; + int i; + + config = tc->config; + + trace_config.arch_ver = ARCH_V8; + trace_config.core_prof = profile_CortexA; + trace_config.reg_configr = config->cfg; + /* TODO: take thread_id from config ? */ + trace_config.reg_traceidr = thread_id + 1; + + for (i = 0; i < 14; i++) { + snprintf(name, 32, "dev.coresight_etm4x.0.idr%d", i); + regsize = sizeof(id_regs[i]); + error = sysctlbyname(name, &id_regs[i], ®size, NULL, 0); + if (error) { + printf("Error: could not query ETMv4\n"); + return (error); + } + } + + trace_config.reg_idr0 = id_regs[0]; + trace_config.reg_idr1 = id_regs[1]; + trace_config.reg_idr2 = id_regs[2]; + trace_config.reg_idr8 = id_regs[8]; + trace_config.reg_idr9 = id_regs[9]; + trace_config.reg_idr10 = id_regs[10]; + trace_config.reg_idr11 = id_regs[11]; + trace_config.reg_idr12 = id_regs[12]; + trace_config.reg_idr13 = id_regs[13]; + + /* Instruction decoder. */ + ret = create_generic_decoder(dcd_tree_h, OCSD_BUILTIN_DCD_ETMV4I, + (void *)&trace_config, 0, tc); + + return (ret); +} + +static int +cs_process_chunk_raw(struct trace_context *tc, size_t start, size_t len, + uint32_t *consumed) +{ + void *base; + + base = (void *)((uintptr_t)tc->base + (uintptr_t)start); + + fwrite(base, len, 1, tc->raw_f); + fflush(tc->raw_f); + + *consumed = len; + + return (0); +} + +static int +cs_process_chunk(struct trace_context *tc, struct cs_decoder *dec, + size_t start, size_t len, uint32_t *consumed) +{ + void *base; + int error; + + /* Coresight data is always on first cpu cdev due to funnelling by HW.*/ + base = (void *)((uintptr_t)tc->base + (uintptr_t)start); + + dprintf("Processing data for CPU%d\n", dec->cpu_id); + + error = ocsd_dt_process_data(dec->dcdtree_handle, + OCSD_OP_DATA, start, len, base, consumed); + + if (*consumed != len) { + printf("error"); + exit(5); + } + + return (error); +} + +struct pmcstat_pcmap * +pmcstat_process_find_map(struct pmcstat_process *p, uintfptr_t pc) +{ + struct pmcstat_pcmap *ppm; + + TAILQ_FOREACH(ppm, &p->pp_map, ppm_next) { + if (pc >= ppm->ppm_lowpc && pc < ppm->ppm_highpc) + return (ppm); + if (pc < ppm->ppm_lowpc) + return (NULL); + } + + return (NULL); +} + +static struct pmcstat_symbol * +symbol_lookup(const struct trace_context *tc, uint64_t ip, + struct pmcstat_image **img, uint64_t *newpc0) +{ + struct pmcstat_image *image; + struct pmcstat_symbol *sym; + struct pmcstat_pcmap *map; + uint64_t newpc; + + map = pmcstat_process_find_map(tc->pp, ip); + if (map != NULL) { + image = map->ppm_image; + newpc = ip - ((unsigned long)map->ppm_lowpc + + (image->pi_vaddr - image->pi_start)); + sym = pmcstat_symbol_search(image, newpc); /* Could be NULL. */ + newpc += image->pi_vaddr; + + *img = image; + *newpc0 = newpc; + + return (sym); + } else + *img = NULL; + + return (NULL); +} + +static void __unused +print_timestamp(const ocsd_generic_trace_elem *elem) +{ + char ts[100]; + + if (elem->timestamp != 0) + sprintf(ts, "ts %ld", elem->timestamp); + else + sprintf(ts, " "); +} + +static ocsd_datapath_resp_t +gen_trace_elem_print_lookup(const void *p_context, + const ocsd_trc_index_t index_sop __unused, + const uint8_t trc_chan_id __unused, + const ocsd_generic_trace_elem *elem) +{ + struct trace_context *tc; + struct pmcstat_image *image; + ocsd_datapath_resp_t resp; + struct pmcstat_symbol *sym; + unsigned long offset; + const struct cs_decoder *dec; + const char *piname; + const char *psname; + uint64_t newpc; + uint64_t ip; + FILE *out; + + dec = (const struct cs_decoder *)p_context; + tc = dec->tc; + out = dec->out; + + resp = OCSD_RESP_CONT; + + dprintf("%s: Idx:%d ELEM TYPE %d, st_addr %lx, en_addr %lx\n", + __func__, index_sop, elem->elem_type, + elem->st_addr, elem->en_addr); + + if (elem->st_addr <= 0) + return (resp); + + ip = elem->st_addr; + + sym = symbol_lookup(tc, ip, &image, &newpc); + + static const char *ARMv8Excep[] = { + "PE Reset", "Debug Halt", "Call", "Trap", + "System Error", "Reserved", "Inst Debug", "Data Debug", + "Reserved", "Reserved", "Alignment", "Inst Fault", + "Data Fault", "Reserved", "IRQ", "FIQ" + }; + + switch (elem->elem_type) { + case OCSD_GEN_TRC_ELEM_UNKNOWN: + fprintf(out, "Unknown packet.\n"); + return (resp); + case OCSD_GEN_TRC_ELEM_NO_SYNC: + fprintf(out, "No sync.\n"); + return (resp); + case OCSD_GEN_TRC_ELEM_TRACE_ON: + /* fprintf(out, "Trace on.\n"); */ + return (resp); + case OCSD_GEN_TRC_ELEM_EO_TRACE: + fprintf(out, "End of Trace.\n"); + return (resp); + case OCSD_GEN_TRC_ELEM_PE_CONTEXT: + break; + case OCSD_GEN_TRC_ELEM_INSTR_RANGE: + case OCSD_GEN_TRC_ELEM_I_RANGE_NOPATH: + return (resp); + case OCSD_GEN_TRC_ELEM_ADDR_NACC: + break; + case OCSD_GEN_TRC_ELEM_ADDR_UNKNOWN: + return (resp); + case OCSD_GEN_TRC_ELEM_EXCEPTION: + fprintf(out, "Exception #%d (%s)\n", elem->exception_number, + ARMv8Excep[elem->exception_number]); + return (resp); + case OCSD_GEN_TRC_ELEM_EXCEPTION_RET: + fprintf(out, "Exception RET to %lx\n", elem->st_addr); + return (resp); + case OCSD_GEN_TRC_ELEM_TIMESTAMP: + fprintf(out, "Timestamp: %lx\n", elem->timestamp); + return (resp); + case OCSD_GEN_TRC_ELEM_CYCLE_COUNT: + fprintf(out, "Cycle count: %d\n", elem->cycle_count); + return (resp); + case OCSD_GEN_TRC_ELEM_EVENT: + case OCSD_GEN_TRC_ELEM_SWTRACE: + case OCSD_GEN_TRC_ELEM_SYNC_MARKER: + case OCSD_GEN_TRC_ELEM_MEMTRANS: + case OCSD_GEN_TRC_ELEM_INSTRUMENTATION: + case OCSD_GEN_TRC_ELEM_CUSTOM: + return (resp); + }; + + + if (sym || image) { + xo_open_instance("entry"); + xo_emit_h(dec->xop, "{:pc/pc 0x%08lx/%x}", ip); + xo_emit_h(dec->xop, " "); + } + + if (image) { + if (tc->mode == HWT_MODE_THREAD) { + xo_emit_h(dec->xop, "{:newpc/(%lx)/%x}", newpc); + xo_emit_h(dec->xop, "\t"); + } + + piname = pmcstat_string_unintern(image->pi_name); + xo_emit_h(dec->xop, "{:piname/%12s/%s}", piname); + } + + if (sym) { + psname = pmcstat_string_unintern(sym->ps_name); + offset = newpc - (sym->ps_start + image->pi_vaddr); + xo_emit_h(dec->xop, "\t"); + xo_emit_h(dec->xop, "{:psname/%s/%s}", psname); + xo_emit_h(dec->xop, "{:offset/+0x%lx/%ju}", offset); + } + + if (sym || image) { + xo_emit_h(dec->xop, "\n"); + xo_close_instance("entry"); + } + + xo_flush(); + + return (resp); +} + +static int +hwt_coresight_init(struct trace_context *tc, struct cs_decoder *dec, + int thread_id) +{ + char filename[MAXPATHLEN]; + int error; + + dec->cpu_id = thread_id; + dec->tc = tc; + dec->dp_ret = OCSD_RESP_CONT; + dec->dcdtree_handle = ocsd_create_dcd_tree(OCSD_TRC_SRC_FRAME_FORMATTED, + OCSD_DFRMTR_FRAME_MEM_ALIGN); + if (dec->dcdtree_handle == C_API_INVALID_TREE_HANDLE) { + printf("can't find dcd tree\n"); + return (-1); + } + + if (tc->filename) { + if (tc->mode == HWT_MODE_CPU) + snprintf(filename, MAXPATHLEN, "%s%d", tc->filename, + dec->cpu_id); + else + snprintf(filename, MAXPATHLEN, "%s", tc->filename); + + dec->out = fopen(filename, "w"); + if (dec->out == NULL) { + printf("could not open %s\n", filename); + return (ENXIO); + } + dec->xop = xo_create_to_file(dec->out, XO_STYLE_TEXT, XOF_WARN); + } else { + dec->out = stdout; + dec->xop = NULL; + } + + if (tc->flag_format) + cs_flags |= FLAG_FORMAT; + +#if 0 + cs_flags |= FLAG_FRAME_RAW_UNPACKED; + cs_flags |= FLAG_FRAME_RAW_PACKED; +#endif + + error = create_decoder_etmv4(tc, dec->dcdtree_handle, thread_id); + if (error != OCSD_OK) { + printf("can't create decoder: tc->base %p\n", tc->base); + return (-2); + } + +#ifdef HWT_CORESIGHT_DEBUG + ocsd_tl_log_mapped_mem_ranges(dec->dcdtree_handle); +#endif + + if (cs_flags & FLAG_FORMAT) + ocsd_dt_set_gen_elem_printer(dec->dcdtree_handle); + else + ocsd_dt_set_gen_elem_outfn(dec->dcdtree_handle, + gen_trace_elem_print_lookup, dec); + + attach_raw_printers(dec->dcdtree_handle); + + return (0); +} + +static void +hwt_coresight_fill_config(struct trace_context *tc, struct etmv4_config *config) +{ + int excp_level; + uint32_t reg; + uint32_t val; + int i; + + memset(config, 0, sizeof(struct etmv4_config)); + + reg = TRCCONFIGR_RS | TRCCONFIGR_TS; + reg |= TRCCONFIGR_CID | TRCCONFIGR_VMID; + reg |= TRCCONFIGR_COND_DIS; + config->cfg = reg; + + config->ts_ctrl = 0; + config->syncfreq = TRCSYNCPR_4K; + + if (tc->mode == HWT_MODE_THREAD) + excp_level = 0; /* User mode. */ + else + excp_level = 2; /* CPU mode. */ + + reg = TRCVICTLR_SSSTATUS; + reg |= (1 << EVENT_SEL_S); + reg |= TRCVICTLR_EXLEVEL_NS(1 << excp_level); + reg |= TRCVICTLR_EXLEVEL_S(1 << excp_level); + config->vinst_ctrl = reg; + + /* Address-range filtering. */ + val = 0; + for (i = 0; i < tc->nranges * 2; i++) { + config->addr_val[i] = tc->addr_ranges[i]; + + reg = TRCACATR_EXLEVEL_S(1 << excp_level); + reg |= TRCACATR_EXLEVEL_NS(1 << excp_level); + config->addr_acc[i] = reg; + + /* Include the range ID. */ + val |= (1 << (TRCVIIECTLR_INCLUDE_S + i / 2)); + } + config->viiectlr = val; +} + +static int +hwt_coresight_set_config(struct trace_context *tc) +{ + struct hwt_set_config sconf; + struct etmv4_config *config; + int error; + + config = malloc(sizeof(struct etmv4_config)); + hwt_coresight_fill_config(tc, config); + + tc->config = config; + + sconf.config = config; + sconf.config_size = sizeof(struct etmv4_config); + sconf.config_version = 1; + sconf.pause_on_mmap = tc->suspend_on_mmap ? 1 : 0; + + error = ioctl(tc->thr_fd, HWT_IOC_SET_CONFIG, &sconf); + + return (error); +} + +static int +cs_process_chunk1(struct trace_context *tc, struct cs_decoder *dec, + size_t cursor, size_t len, uint32_t *processed) +{ + int cpu_id; + int error; + + if (tc->raw) { + error = cs_process_chunk_raw(tc, cursor, len, processed); + return (error); + } else if (tc->mode == HWT_MODE_CPU) { + CPU_FOREACH_ISSET(cpu_id, &tc->cpu_map) { + error = cs_process_chunk(tc, &dec[cpu_id], cursor, len, + processed); + if (error) + return (error); + } + } else { + error = cs_process_chunk(tc, dec, cursor, len, processed); + return (error); + } + + return (0); +} + +static int +hwt_coresight_init1(struct trace_context *tc, struct cs_decoder *dec) +{ + int cpu_id; + int error; + + if (tc->raw) { + /* No decoder needed, just a file for raw data. */ + tc->raw_f = fopen(tc->filename, "w"); + if (tc->raw_f == NULL) { + printf("could not open file %s\n", tc->filename); + return (ENXIO); + } + } else if (tc->mode == HWT_MODE_CPU) { + CPU_FOREACH_ISSET(cpu_id, &tc->cpu_map) { + error = hwt_coresight_init(tc, &dec[cpu_id], cpu_id); + if (error) + return (error); + } + } else { + error = hwt_coresight_init(tc, dec, tc->thread_id); + if (error) + return (error); + } + + return (0); +} + +static void +catch_int(int sig_num __unused) +{ + + printf("Decoder stopped\n"); + exit(0); +} + +static int +cs_get_offs(struct trace_context *tc, size_t *offs) +{ + struct hwt_bufptr_get bget = {0}; + vm_offset_t curpage_offset; + int curpage; + int error; + + bget.ident = &curpage; + bget.offset = &curpage_offset; + bget.data = NULL; + + error = ioctl(tc->thr_fd, HWT_IOC_BUFPTR_GET, &bget); + if (error) + return (error); + + dprintf("curpage %d curpage_offset %ld\n", curpage, curpage_offset); + + *offs = curpage * PAGE_SIZE + curpage_offset; + + return (0); +} + +static int +hwt_coresight_process(struct trace_context *tc) +{ + size_t offs; + size_t new_offs; + int error; + int t; + struct cs_decoder *dec; + uint32_t processed; + size_t cursor; + int len; + size_t totals; + int ncpu; + + xo_open_container("trace"); + xo_open_list("entries"); + + signal(SIGINT, catch_int); + + ocsd_def_errlog_init(OCSD_ERR_SEV_INFO, 1); + + ncpu = hwt_ncpu(); + + dec = malloc(sizeof(struct cs_decoder) * ncpu); + + error = hwt_coresight_init1(tc, dec); + if (error) + return (error); + + error = cs_get_offs(tc, &offs); + if (error) { + printf("%s: cant get offset\n", __func__); + return (-1); + } + + + printf("Decoder started. Press ctrl+c to stop.\n"); + + cursor = 0; + processed = 0; + totals = 0; + len = offs; + + cs_process_chunk1(tc, dec, cursor, len, &processed); + cursor += processed; + totals += processed; + + t = 0; + + while (1) { + error = cs_get_offs(tc, &new_offs); + if (error) + return (-1); + + if (new_offs == cursor) { + /* No new entries in trace. */ + if (tc->terminate && t++ > 2) + break; + hwt_sleep(10); + } else if (new_offs > cursor) { + /* New entries in the trace buffer. */ + len = new_offs - cursor; + cs_process_chunk1(tc, dec, cursor, len, &processed); + cursor += processed; + totals += processed; + t = 0; + + } else if (new_offs < cursor) { + /* New entries in the trace buffer. Buffer wrapped. */ + len = tc->bufsize - cursor; + cs_process_chunk1(tc, dec, cursor, len, &processed); + cursor += processed; + totals += processed; + + cursor = 0; + len = new_offs; + cs_process_chunk1(tc, dec, cursor, len, &processed); + cursor += processed; + totals += processed; + t = 0; + } + } + + printf("\nBytes processed: %ld\n", totals); + + xo_close_list("file"); + xo_close_container("wc"); + if (xo_finish() < 0) + xo_err(EXIT_FAILURE, "stdout"); + + return (0); +} + +struct trace_dev_methods cs_methods = { + .init = NULL, + .mmap = hwt_coresight_mmap, + .process = hwt_coresight_process, + .set_config = hwt_coresight_set_config, +}; Index: usr.sbin/hwt/hwt_elf.h =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_elf.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Bojan Novković + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef HWT_ELF_H_ +#define HWT_ELF_H_ + +/* + * Metadata for the .text loaded from executable + * 'path' into the traced process's address space. + */ +struct hwt_exec_img { + size_t size; + uint64_t offs; + uint64_t addr; + const char *path; +}; + +int hwt_elf_count_libs(const char *elf_path, uint32_t *nlibs0); +int hwt_elf_get_text_offs(const char *elf_path, uint64_t *offs); + +#endif /* HWT_ELF_H_ */ Index: usr.sbin/hwt/hwt_elf.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_elf.c @@ -0,0 +1,196 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwt.h" +#include "hwt_elf.h" +#include "sys/hwt_record.h" + +#include "libpmcstat_stubs.h" +#include + +int +hwt_elf_count_libs(const char *elf_path, uint32_t *nlibs0) +{ + GElf_Shdr shdr; + GElf_Phdr ph; + GElf_Ehdr eh; + Elf_Scn *scn; + Elf *elf; + size_t sh_entsize; + Elf_Data *data; + GElf_Dyn dyn; + int is_dynamic; + uint32_t nlibs; + int fd; + size_t i; + + nlibs = 0; + + assert(elf_version(EV_CURRENT) != EV_NONE); + + fd = open(elf_path, O_RDONLY, 0); + + assert(fd >= 0); + + elf = elf_begin(fd, ELF_C_READ, NULL); + + assert(elf != NULL); + assert(elf_kind(elf) == ELF_K_ELF); + + if (gelf_getehdr(elf, &eh) != &eh) { + printf("could not find elf header\n"); + return (-1); + } + + if (eh.e_type != ET_EXEC && eh.e_type != ET_DYN) { + printf("unsupported image\n"); + return (-2); + } + + if (eh.e_ident[EI_CLASS] != ELFCLASS32 && + eh.e_ident[EI_CLASS] != ELFCLASS64) + return (-3); + + is_dynamic = 0; + + for (i = 0; i < eh.e_phnum; i++) { + if (gelf_getphdr(elf, i, &ph) != &ph) { + printf("could not get program header %zu\n", i); + return (-4); + } + switch (ph.p_type) { + case PT_DYNAMIC: + is_dynamic = 1; + break; + case PT_INTERP: + nlibs++; + break; + } + } + + if (!is_dynamic) + goto done; + + scn = NULL; + data = NULL; + + while ((scn = elf_nextscn(elf, scn)) != NULL) { + assert(gelf_getshdr(scn, &shdr) == &shdr); + + if (shdr.sh_type == SHT_DYNAMIC) { + data = elf_getdata(scn, data); + assert(data != NULL); + + sh_entsize = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT); + + for (i = 0; i < shdr.sh_size / sh_entsize; i++) { + assert(gelf_getdyn(data, i, &dyn) == &dyn); + if (dyn.d_tag == DT_NEEDED) + nlibs++; + } + } + } + +done: + assert(elf_end(elf) == 0); + assert(close(fd) == 0); + + *nlibs0 = nlibs; + + return (0); +} + +int +hwt_elf_get_text_offs(const char *elf_path, uint64_t *offs) +{ + const char *scnname; + GElf_Shdr shdr; + int fd, error; + Elf_Scn *scn; + size_t ndx; + Elf *elf; + + elf = NULL; + scn = NULL; + fd = open(elf_path, O_RDONLY, 0); + if (fd < 0) { + warnx("%s: cannot open \"%s\": %s", __func__, elf_path, + elf_errmsg(elf_errno())); + return (-1); + } + if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { + warnx("%s: cannot read \"%s\": %s", __func__, elf_path, + elf_errmsg(elf_errno())); + error = -1; + goto out; + } + + error = -1; + while ((scn = elf_nextscn(elf, scn)) != NULL) { + if (elf_getshdrstrndx(elf, &ndx) != 0) { + warnx("%s: elf_getshdrstrndx failed: %s", __func__, + elf_errmsg(elf_errno())); + goto out; + } + gelf_getshdr(scn, &shdr); + if ((scnname = elf_strptr(elf, ndx, shdr.sh_name)) == NULL) + continue; + + if (strncmp(scnname, ".text", strlen(".text")) == 0) { + error = 0; + *offs = shdr.sh_offset; + break; + } + } + +out: + close(fd); + + return (error); +} Index: usr.sbin/hwt/hwt_process.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_process.c @@ -0,0 +1,141 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwt.h" + +#include "libpmcstat_stubs.h" +#include + +struct pmcstat_image_hash_list pmcstat_image_hash[PMCSTAT_NHASH]; + +int +hwt_process_start(int *sockpair) +{ + int error; + + error = write(sockpair[PARENTSOCKET], "!", 1); + if (error != 1) + return (-1); + + return (0); +} + +static void +hwt_process_onsig(int signo) +{ + pid_t pid; + int status; + + assert(signo == SIGCHLD); + + while ((pid = wait3(&status, WNOHANG, NULL)) > 0) + if (WIFEXITED(status)) + hwt_procexit(pid, WEXITSTATUS(status)); +} + +int +hwt_process_create(int *sockpair, char **cmd, char **env __unused, int *pid0) +{ + char token; + pid_t pid; + int error; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) < 0) + return (-1); + + signal(SIGCHLD, hwt_process_onsig); + siginterrupt(SIGCHLD, 1); + pid = fork(); + + switch (pid) { + case -1: + return (-1); + case 0: + /* Child */ + close(sockpair[PARENTSOCKET]); + + error = write(sockpair[CHILDSOCKET], "+", 1); + if (error != 1) + return (-1); + + error = read(sockpair[CHILDSOCKET], &token, 1); + if (error < 0) + return (-2); + + if (token != '!') + return (-3); + + close(sockpair[CHILDSOCKET]); + + execvp(cmd[0], cmd); + + kill(getppid(), SIGCHLD); + + exit(-3); + default: + /* Parent */ + close(sockpair[CHILDSOCKET]); + break; + } + + *pid0 = pid; + + return (0); +} + +struct pmcstat_process * +hwt_process_alloc(void) +{ + struct pmcstat_process *pp; + + pp = malloc(sizeof(struct pmcstat_process)); + if (pp) + TAILQ_INIT(&pp->pp_map); + + return (pp); +} Index: usr.sbin/hwt/hwt_pt.h =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_pt.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 2023 Bojan Novković + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HWT_PT_H_ +#define _HWT_PT_H_ + +#include + +extern struct trace_dev_methods pt_methods; +#endif /* !_HWT_PT_H_ */ Index: usr.sbin/hwt/hwt_pt.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_pt.c @@ -0,0 +1,687 @@ +/*- + * Copyright (c) 2023 Bojan Novković + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libpmcstat_stubs.h" +#include + +#include "hwt.h" +#include "hwt_elf.h" +#include "hwt_pt.h" + +#define pt_strerror(errcode) pt_errstr(pt_errcode((errcode))) + +/* + * Trace decoder state. + */ +struct pt_dec_ctx { + size_t curoff; + uint64_t ts; + uint64_t curip; + void *tracebuf; + struct pt_block_decoder *dec; + + int id; + RB_ENTRY(pt_dec_ctx) entry; + + xo_handle_t *xop; + int dev_fd; +}; + +typedef void ( + *pt_ctx_iter_cb)(struct trace_context *, struct pt_dec_ctx *, void *); +static int pt_ctx_compare(const void *n1, const void *n2); + +/* + * Active decoder states. + */ +static struct pt_image_section_cache *pt_iscache; +static struct pt_dec_ctx *cpus; +static RB_HEAD(threads, pt_dec_ctx) threads; +RB_GENERATE_STATIC(threads, pt_dec_ctx, entry, pt_ctx_compare); + +static int +pt_ctx_compare(const void *n1, const void *n2) +{ + const struct pt_dec_ctx *c1 = n1; + const struct pt_dec_ctx *c2 = n2; + + return (c1->id < c2->id ? -1 : c1->id > c2->id ? 1 : 0); +} + +/* + * Iterate over all active decoders and invoked provided callback. + */ +static void +pt_foreach_ctx(struct trace_context *tc, pt_ctx_iter_cb callback, void *arg) +{ + int cpu_id; + struct pt_dec_ctx *dctx; + + switch (tc->mode) { + case HWT_MODE_CPU: + CPU_FOREACH_ISSET(cpu_id, &tc->cpu_map) { + callback(tc, &cpus[cpu_id], arg); + } + break; + case HWT_MODE_THREAD: + RB_FOREACH(dctx, threads, &threads) { + callback(tc, dctx, arg); + } + break; + default: + errx(EXIT_FAILURE, "%s: unknown mode %d\n", __func__, tc->mode); + break; + } +} + +/* + * Initialize a trace decoder. + * Invoked as a callback from 'pt_foreach_ctx'. + */ +static void +pt_cpu_ctx_init_cb(struct trace_context *tc, struct pt_dec_ctx *dctx, + void *arg __unused) +{ + int cpu_id, fd; + char filename[32]; + struct pt_config config; + + cpu_id = dctx - cpus; + sprintf(filename, "/dev/hwt_%d_%d", tc->ident, cpu_id); + fd = open(filename, O_RDONLY); + if (fd < 0) { + errx(EXIT_FAILURE, "Can't open %s\n", filename); + } + + /* + * thr_fd is used to issue ioctls which control all + * cores use fd to the first cpu for this (thread is + * always 0). + */ + if (tc->thr_fd == 0) { + tc->thr_fd = fd; + } + dctx->tracebuf = mmap(NULL, tc->bufsize, PROT_READ, MAP_SHARED, fd, 0); + if (dctx->tracebuf == MAP_FAILED) { + errx(EXIT_FAILURE, + "%s: failed to map tracing buffer for cpu %d: %s\n", + __func__, cpu_id, strerror(errno)); + } + dctx->id = cpu_id; + + if (!tc->raw) { + memset(&config, 0, sizeof(config)); + config.size = sizeof(config); + config.begin = dctx->tracebuf; + config.end = (uint8_t *)dctx->tracebuf + tc->bufsize; + + dctx->dec = pt_blk_alloc_decoder(&config); + if (dctx->dec == NULL) { + printf("%s: failed to allocate PT decoder for CPU %d\n", + __func__, cpu_id); + free(dctx); + return; + } + } +} + +/* + * Add a new ELF section to the trace decoder. + * Invoked as a callback from 'pt_foreach_ctx'. + */ +static void +pt_update_image_cb(struct trace_context *tc __unused, struct pt_dec_ctx *dctx, + void *arg) +{ + int isid; + int error; + struct pt_image *image; + + isid = *(int *)arg; + image = pt_blk_get_image(dctx->dec); + error = pt_image_add_cached(image, pt_iscache, isid, NULL); + if (error) + errx(EXIT_FAILURE, + "%s: failed to add cached section to decoder image: %s", + __func__, pt_strerror(error)); +} + +/* + * Add all ELF sections to the trace decoder. + * Invoked as a callback from 'pt_foreach_ctx'. + */ +static int +hwt_pt_image_load_cb(struct trace_context *tc, struct hwt_exec_img *img) +{ + int isid; + + if (tc->raw) + return (0); + isid = pt_iscache_add_file(pt_iscache, img->path, img->offs, img->size, + img->addr); + if (isid < 0) { + printf("%s: error adding file '%s' to the section cache: %s\n", + __func__, img->path, pt_strerror(isid)); + return (-1); + } + + pt_foreach_ctx(tc, pt_update_image_cb, &isid); + + return (0); +} + +static int +hwt_pt_init(struct trace_context *tc) +{ + + /* Buffer size must be power of two. */ + assert((tc->bufsize & (tc->bufsize - 1)) == 0); + + if (tc->raw) { + /* No decoder needed, just a file for raw data. */ + tc->raw_f = fopen(tc->filename, "w"); + if (tc->raw_f == NULL) { + printf("%s: could not open file %s\n", __func__, + tc->filename); + return (ENXIO); + } + } + + pt_iscache = pt_iscache_alloc(tc->image_name); + if (pt_iscache == NULL) + errx(EXIT_FAILURE, "%s: failed to allocate section cache", + __func__); + + switch (tc->mode) { + case HWT_MODE_CPU: + cpus = calloc(hwt_ncpu(), sizeof(struct pt_dec_ctx)); + if (!cpus) { + printf("%s: failed to allocate decoders\n", __func__); + return (ENOMEM); + } + break; + case HWT_MODE_THREAD: + RB_INIT(&threads); + break; + default: + printf("%s: invalid tracing mode %d\n", __func__, tc->mode); + return (EINVAL); + } + + return (0); +} + +/* + * Map and initialize the tracing buffer. + * Called whenever a new traced thread gets created or + * when HWT_MODE_CPU tracing is started. + */ +static int +hwt_pt_mmap(struct trace_context *tc, struct hwt_record_user_entry *rec) +{ + int tid, fd; + char filename[32]; + struct pt_config config; + struct pt_image *srcimg, *dstimg; + struct pt_dec_ctx *dctx, *srcctx; + + switch (tc->mode) { + case HWT_MODE_CPU: + pt_foreach_ctx(tc, pt_cpu_ctx_init_cb, NULL); + break; + case HWT_MODE_THREAD: + if (rec == NULL) { + /* Have we already mapped the first thread? */ + if (tc->thr_fd != 0) + return (EINVAL); + tid = 0; + } else { + tid = rec->thread_id; + } + sprintf(filename, "/dev/hwt_%d_%d", tc->ident, tid); + fd = open(filename, O_RDONLY); + if (fd < 0) { + printf("Can't open %s\n", filename); + return (-1); + } + if (tc->thr_fd == 0) { + tc->thr_fd = fd; + } + dctx = calloc(1, sizeof(*dctx)); + if (dctx == NULL) + return (ENOMEM); + dctx->dev_fd = fd; + dctx->tracebuf = mmap(NULL, tc->bufsize, PROT_READ, MAP_SHARED, + fd, 0); + if (dctx->tracebuf == MAP_FAILED) { + printf( + "%s: failed to map tracing buffer for thread %d: %s\n", + __func__, tid, strerror(errno)); + free(dctx); + return (ENOMEM); + } + dctx->id = tid; + if (!tc->raw) { + /* + * Grab another context, if any, and copy its decoder + * image. + */ + if (!RB_EMPTY(&threads)) { + srcctx = RB_ROOT(&threads); + srcimg = pt_blk_get_image(srcctx->dec); + dstimg = pt_blk_get_image(dctx->dec); + pt_image_copy(dstimg, srcimg); + } + memset(&config, 0, sizeof(config)); + config.size = sizeof(config); + config.begin = dctx->tracebuf; + config.end = (uint8_t *)dctx->tracebuf + tc->bufsize; + + dctx->dec = pt_blk_alloc_decoder(&config); + if (dctx->dec == NULL) { + printf( + "%s: failed to allocate PT decoder for thread\n", + __func__); + free(dctx); + return (ENOMEM); + } + } + RB_INSERT(threads, &threads, dctx); + break; + default: + return (EINVAL); + } + + return (0); +} + +static int +hwt_pt_set_config(struct trace_context *tc) +{ + struct hwt_set_config sconf; + struct pt_cpu_config *config; + int i, error; + uint64_t rtit_ctl = 0; + + config = calloc(1, sizeof(struct pt_cpu_config)); + /* Fill config */ + if (tc->mode == HWT_MODE_THREAD) + rtit_ctl |= RTIT_CTL_USER; + else + rtit_ctl |= RTIT_CTL_OS; + + if (tc->nranges) { + /* IP range filtering. */ + config->nranges = tc->nranges; + for (i = 0; i < tc->nranges; i++) { + config->ip_ranges[i].start = tc->addr_ranges[i * 2]; + config->ip_ranges[i].end = tc->addr_ranges[i * 2 + 1]; + } + } + + rtit_ctl |= RTIT_CTL_BRANCHEN; + + config->rtit_ctl = rtit_ctl; + tc->config = config; + + sconf.config = config; + sconf.config_size = sizeof(struct pt_config); + sconf.config_version = 1; + sconf.pause_on_mmap = tc->suspend_on_mmap ? 1 : 0; + + error = ioctl(tc->thr_fd, HWT_IOC_SET_CONFIG, &sconf); + + return (error); +} + +static void +hwt_pt_print(struct trace_context *tc, struct pt_dec_ctx *dctx, uint64_t ip) +{ + uint64_t newpc; + unsigned long offset; + struct pmcstat_symbol *sym; + struct pmcstat_image *image; + const char *piname; + const char *psname; + + sym = hwt_sym_lookup(tc, ip, &image, &newpc); + if (sym || image) { + xo_emit_h(dctx->xop, "{:type/%s} {:id/%d}\t", + tc->mode == HWT_MODE_CPU ? "CPU" : "thr", dctx->id); + xo_emit_h(dctx->xop, "{:pc/pc 0x%08lx/%x}", ip); + xo_emit_h(dctx->xop, " "); + } + + if (image) { + if (tc->mode == HWT_MODE_THREAD) { + xo_emit_h(dctx->xop, "{:newpc/(%lx)/%x}", newpc); + xo_emit_h(dctx->xop, "\t"); + } + + piname = pmcstat_string_unintern(image->pi_name); + xo_emit_h(dctx->xop, "{:piname/%12s/%s}", piname); + } + + if (sym) { + psname = pmcstat_string_unintern(sym->ps_name); + offset = newpc - + (sym->ps_start + (image != NULL ? image->pi_vaddr : 0)); + xo_emit_h(dctx->xop, "\t"); + xo_emit_h(dctx->xop, "{:psname/%s/%s}", psname); + xo_emit_h(dctx->xop, "{:offset/+0x%lx/%ju}", offset); + } + + if (sym || image) { + xo_emit_h(dctx->xop, "\n"); + xo_close_instance("entry"); + } +} + +static int +hwt_pt_decode_chunk(struct trace_context *tc, struct pt_dec_ctx *dctx, + uint64_t start, size_t len, uint64_t *processed) +{ + int ret; + int error = 0; + struct pt_block blk; + struct pt_event event; + uint64_t oldoffs, offs; + struct pt_block_decoder *dec; + + dec = dctx->dec; + offs = start; + /* Set decoder to current offset. */ + ret = pt_blk_sync_set(dec, start); + blk.ip = 0; + do { + /* Process any pending events. */ + while (ret & pts_event_pending) { + ret = pt_blk_event(dec, &event, sizeof(event)); + pt_blk_get_offset(dec, &offs); + if (offs >= (start + len)) + break; + } + ret = pt_blk_next(dec, &blk, sizeof(blk)); + if (ret < 0) { + if (ret == -pte_eos) { + /* Restore to last valid offset. */ + pt_blk_sync_backward(dec); + error = 0; + break; + } + + ret = pt_blk_sync_forward(dec); + oldoffs = offs; + pt_blk_get_offset(dec, &offs); + if (ret < 0 || offs >= (start + len) || + offs <= oldoffs) { + if (ret < 0 && ret != -pte_eos) { + error = ret; + printf("ip: %p\n", (void *)blk.ip); + printf( + "%s: error decoding next instruction: %s\n", + __func__, pt_strerror(error)); + } + break; + } + } + pt_blk_get_offset(dec, &offs); + /* Print new symbol offset. */ + hwt_pt_print(tc, dctx, blk.ip); + } while (offs < (start + len)); + pt_blk_get_offset(dec, &offs); + printf("len %p, offs %p\n", (void *)len, (void *)offs); + *processed = offs - start; + + return (error); +} + +/* + * Dumps raw packet bytes into tc->raw_f. + */ +static int +hwt_pt_dump_chunk(struct pt_dec_ctx *dctx, FILE *raw_f, uint64_t offs, + size_t len, uint64_t *processed) +{ + void *base; + + base = (void *)((uintptr_t)dctx->tracebuf + (uintptr_t)offs); + fwrite(base, len, 1, raw_f); + fflush(raw_f); + + *processed = len; + + return (0); +} + +static int +pt_process_chunk(struct trace_context *tc, struct pt_dec_ctx *dctx, + uint64_t offs, size_t len, uint64_t *processed) +{ + if (tc->raw) { + return ( + hwt_pt_dump_chunk(dctx, tc->raw_f, offs, len, processed)); + } else { + return (hwt_pt_decode_chunk(tc, dctx, offs, len, processed)); + } +} + +static struct pt_dec_ctx * +pt_get_decoder_ctx(struct trace_context *tc, int ctxid) +{ + switch (tc->mode) { + case HWT_MODE_CPU: + assert(ctxid < hwt_ncpu()); + return (&cpus[ctxid]); + case HWT_MODE_THREAD: { + struct pt_dec_ctx srch; + srch.id = ctxid; + return (RB_FIND(threads, &threads, &srch)); + } + default: + break; + } + + return (NULL); +} + +/* + * Re-sync buffer when wrap is detected. + */ +static void +pt_ctx_sync_ts(struct trace_context *tc, struct pt_dec_ctx *dctx, + uint64_t newts) +{ + size_t newoff; + + newoff = (newts - dctx->ts) & (tc->bufsize - 1); + + dctx->ts = newts; + dctx->curoff = newoff; +} + +static int +pt_process_data(struct trace_context *tc, struct pt_dec_ctx *dctx, uint64_t ts) +{ + int error; + uint64_t processed; + size_t newoff, curoff, len; + + /* + * Check if the buffer wrapped since + * the last time we processed it + * and try to resync. + */ + if ((ts - dctx->ts) > tc->bufsize) { + printf( + "%s: WARNING: buffer wrapped - re-syncing to last known offset\n", + __func__); + pt_ctx_sync_ts(tc, dctx, ts); + return (0); + } + + len = ts - dctx->ts; + curoff = dctx->curoff; + newoff = (curoff + len) % tc->bufsize; + + if (newoff > curoff) { + /* New entries in the trace buffer. */ + len = newoff - curoff; + error = pt_process_chunk(tc, dctx, curoff, len, &processed); + if (error != 0) { + return (error); + } + dctx->curoff += processed; + dctx->ts += processed; + + } else if (newoff < curoff) { + /* New entries in the trace buffer. Buffer wrapped. */ + len = tc->bufsize - curoff; + error = pt_process_chunk(tc, dctx, curoff, len, &processed); + if (error != 0) { + return (error); + } + + dctx->curoff += processed; + dctx->ts += processed; + + curoff = 0; + len = newoff; + error = pt_process_chunk(tc, dctx, curoff, len, &processed); + if (error != 0) { + return (error); + } + + dctx->curoff = processed; + dctx->ts += processed; + } + + return (0); +} + +/* + * Decode part of the tracing buffer. + */ +static int +hwt_pt_process_buffer(struct trace_context *tc, int id, int curpage, + vm_offset_t offset) +{ + int error; + uint64_t ts; + struct pt_dec_ctx *dctx; + + dctx = pt_get_decoder_ctx(tc, id); + if (dctx == NULL) { + printf("%s: unable to find decorder context for ID %d\n", + __func__, id); + + return (-1); + } + + ts = (curpage * PAGE_SIZE) + offset; + error = pt_process_data(tc, dctx, ts); + if (error) { + return (error); + } + + return (0); +} + +/* + * Fetch last valid trace offset for 'dev_fd'. + */ +static int +pt_get_offs(int dev_fd, uint64_t *offs) +{ + struct hwt_bufptr_get bget; + vm_offset_t offset; + int curpage; + int error; + + bget.ident = &curpage; + bget.offset = &offset; + error = ioctl(dev_fd, HWT_IOC_BUFPTR_GET, &bget); + if (error) + return (error); + *offs = curpage * PAGE_SIZE + offset; + + return (0); +} + +static void +pt_ctx_shutdown_cb(struct trace_context *tc, struct pt_dec_ctx *dctx, + void *arg __unused) +{ + int error; + uint64_t ts; + + error = pt_get_offs(dctx->dev_fd, &ts); + if (error) + errx(EXIT_FAILURE, "pt_get_offs"); + error = pt_process_data(tc, dctx, ts); + if (error) + errx(EXIT_FAILURE, "pt_process_data"); +} + +static void +hwt_pt_shutdown(struct trace_context *tc) +{ + pt_foreach_ctx(tc, pt_ctx_shutdown_cb, NULL); +} + +struct trace_dev_methods pt_methods = { + .init = hwt_pt_init, + .shutdown = hwt_pt_shutdown, + .mmap = hwt_pt_mmap, + .process_buffer = hwt_pt_process_buffer, + .set_config = hwt_pt_set_config, + .image_load_cb = hwt_pt_image_load_cb +}; Index: usr.sbin/hwt/hwt_record.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_record.c @@ -0,0 +1,195 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwt.h" +#include "hwt_elf.h" + +#include "libpmcstat_stubs.h" +#include + +#define HWT_RECORD_DEBUG +#undef HWT_RECORD_DEBUG + +#ifdef HWT_RECORD_DEBUG +#define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define dprintf(fmt, ...) +#endif + +static int +hwt_record_to_elf_img(struct trace_context *tc, + struct hwt_record_user_entry *entry, struct hwt_exec_img *img) +{ + pmcstat_interned_string path; + struct pmcstat_image *image; + struct pmc_plugins plugins; + struct pmcstat_args args; + unsigned long addr; + char imagepath[PATH_MAX]; + + dprintf(" path %s addr %lx\n", entry->fullpath, + (unsigned long)entry->addr); + + memset(&plugins, 0, sizeof(struct pmc_plugins)); + memset(&args, 0, sizeof(struct pmcstat_args)); + args.pa_fsroot = "/"; + snprintf(imagepath, sizeof(imagepath), "%s/%s", tc->fs_root, + entry->fullpath); + path = pmcstat_string_intern(imagepath); + + image = pmcstat_image_from_path(path, + !!(entry->record_type == HWT_RECORD_KERNEL), &args, &plugins); + if (image == NULL) + return (-1); + + if (image->pi_type == PMCSTAT_IMAGE_UNKNOWN) + pmcstat_image_determine_type(image, &args); + + if (image->pi_end == 0) { + printf(" image '%s' has no executable sections, skipping\n", + imagepath); + free(image); + image = NULL; + img->size = 0; + return (0); + } + addr = (unsigned long)entry->addr & ~1; + if (entry->record_type != HWT_RECORD_KERNEL) + addr -= (image->pi_start - image->pi_vaddr); + + pmcstat_image_link(tc->pp, image, addr); + dprintf("image pi_vaddr %lx pi_start %lx" + " pi_end %lx pi_entry %lx\n", + (unsigned long)image->pi_vaddr, (unsigned long)image->pi_start, + (unsigned long)image->pi_end, (unsigned long)image->pi_entry); + + if (hwt_elf_get_text_offs(imagepath, &img->offs)) + return (-1); + img->size = image->pi_end - image->pi_start; + img->addr = addr; + img->path = entry->fullpath; + + return (0); +} + +int +hwt_record_fetch(struct trace_context *tc, int *nrecords, int wait) +{ + struct hwt_record_user_entry *entry; + struct hwt_record_get record_get; + struct hwt_exec_img img; + struct hwt_wakeup w; + int nentries; + int error; + int j; + + nentries = 256; + tc->records = malloc(sizeof(struct hwt_record_user_entry) * nentries); + + record_get.records = tc->records; + record_get.nentries = &nentries; + record_get.wait = wait; + + error = ioctl(tc->thr_fd, HWT_IOC_RECORD_GET, &record_get); + if (error != 0) { + printf("RECORD_GET error %d entires %d\n", + error, nentries); + return (error); + } + + dprintf("%s: error %d: nent %d\n", __func__, error, nentries); + + for (j = 0; j < nentries; j++) { + entry = &tc->records[j]; + + switch (entry->record_type) { + case HWT_RECORD_MMAP: + case HWT_RECORD_EXECUTABLE: + case HWT_RECORD_INTERP: + case HWT_RECORD_KERNEL: + hwt_record_to_elf_img(tc, entry, &img); + /* Invoke backend callback, if any. */ + if (tc->trace_dev->methods->image_load_cb != NULL && + (error = tc->trace_dev->methods->image_load_cb(tc, + &img))) { + ioctl(tc->thr_fd, HWT_IOC_WAKEUP, &w); + return (error); + } + + if (entry->record_type != HWT_RECORD_KERNEL) + hwt_mmap_received(tc, entry); + break; + case HWT_RECORD_THREAD_CREATE: + /* Let the backend register the newly created thread. */ + if ((error = tc->trace_dev->methods->mmap(tc, entry)) != + 0) + return (error); + break; + case HWT_RECORD_THREAD_SET_NAME: + case HWT_RECORD_MUNMAP: + break; + case HWT_RECORD_BUFFER: + /* Invoke backend process method. */ + assert(tc->trace_dev->methods->process_buffer != NULL); + if ((error = tc->trace_dev->methods->process_buffer(tc, + entry->buf_id, entry->curpage, + entry->offset)) != 0) + return (error); + default: + break; + } + } + free(tc->records); + tc->records = NULL; + + *nrecords = nentries; + + return (error); +} Index: usr.sbin/hwt/hwt_spe.h =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_spe.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Arm Ltd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HWT_SPE_H_ +#define _HWT_SPE_H_ + +extern struct trace_dev_methods spe_methods; + +struct arm_spe_mmap { + int n; + /* fd[n], *buf[n] */ + int *fds; + void **bufs; + int m; /* CPU_FLS (highest cpu_id in cpuset + 1) */ + /* idx[m] */ + uint16_t *idx; /* mapping between cpu_id + bufs[idx] */ +}; + +#endif /* !_HWT_SPE_H_ */ Index: usr.sbin/hwt/hwt_spe.c =================================================================== --- /dev/null +++ usr.sbin/hwt/hwt_spe.c @@ -0,0 +1,444 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Arm Ltd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Arm Statistical Profiling Extension (SPE) + * + * Note: this userspace tool currently has basic functionality in terms of + * configuration + filtering. It can only dump raw SPE records via the `-w + * filename` command line option. See arm_spe_config for currently available + * config however these are not yet available via command line options. + * + * There is currently no inbuilt decoder but the raw SPE records can be decoded + * with the following tool: + * + * https://gitlab.arm.com/telemetry-solution/telemetry-solution/-/tree/main/tools/spe_parser + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwt.h" +#include "hwt_spe.h" +#include "arm64/spe/arm_spe.h" + +#define HWT_SPE_DEBUG +#undef HWT_SPE_DEBUG + +#ifdef HWT_SPE_DEBUG +#define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else +#define dprintf(fmt, ...) +#endif + +static struct arm_spe_mmap *spe_mmap; +static int kq_fd; + +#ifdef HWT_SPE_DEBUG +static void hex_dump(uint8_t *buf, size_t len) +{ + size_t i; + + printf("--------------------------------------------------------------\n"); + for (i = 0; i < len; ++i) { + if (i % 8 == 0) { + printf(" "); + } + if (i % 16 == 0) { + if (i != 0) { + printf("\r\n"); + } + printf("\t"); + } + printf("%02X ", buf[i]); + } + printf("\r\n"); +} +#else +static void hex_dump(uint8_t *buf __unused, size_t len __unused) {} +#endif + +static int +hwt_map_memory(struct trace_context *tc, int id, int *fd, void **addr) +{ + char filename[32]; + + sprintf(filename, "/dev/hwt_%d_%d", tc->ident, id); + + *fd = open(filename, O_RDONLY); + if (*fd < 0) { + printf("Can't open %s\n", filename); + return (-1); + } + + *addr = mmap(NULL, tc->bufsize, PROT_READ, MAP_SHARED, *fd, 0); + if (*addr == MAP_FAILED) { + printf("mmap failed: err %d\n", errno); + return (-1); + } + + return (0); +} + +static int +hwt_spe_mmap(struct trace_context *tc, + struct hwt_record_user_entry *entry __unused) +{ + int error, cpu_id, i=0; + + spe_mmap = malloc(sizeof(struct arm_spe_mmap)); + if (spe_mmap == NULL) + return (ENOMEM); + + if (tc->mode == HWT_MODE_CPU) + spe_mmap->n = CPU_COUNT(&tc->cpu_map); + else /* HWT_MODE_THREAD */ + spe_mmap->n = 1; + + spe_mmap->m = CPU_FLS(&tc->cpu_map); /* Highest id in cpu_map + 1 */ + /* Maintain a mapping between cpu_id + idx in bufs[idx] */ + spe_mmap->idx = malloc(sizeof(uint16_t) * spe_mmap->m); + spe_mmap->bufs = malloc(sizeof(void *) * spe_mmap->n); + spe_mmap->fds = malloc(sizeof(int) * spe_mmap->n); + if (spe_mmap->bufs == NULL || spe_mmap->fds == NULL) + return (ENOMEM); + dprintf("spe_mmap->bufs:%p, spe_mmap->n:%d\n", spe_mmap->bufs, spe_mmap->n); + + if (tc->mode == HWT_MODE_CPU) { + CPU_FOREACH_ISSET(cpu_id, &tc->cpu_map) { + error = hwt_map_memory(tc, cpu_id, + &spe_mmap->fds[i], &spe_mmap->bufs[i]); + dprintf("cpu_id:%d &spe_mmap->bufs[%d]:%p value:%p\n", cpu_id, i, &spe_mmap->bufs[i], spe_mmap->bufs[i]); + if (error) + return error; + spe_mmap->idx[cpu_id] = i; + i++; + } + } else { /* HWT_MODE_THREAD */ + error = hwt_map_memory(tc, 0, + &spe_mmap->fds[0], &spe_mmap->bufs[0]); + if (error) + return error; + } + + /* + * thr_fd is used to issue ioctls which control all cores + * use fd to the first cpu for this (thread is always 0) + */ + tc->thr_fd = spe_mmap->fds[0]; + + return (0); +} + +static int +hwt_spe_init(struct trace_context *tc) +{ + struct kevent event[3]; + int ret; + + if (tc->raw) { + /* No decoder needed, just a file for raw data. */ + tc->raw_f = fopen(tc->filename, "wb"); + if (tc->raw_f == NULL) { + printf("%s: could not open file %s\n", __func__, + tc->filename); + return (ENXIO); + } + } else { + printf("error: SPE can currently only dump raw data: '-r' " + "flag is required with '-w filename'\n"); + return (ENOTSUP); + } + + /* + * Setup kevent queue to process ARM_SPE_KQ_{...} events from: + * 1. kernel (e.g. buffer full event) + * 2. userspace signal handler + */ + tc->kqueue_fd = kqueue(); + if (tc->kqueue_fd == -1) + err(EXIT_FAILURE, "kqueue() failed"); + + kq_fd = tc->kqueue_fd; /* sig handler needs access to kq via global */ + + EV_SET(&event[0], ARM_SPE_KQ_BUF, EVFILT_USER, EV_ADD | EV_CLEAR, + NOTE_FFCOPY, 0, NULL); + EV_SET(&event[1], ARM_SPE_KQ_SHUTDOWN, EVFILT_USER, EV_ADD | EV_CLEAR, + NOTE_FFCOPY, 0, NULL); + EV_SET(&event[2], ARM_SPE_KQ_SIGNAL, EVFILT_USER, EV_ADD | EV_CLEAR, + NOTE_FFCOPY, 0, NULL); + + ret = kevent(tc->kqueue_fd, event, 3, NULL, 0, NULL); + if (ret == -1) + err(EXIT_FAILURE, "kevent register"); + for (int i = 0; i < 3; i++) { + if (event[i].flags & EV_ERROR) + errx(EXIT_FAILURE, "Event error: %s", + strerror(event[i].data)); + } + + return ret; +} + +static void +sighandler(const int sig_num) +{ + struct kevent kev; + int ret; + + // TODO - handle different signals differently? + switch (sig_num) { + case SIGINT: + printf("SIGINT\n"); /* ctrl+c */ + break; + case SIGTERM: + printf("SIGTERM\n"); + break; + default: + break; + } + + /* Wake up event loop in hwt_spe_process */ + EV_SET(&kev, ARM_SPE_KQ_SIGNAL, EVFILT_USER, 0, + NOTE_TRIGGER | NOTE_FFCOPY, 0, NULL); + ret = kevent(kq_fd, &kev, 1, NULL, 0, NULL); + if (ret == -1) + err(EXIT_FAILURE, "kevent register"); + if (kev.flags & EV_ERROR) + errx(EXIT_FAILURE, "event error: %s", strerror(kev.data)); +} + +static int +hwt_spe_stop_tracing(struct trace_context *tc) +{ + int err; + + err = hwt_stop_tracing(tc); + if (err) + errx(EX_SOFTWARE, "failed to stop tracing, error %d\n", err); + + return err; +} + +static void +hwt_spe_free(void) +{ + free(spe_mmap->idx); + free(spe_mmap->bufs); + free(spe_mmap->fds); + free(spe_mmap); +} + +static int +hwt_spe_get_data(struct trace_context *tc) +{ + + struct hwt_bufptr_get bget; + struct hwt_svc_buf svc_buf; + struct arm_spe_svc_buf binfo; + vm_offset_t buf_start = 0; + size_t size, ret; + uint64_t data; + void *buf; + int ident, idx, error; + bool not_final; + + bget.ident = &ident; + bget.offset = &size; + bget.data = &data; + + /* + * Returns the oldest buffer that needs servicing or errno ENOENT if + * there are no buffers pending + */ + error = ioctl(tc->thr_fd, HWT_IOC_BUFPTR_GET, &bget); + if (error) { + if (errno == ENOENT) /* no items on queue */ + return (0); + + errx(EX_SOFTWARE, "Failed HWT_IOC_BUFPTR_GET err:%d %s\n", + errno, strerror(errno)); + } + + if (ident > (spe_mmap->m - 1)) + return ENXIO; + idx = spe_mmap->idx[ident]; + + + /* Copy from the correct half of the ping pong buffer */ + if ((data & KQ_BUF_POS) == 0) + buf_start = 0; + else + buf_start = tc->bufsize/2; + + /* TODO: once decoder is implemented, if KQ_PARTREC, discard last (partial) record */ + buf = (uint8_t *)spe_mmap->bufs[idx]+buf_start; + hex_dump(buf, 64); + ret = fwrite(buf, 1, size, tc->raw_f); + if (ret < size) + errx(EX_SOFTWARE, "failed to write to file, wrote %zu/%zu bytes\n", + ret, size); + else + tc->bytes_written += ret; + + /* Clear service buffer flag */ + binfo.ident = ident; + binfo.buf_idx = (data & KQ_BUF_POS); + svc_buf.data = &binfo; + svc_buf.data_size = sizeof(binfo); + svc_buf.data_version = 1; + + /* KQ_BUF_FINAL indicates shutdown so no need to clear service flag */ + not_final = !(data & KQ_FINAL_BUF); + if (not_final && ioctl(tc->thr_fd, HWT_IOC_SVC_BUF, &svc_buf)) { + errx(EX_SOFTWARE, "Failed HWT_IOC_SVC_BUF err:%d %s\n", + errno, strerror(errno)); + } + + return error; +} + +static int +hwt_spe_process(struct trace_context *tc) +{ + struct kevent kev; + int ret; + struct sigaction sigact = {0}; + int nevents; + u_int npending; + + sigact.sa_handler = sighandler; + sigact.sa_flags = SA_RESTART; + /* TODO: register/handle other signals */ + sigaction(SIGINT, &sigact, 0); + sigaction(SIGHUP, &sigact, 0); + sigaction(SIGTERM, &sigact, 0); + sigaction(SIGQUIT, &sigact, 0); + + dprintf("%s tc->image_name:%s tc->func_name:%s tc->ident:%d\n", __func__, tc->image_name, tc->func_name, tc->ident); + printf("Press ctrl+c to stop profiling\n"); + + for (;;) { + /* Sleep until something happens. */ + nevents = kevent(tc->kqueue_fd, NULL, 0, &kev, 1, NULL); + if (nevents == -1 && errno != EINTR) + err(EXIT_FAILURE, "kevent wait"); + else if (nevents == -1 && errno == EINTR) + continue; + + dprintf("%s nevents:%d kev.fflags:%x data:%#lx\n", __func__, + nevents, kev.fflags, kev.data); + + if (kev.ident == ARM_SPE_KQ_SIGNAL) { + dprintf("%s ARM_SPE_KQ_SIGNAL\n", __func__); + ret = hwt_spe_stop_tracing(tc); + if (ret) + goto free; + } + if (kev.ident == ARM_SPE_KQ_BUF) { + dprintf("%s ARM_SPE_KQ_BUF npending:%lu\n", __func__, kev.data); + npending = kev.data; + while (npending) { + ret = hwt_spe_get_data(tc); + if (ret) { + printf("hwt_spe_get_data error:%d %s\n", + ret, strerror(errno)); + goto free; + } + npending--; + } + } + if (kev.ident == ARM_SPE_KQ_SHUTDOWN) { + dprintf("%s ARM_SPE_KQ_SHUTDOWN\n", __func__); + goto free; + } + } + +free: + hwt_spe_free(); + + if (tc->raw) + printf("%s %lu bytes written to file %s\n", __func__, + tc->bytes_written, tc->filename); + + if (errno == EINTR) + return 0; + + return ret; +} + +static int +hwt_spe_set_config(struct trace_context *tc) +{ + struct hwt_set_config sconf; + struct arm_spe_config *cfg; + int ret; + + cfg = calloc(1, sizeof(struct arm_spe_config)); + if (cfg == NULL) + return (ENOMEM); + + sconf.config = cfg; + sconf.config_size = sizeof(struct arm_spe_config); + sconf.config_version = 1; + sconf.pause_on_mmap = tc->suspend_on_mmap ? 1 : 0; + + cfg->interval = 4096; + cfg->level = ARM_SPE_KERNEL_AND_USER; + cfg->ctx_field = ARM_SPE_CTX_PID; + + tc->config = cfg; + + ret = ioctl(tc->thr_fd, HWT_IOC_SET_CONFIG, &sconf); + + return ret; +} + +struct trace_dev_methods spe_methods = { + .init = hwt_spe_init, + .mmap = hwt_spe_mmap, + .process = hwt_spe_process, + .set_config = hwt_spe_set_config, +}; Index: usr.sbin/hwt/libpmcstat_stubs.h =================================================================== --- /dev/null +++ usr.sbin/hwt/libpmcstat_stubs.h @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include + +/* These are stubs, needed for compilation only. */ + +#ifndef _HWT_LIBPMCSTAT_STUBS_H_ +#define _HWT_LIBPMCSTAT_STUBS_H_ + +enum pmc_mode { + PMC_MODE_INVALID, +}; + +typedef int pmc_id_t; +typedef int pmc_value_t; + +struct pmclog_ev; + +int pmclog_read(void *cookie __unused, struct pmclog_ev *ev __unused); +int pmc_close_logfile(void); +int pmc_attach(pmc_id_t pmc __unused, pid_t pid __unused); + +#endif /* !_HWT_LIBPMCSTAT_STUBS_H_ */ Index: usr.sbin/hwt/libpmcstat_stubs.c =================================================================== --- /dev/null +++ usr.sbin/hwt/libpmcstat_stubs.c @@ -0,0 +1,58 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "libpmcstat_stubs.h" +#include + +/* This is a stub file, needed for compilation only. */ + +struct pmcstat_pmcs pmcstat_pmcs; +struct pmcstat_process_hash_list pmcstat_process_hash[PMCSTAT_NHASH]; + +int +pmclog_read(void *cookie __unused, struct pmclog_ev *ev __unused) +{ + + return (-1); +} + +int +pmc_close_logfile(void) +{ + + return (-1); +} + +int +pmc_attach(pmc_id_t pmc __unused, pid_t pid __unused) +{ + + return (-1); +}