[PATCH 20/28] ktap: add userspace/kp_parse_events.c
From: Jovi Zhangwei
Date: Fri Mar 28 2014 - 09:56:54 EST
Function 'kp_parse_events' parse event string passed by user,
and return events description structure, it covers:
1). tracepoint
Search tracepoint name through '/sys/kernel/debug/tracing/events/',
and get event id.
2). kprobe
Search symbol name through '/proc/kallsyms', then write event to
'/sys/kernel/debug/tracing/kprobe_events', and read events id.
3). uprobe
Search symbol name through libelf, then write event to
'/sys/kernel/debug/tracing/uprobe_events', then read events id.
4). SDT
Same as uprobe.
All events id will assembly ktap_eventdesc_t structure, and finially
pass to 'kdebug.trace_by_id' function.
Signed-off-by: Jovi Zhangwei <jovi.zhangwei@xxxxxxxxx>
---
tools/ktap/userspace/kp_parse_events.c | 798 +++++++++++++++++++++++++++++++++
1 file changed, 798 insertions(+)
create mode 100644 tools/ktap/userspace/kp_parse_events.c
diff --git a/tools/ktap/userspace/kp_parse_events.c b/tools/ktap/userspace/kp_parse_events.c
new file mode 100644
index 0000000..fa78a68
--- /dev/null
+++ b/tools/ktap/userspace/kp_parse_events.c
@@ -0,0 +1,798 @@
+/*
+ * parse_events.c - ktap events parser
+ *
+ * This file is part of ktap by Jovi Zhangwei.
+ *
+ * Copyright (C) 2012-2014 Jovi Zhangwei <jovi.zhangwei@xxxxxxxxx>.
+ *
+ * ktap is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * ktap is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "../include/ktap_types.h"
+#include "../include/ktap_bc.h"
+#include "kp_symbol.h"
+#include "kp_util.h"
+
+#define TRACING_EVENTS_DIR "/sys/kernel/debug/tracing/events"
+
+static u8 *idmap;
+static int idmap_size = 1024; /* set init size */
+static int id_nr;
+
+static int idmap_init(void)
+{
+ idmap = malloc(idmap_size);
+ if (!idmap)
+ return -1;
+
+ memset(idmap, 0, idmap_size);
+ return 0;
+}
+
+static void idmap_free(void)
+{
+ id_nr = 0;
+ free(idmap);
+}
+
+static inline int idmap_is_set(int id)
+{
+ return idmap[id / 8] & (1 << (id % 8));
+}
+
+static void idmap_set(int id)
+{
+ if (id >= idmap_size * 8) {
+ int newsize = id + 100; /* allocate extra 800 id */
+ idmap = realloc(idmap, newsize);
+ memset(idmap + idmap_size, 0, newsize - idmap_size);
+ idmap_size = newsize;
+ }
+
+ if (!idmap_is_set(id))
+ id_nr++;
+
+ idmap[id / 8] = idmap[id / 8] | (1 << (id % 8));
+}
+
+static void idmap_clear(int id)
+{
+ if (!idmap_is_set(id))
+ return;
+
+ id_nr--;
+ idmap[id / 8] = idmap[id / 8] & ~ (1 << (id % 8));
+}
+
+static int idmap_get_max_id(void)
+{
+ return idmap_size * 8;
+}
+
+static int *get_id_array()
+{
+ int *id_array;
+ int i, j = 0;
+
+ id_array = malloc(sizeof(int) * id_nr);
+ if (!id_array)
+ return NULL;
+
+ for (i = 0; i < idmap_get_max_id(); i++) {
+ if (idmap_is_set(i))
+ id_array[j++] = i;
+ }
+
+ return id_array;
+}
+
+static int add_event(char *evtid_path)
+{
+ char id_buf[24];
+ int id, fd;
+
+ fd = open(evtid_path, O_RDONLY);
+ if (fd < 0) {
+ /*
+ * some tracepoint doesn't have id file, like ftrace,
+ * return success in here, and don't print error.
+ */
+ verbose_printf("warning: cannot open file %s\n", evtid_path);
+ return 0;
+ }
+
+ if (read(fd, id_buf, sizeof(id_buf)) < 0) {
+ fprintf(stderr, "read file error %s\n", evtid_path);
+ close(fd);
+ return -1;
+ }
+
+ id = atoll(id_buf);
+
+ idmap_set(id);
+
+ close(fd);
+ return 0;
+}
+
+static int add_tracepoint(const char *sys_name, const char *evt_name)
+{
+ char evtid_path[PATH_MAX] = {0};
+
+ snprintf(evtid_path, PATH_MAX, "%s/%s/%s/id", TRACING_EVENTS_DIR,
+ sys_name, evt_name);
+ return add_event(evtid_path);
+}
+
+static int parse_events_add_tracepoint(char *sys, char *event)
+{
+ process_available_tracepoints(sys, event, add_tracepoint);
+ return 0;
+}
+
+enum {
+ KPROBE_EVENT,
+ UPROBE_EVENT,
+};
+
+struct probe_list {
+ struct probe_list *next;
+ int type;
+ char event[64];
+};
+
+static struct probe_list *probe_list_head; /* for cleanup resources */
+
+/*
+ * Some symbol format cannot write to uprobe_events in debugfs, like:
+ * symbol "check_one_fd.part.0" in glibc.
+ * For those symbols, we change the format to:
+ * "check_one_fd.part.0" -> "check_one_fd_part_0"
+ */
+static char *format_symbol_name(const char *old_symbol)
+{
+ char *new_name = strdup(old_symbol);
+ char *name = new_name;
+ int changed = 0;
+
+ if (!isalpha(*name) && *name != '_') {
+ *name = '_';
+ changed = 1;
+ }
+
+ while (*++name != '\0') {
+ if (!isalpha(*name) && !isdigit(*name) && *name != '_') {
+ *name = '_';
+ changed = 1;
+ continue;
+ }
+ }
+
+ if (changed)
+ fprintf(stderr,
+ "Warning: symbol \"%s\" transformed to event \"%s\"\n",
+ old_symbol, new_name);
+
+ /* this is a good name */
+ return new_name;
+}
+
+
+#define KPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/kprobe_events"
+
+/**
+ * @return 0 on success, otherwise -1
+ */
+static int
+write_kprobe_event(int fd, int ret_probe, const char *symbol,
+ unsigned long start, char *fetch_args)
+{
+ char probe_event[128] = {0};
+ char event[64] = {0};
+ struct probe_list *pl;
+ char event_id_path[128] = {0};
+ char *symbol_name;
+ int id_fd, ret;
+
+ /* In case some symbols cannot write to uprobe_events debugfs file */
+ symbol_name = format_symbol_name(symbol);
+
+ if (!fetch_args)
+ fetch_args = " ";
+
+ if (ret_probe) {
+ snprintf(event, 64, "ktap_kprobes_%d/ret_%s",
+ getpid(), symbol_name);
+ /* Return probe point must be a symbol */
+ snprintf(probe_event, 128, "r:%s %s %s",
+ event, symbol, fetch_args);
+ } else {
+ snprintf(event, 64, "ktap_kprobes_%d/%s",
+ getpid(), symbol_name);
+ snprintf(probe_event, 128, "p:%s 0x%lx %s",
+ event, start, fetch_args);
+ }
+
+ sprintf(event_id_path, "/sys/kernel/debug/tracing/events/%s/id", event);
+ /* if event id already exist, then don't write to kprobes_event again */
+ id_fd = open(event_id_path, O_RDONLY);
+ if (id_fd > 0) {
+ close(id_fd);
+
+ /* remember add event id to ids_array */
+ ret = add_event(event_id_path);
+ if (ret)
+ goto error;
+
+ goto out;
+ }
+
+ verbose_printf("write kprobe event %s\n", probe_event);
+
+ if (write(fd, probe_event, strlen(probe_event)) <= 0) {
+ fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+ KPROBE_EVENTS_PATH);
+ goto error;
+ }
+
+ /* add to cleanup list */
+ pl = malloc(sizeof(struct probe_list));
+ if (!pl)
+ goto error;
+
+ pl->type = KPROBE_EVENT;
+ pl->next = probe_list_head;
+ memcpy(pl->event, event, 64);
+ probe_list_head = pl;
+
+ ret = add_event(event_id_path);
+ if (ret < 0)
+ goto error;
+
+ out:
+ free(symbol_name);
+ return 0;
+
+ error:
+ free(symbol_name);
+ return -1;
+}
+
+static unsigned long kprobes_text_start;
+static unsigned long kprobes_text_end;
+
+static void init_kprobe_prohibited_area(void)
+{
+ static int once = 0;
+
+ if (once > 0)
+ return;
+
+ once = 1;
+ kprobes_text_start = find_kernel_symbol("__kprobes_text_start");
+ kprobes_text_end = find_kernel_symbol("__kprobes_text_end");
+}
+
+static int check_kprobe_addr_prohibited(unsigned long addr)
+{
+ if (addr >= kprobes_text_start && addr <= kprobes_text_end)
+ return -1;
+
+ return 0;
+}
+
+struct probe_cb_base {
+ int fd;
+ int ret_probe;
+ const char *event;
+ char *binary;
+ char *symbol;
+ char *fetch_args;
+};
+
+static int kprobe_symbol_actor(void *arg, const char *name, char type,
+ unsigned long start)
+{
+ struct probe_cb_base *base = (struct probe_cb_base *)arg;
+
+ /* only can probe text function */
+ if (type != 't' && type != 'T')
+ return -1;
+
+ if (!strglobmatch(name, base->symbol))
+ return -1;
+
+ if (check_kprobe_addr_prohibited(start))
+ return -1;
+
+ /* ignore reture code of write debugfs */
+ write_kprobe_event(base->fd, base->ret_probe, name, start,
+ base->fetch_args);
+
+ return 0; /* success */
+}
+
+static int parse_events_add_kprobe(char *event)
+{
+ char *symbol, *end;
+ struct probe_cb_base base;
+ int fd, ret;
+
+ fd = open(KPROBE_EVENTS_PATH, O_WRONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s\n", KPROBE_EVENTS_PATH);
+ return -1;
+ }
+
+ end = strpbrk(event, "% ");
+ if (end)
+ symbol = strndup(event, end - event);
+ else
+ symbol = strdup(event);
+
+ base.fd = fd;
+ base.ret_probe = !!strstr(event, "%return");
+ base.symbol = symbol;
+ base.fetch_args = strchr(event, ' ');
+
+ init_kprobe_prohibited_area();
+
+ ret = kallsyms_parse(&base, kprobe_symbol_actor);
+ if (ret <= 0) {
+ fprintf(stderr, "cannot parse symbol \"%s\"\n", symbol);
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+
+ free(symbol);
+ close(fd);
+
+ return ret;
+}
+
+#define UPROBE_EVENTS_PATH "/sys/kernel/debug/tracing/uprobe_events"
+
+/**
+ * @return 0 on success, otherwise -1
+ */
+static int
+write_uprobe_event(int fd, int ret_probe, const char *binary,
+ const char *symbol, unsigned long addr,
+ char *fetch_args)
+{
+ char probe_event[128] = {0};
+ char event[64] = {0};
+ struct probe_list *pl;
+ char event_id_path[128] = {0};
+ char *symbol_name;
+ int id_fd, ret;
+
+ /* In case some symbols cannot write to uprobe_events debugfs file */
+ symbol_name = format_symbol_name(symbol);
+
+ if (!fetch_args)
+ fetch_args = " ";
+
+ if (ret_probe) {
+ snprintf(event, 64, "ktap_uprobes_%d/ret_%s",
+ getpid(), symbol_name);
+ snprintf(probe_event, 128, "r:%s %s:0x%lx %s",
+ event, binary, addr, fetch_args);
+ } else {
+ snprintf(event, 64, "ktap_uprobes_%d/%s",
+ getpid(), symbol_name);
+ snprintf(probe_event, 128, "p:%s %s:0x%lx %s",
+ event, binary, addr, fetch_args);
+ }
+
+ sprintf(event_id_path, "/sys/kernel/debug/tracing/events/%s/id", event);
+ /* if event id already exist, then don't write to uprobes_event again */
+ id_fd = open(event_id_path, O_RDONLY);
+ if (id_fd > 0) {
+ close(id_fd);
+
+ /* remember add event id to ids_array */
+ ret = add_event(event_id_path);
+ if (ret)
+ goto error;
+
+ goto out;
+ }
+
+ verbose_printf("write uprobe event %s\n", probe_event);
+
+ if (write(fd, probe_event, strlen(probe_event)) <= 0) {
+ fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+ UPROBE_EVENTS_PATH);
+ goto error;
+ }
+
+ /* add to cleanup list */
+ pl = malloc(sizeof(struct probe_list));
+ if (!pl)
+ goto error;
+
+ pl->type = UPROBE_EVENT;
+ pl->next = probe_list_head;
+ memcpy(pl->event, event, 64);
+ probe_list_head = pl;
+
+ ret = add_event(event_id_path);
+ if (ret < 0)
+ goto error;
+
+ out:
+ free(symbol_name);
+ return 0;
+
+ error:
+ free(symbol_name);
+ return -1;
+}
+
+/**
+ * TODO: avoid copy-paste stuff
+ *
+ * @return 1 on success, otherwise 0
+ */
+#ifdef NO_LIBELF
+static int parse_events_resolve_symbol(int fd, char *event, int type)
+{
+ char *colon, *binary, *fetch_args;
+ unsigned long symbol_address;
+
+ colon = strchr(event, ':');
+ if (!colon)
+ return -1;
+
+ symbol_address = strtol(colon + 1 /* skip ":" */, NULL, 0);
+
+ fetch_args = strchr(event, ' ');
+
+ /**
+ * We already have address, no need in resolving.
+ */
+ if (symbol_address) {
+ int ret;
+
+ binary = strndup(event, colon - event);
+ ret = write_uprobe_event(fd, !!strstr(event, "%return"), binary,
+ "NULL", symbol_address, fetch_args);
+ free(binary);
+ return ret;
+ }
+
+ fprintf(stderr, "error: cannot resolve event \"%s\" without libelf, "
+ "please recompile ktap with NO_LIBELF disabled\n",
+ event);
+ exit(EXIT_FAILURE);
+ return -1;
+}
+
+#else
+static int uprobe_symbol_actor(const char *name, vaddr_t addr, void *arg)
+{
+ struct probe_cb_base *base = (struct probe_cb_base *)arg;
+ int ret;
+
+ if (!strglobmatch(name, base->symbol))
+ return 0;
+
+ verbose_printf("uprobe: binary: \"%s\" symbol \"%s\" "
+ "resolved to 0x%lx\n",
+ base->binary, base->symbol, (unsigned long)addr);
+
+ ret = write_uprobe_event(base->fd, base->ret_probe, base->binary,
+ name, addr, base->fetch_args);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int parse_events_resolve_symbol(int fd, char *event, int type)
+{
+ char *colon, *end;
+ vaddr_t symbol_address;
+ int ret;
+ struct probe_cb_base base = {
+ .fd = fd,
+ .event = event
+ };
+
+ colon = strchr(event, ':');
+ if (!colon)
+ return 0;
+
+ base.ret_probe = !!strstr(event, "%return");
+ symbol_address = strtol(colon + 1 /* skip ":" */, NULL, 0);
+ base.binary = strndup(event, colon - event);
+
+ base.fetch_args = strchr(event, ' ');
+
+ /*
+ * We already have address, no need in resolving.
+ */
+ if (symbol_address) {
+ int ret;
+ ret = write_uprobe_event(fd, base.ret_probe, base.binary,
+ "NULL", symbol_address,
+ base.fetch_args);
+ free(base.binary);
+ return ret;
+ }
+
+ end = strpbrk(event, "% ");
+ if (end)
+ base.symbol = strndup(colon + 1, end - 1 - colon);
+ else
+ base.symbol = strdup(colon + 1);
+
+ ret = parse_dso_symbols(base.binary, type, uprobe_symbol_actor,
+ (void *)&base);
+ if (!ret) {
+ fprintf(stderr, "error: cannot find symbol %s in binary %s\n",
+ base.symbol, base.binary);
+ ret = -1;
+ } else if(ret > 0) {
+ /* no error found when parse symbols */
+ ret = 0;
+ }
+
+ free(base.binary);
+ free(base.symbol);
+
+ return ret;
+}
+#endif
+
+static int parse_events_add_uprobe(char *old_event, int type)
+{
+ int ret;
+ int fd;
+
+ fd = open(UPROBE_EVENTS_PATH, O_WRONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH);
+ return -1;
+ }
+
+ ret = parse_events_resolve_symbol(fd, old_event, type);
+
+ close(fd);
+ return ret;
+}
+
+static int parse_events_add_probe(char *old_event)
+{
+ char *separator;
+
+ separator = strchr(old_event, ':');
+ if (!separator || (separator == old_event))
+ return parse_events_add_kprobe(old_event);
+ else
+ return parse_events_add_uprobe(old_event, FIND_SYMBOL);
+}
+
+static int parse_events_add_sdt(char *old_event)
+{
+ return parse_events_add_uprobe(old_event, FIND_STAPSDT_NOTE);
+}
+
+static void strim(char *s)
+{
+ size_t size;
+ char *end;
+
+ size = strlen(s);
+ if (!size)
+ return;
+
+ end = s + size -1;
+ while (end >= s && isspace(*end))
+ end--;
+
+ *(end + 1) = '\0';
+}
+
+static int get_sys_event_filter_str(char *start,
+ char **sys, char **event, char **filter)
+{
+ char *separator, *separator2, *ptr, *end;
+
+ while (*start == ' ')
+ start++;
+
+ /* find sys */
+ separator = strchr(start, ':');
+ if (!separator || (separator == start)) {
+ return -1;
+ }
+
+ ptr = malloc(separator - start + 1);
+ if (!ptr)
+ return -1;
+
+ strncpy(ptr, start, separator - start);
+ ptr[separator - start] = '\0';
+
+ strim(ptr);
+ *sys = ptr;
+
+ if (!strcmp(*sys, "probe") && (*(separator + 1) == '/')) {
+ /* it's uprobe event */
+ separator2 = strchr(separator + 1, ':');
+ if (!separator2)
+ return -1;
+ } else
+ separator2 = separator;
+
+ /* find filter */
+ end = start + strlen(start);
+ while (*--end == ' ') {
+ }
+
+ if (*end == '/') {
+ char *filter_start;
+
+ filter_start = strchr(separator2, '/');
+ if (filter_start == end)
+ return -1;
+
+ ptr = malloc(end - filter_start);
+ if (!ptr)
+ return -1;
+
+ memcpy(ptr, filter_start + 1, end - filter_start - 1);
+ ptr[end - filter_start - 1] = '\0';
+
+ *filter = ptr;
+
+ end = filter_start;
+ } else {
+ *filter = NULL;
+ end++;
+ }
+
+ /* find event */
+ ptr = malloc(end - separator);
+ if (!ptr)
+ return -1;
+
+ memcpy(ptr, separator + 1, end - separator - 1);
+ ptr[end - separator - 1] = '\0';
+
+ strim(ptr);
+ *event = ptr;
+
+ return 0;
+}
+
+static char *get_next_eventdef(char *str)
+{
+ char *separator;
+
+ separator = strchr(str, ',');
+ if (!separator)
+ return str + strlen(str);
+
+ *separator = '\0';
+ return separator + 1;
+}
+
+ktap_eventdesc_t *kp_parse_events(const char *eventdef)
+{
+ char *str = strdup(eventdef);
+ char *sys, *event, *filter, *next;
+ ktap_eventdesc_t *evdef_info;
+ int ret;
+
+ idmap_init();
+
+ parse_next_eventdef:
+ next = get_next_eventdef(str);
+
+ if (get_sys_event_filter_str(str, &sys, &event, &filter))
+ goto error;
+
+ verbose_printf("parse_eventdef: sys[%s], event[%s], filter[%s]\n",
+ sys, event, filter);
+
+ if (!strcmp(sys, "probe"))
+ ret = parse_events_add_probe(event);
+ else if (!strcmp(sys, "sdt"))
+ ret = parse_events_add_sdt(event);
+ else
+ ret = parse_events_add_tracepoint(sys, event);
+
+ if (ret)
+ goto error;
+
+ /* don't trace ftrace:function when all tracepoints enabled */
+ if (!strcmp(sys, "*"))
+ idmap_clear(1);
+
+
+ if (filter && *next != '\0') {
+ fprintf(stderr, "Error: eventdef only can append one filter\n");
+ goto error;
+ }
+
+ str = next;
+ if (*next != '\0')
+ goto parse_next_eventdef;
+
+ evdef_info = malloc(sizeof(*evdef_info));
+ if (!evdef_info)
+ goto error;
+
+ evdef_info->nr = id_nr;
+ evdef_info->id_arr = get_id_array();
+ evdef_info->filter = filter;
+
+ idmap_free();
+ return evdef_info;
+ error:
+ idmap_free();
+ cleanup_event_resources();
+ return NULL;
+}
+
+void cleanup_event_resources(void)
+{
+ struct probe_list *pl;
+ const char *path;
+ char probe_event[128] = {0};
+ int fd, ret;
+
+ for (pl = probe_list_head; pl; pl = pl->next) {
+ if (pl->type == KPROBE_EVENT)
+ path = KPROBE_EVENTS_PATH;
+ else if (pl->type == UPROBE_EVENT)
+ path = UPROBE_EVENTS_PATH;
+ else {
+ fprintf(stderr, "Cannot cleanup event type %d\n",
+ pl->type);
+ continue;
+ }
+
+ snprintf(probe_event, 128, "-:%s", pl->event);
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s\n", UPROBE_EVENTS_PATH);
+ continue;
+ }
+
+ ret = write(fd, probe_event, strlen(probe_event));
+ if (ret <= 0) {
+ fprintf(stderr, "Cannot write %s to %s\n", probe_event,
+ path);
+ close(fd);
+ continue;
+ }
+
+ close(fd);
+ }
+}
+
--
1.8.1.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/