| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * probe-file.c : operate ftrace k/uprobe events files |
| * |
| * Written by Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> |
| */ |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| #include <unistd.h> |
| #include <linux/zalloc.h> |
| #include "namespaces.h" |
| #include "event.h" |
| #include "strlist.h" |
| #include "strfilter.h" |
| #include "debug.h" |
| #include "build-id.h" |
| #include "dso.h" |
| #include "color.h" |
| #include "symbol.h" |
| #include "strbuf.h" |
| #include <api/fs/tracing_path.h> |
| #include <api/fs/fs.h> |
| #include "probe-event.h" |
| #include "probe-file.h" |
| #include "session.h" |
| #include "perf_regs.h" |
| #include "string2.h" |
| |
| /* 4096 - 2 ('\n' + '\0') */ |
| #define MAX_CMDLEN 4094 |
| |
| static bool print_common_warning(int err, bool readwrite) |
| { |
| if (err == -EACCES) |
| pr_warning("No permission to %s tracefs.\nPlease %s\n", |
| readwrite ? "write" : "read", |
| readwrite ? "run this command again with sudo." : |
| "try 'sudo mount -o remount,mode=755 /sys/kernel/tracing/'"); |
| else |
| return false; |
| |
| return true; |
| } |
| |
| static bool print_configure_probe_event(int kerr, int uerr) |
| { |
| const char *config, *file; |
| |
| if (kerr == -ENOENT && uerr == -ENOENT) { |
| file = "{k,u}probe_events"; |
| config = "CONFIG_KPROBE_EVENTS=y and CONFIG_UPROBE_EVENTS=y"; |
| } else if (kerr == -ENOENT) { |
| file = "kprobe_events"; |
| config = "CONFIG_KPROBE_EVENTS=y"; |
| } else if (uerr == -ENOENT) { |
| file = "uprobe_events"; |
| config = "CONFIG_UPROBE_EVENTS=y"; |
| } else |
| return false; |
| |
| if (!debugfs__configured() && !tracefs__configured()) |
| pr_warning("Debugfs or tracefs is not mounted\n" |
| "Please try 'sudo mount -t tracefs nodev /sys/kernel/tracing/'\n"); |
| else |
| pr_warning("%s/%s does not exist.\nPlease rebuild kernel with %s.\n", |
| tracing_path_mount(), file, config); |
| |
| return true; |
| } |
| |
| static void print_open_warning(int err, bool uprobe, bool readwrite) |
| { |
| char sbuf[STRERR_BUFSIZE]; |
| |
| if (print_common_warning(err, readwrite)) |
| return; |
| |
| if (print_configure_probe_event(uprobe ? 0 : err, uprobe ? err : 0)) |
| return; |
| |
| pr_warning("Failed to open %s/%cprobe_events: %s\n", |
| tracing_path_mount(), uprobe ? 'u' : 'k', |
| str_error_r(-err, sbuf, sizeof(sbuf))); |
| } |
| |
| static void print_both_open_warning(int kerr, int uerr, bool readwrite) |
| { |
| char sbuf[STRERR_BUFSIZE]; |
| |
| if (kerr == uerr && print_common_warning(kerr, readwrite)) |
| return; |
| |
| if (print_configure_probe_event(kerr, uerr)) |
| return; |
| |
| if (kerr < 0) |
| pr_warning("Failed to open %s/kprobe_events: %s.\n", |
| tracing_path_mount(), |
| str_error_r(-kerr, sbuf, sizeof(sbuf))); |
| if (uerr < 0) |
| pr_warning("Failed to open %s/uprobe_events: %s.\n", |
| tracing_path_mount(), |
| str_error_r(-uerr, sbuf, sizeof(sbuf))); |
| } |
| |
| int open_trace_file(const char *trace_file, bool readwrite) |
| { |
| char buf[PATH_MAX]; |
| int ret; |
| |
| ret = e_snprintf(buf, PATH_MAX, "%s/%s", tracing_path_mount(), trace_file); |
| if (ret >= 0) { |
| pr_debug("Opening %s write=%d\n", buf, readwrite); |
| if (readwrite && !probe_event_dry_run) |
| ret = open(buf, O_RDWR | O_APPEND, 0); |
| else |
| ret = open(buf, O_RDONLY, 0); |
| |
| if (ret < 0) |
| ret = -errno; |
| } |
| return ret; |
| } |
| |
| static int open_kprobe_events(bool readwrite) |
| { |
| return open_trace_file("kprobe_events", readwrite); |
| } |
| |
| static int open_uprobe_events(bool readwrite) |
| { |
| return open_trace_file("uprobe_events", readwrite); |
| } |
| |
| int probe_file__open(int flag) |
| { |
| int fd; |
| |
| if (flag & PF_FL_UPROBE) |
| fd = open_uprobe_events(flag & PF_FL_RW); |
| else |
| fd = open_kprobe_events(flag & PF_FL_RW); |
| if (fd < 0) |
| print_open_warning(fd, flag & PF_FL_UPROBE, flag & PF_FL_RW); |
| |
| return fd; |
| } |
| |
| int probe_file__open_both(int *kfd, int *ufd, int flag) |
| { |
| if (!kfd || !ufd) |
| return -EINVAL; |
| |
| *kfd = open_kprobe_events(flag & PF_FL_RW); |
| *ufd = open_uprobe_events(flag & PF_FL_RW); |
| if (*kfd < 0 && *ufd < 0) { |
| print_both_open_warning(*kfd, *ufd, flag & PF_FL_RW); |
| return *kfd; |
| } |
| |
| return 0; |
| } |
| |
| /* Get raw string list of current kprobe_events or uprobe_events */ |
| struct strlist *probe_file__get_rawlist(int fd) |
| { |
| int ret, idx, fddup; |
| FILE *fp; |
| char buf[MAX_CMDLEN]; |
| char *p; |
| struct strlist *sl; |
| |
| if (fd < 0) |
| return NULL; |
| |
| sl = strlist__new(NULL, NULL); |
| if (sl == NULL) |
| return NULL; |
| |
| fddup = dup(fd); |
| if (fddup < 0) |
| goto out_free_sl; |
| |
| fp = fdopen(fddup, "r"); |
| if (!fp) |
| goto out_close_fddup; |
| |
| while (!feof(fp)) { |
| p = fgets(buf, MAX_CMDLEN, fp); |
| if (!p) |
| break; |
| |
| idx = strlen(p) - 1; |
| if (p[idx] == '\n') |
| p[idx] = '\0'; |
| ret = strlist__add(sl, buf); |
| if (ret < 0) { |
| pr_debug("strlist__add failed (%d)\n", ret); |
| goto out_close_fp; |
| } |
| } |
| fclose(fp); |
| |
| return sl; |
| |
| out_close_fp: |
| fclose(fp); |
| goto out_free_sl; |
| out_close_fddup: |
| close(fddup); |
| out_free_sl: |
| strlist__delete(sl); |
| return NULL; |
| } |
| |
| static struct strlist *__probe_file__get_namelist(int fd, bool include_group) |
| { |
| char buf[128]; |
| struct strlist *sl, *rawlist; |
| struct str_node *ent; |
| struct probe_trace_event tev; |
| int ret = 0; |
| |
| memset(&tev, 0, sizeof(tev)); |
| rawlist = probe_file__get_rawlist(fd); |
| if (!rawlist) |
| return NULL; |
| sl = strlist__new(NULL, NULL); |
| strlist__for_each_entry(ent, rawlist) { |
| ret = parse_probe_trace_command(ent->s, &tev); |
| if (ret < 0) |
| break; |
| if (include_group) { |
| ret = e_snprintf(buf, 128, "%s:%s", tev.group, |
| tev.event); |
| if (ret >= 0) |
| ret = strlist__add(sl, buf); |
| } else |
| ret = strlist__add(sl, tev.event); |
| clear_probe_trace_event(&tev); |
| /* Skip if there is same name multi-probe event in the list */ |
| if (ret == -EEXIST) |
| ret = 0; |
| if (ret < 0) |
| break; |
| } |
| strlist__delete(rawlist); |
| |
| if (ret < 0) { |
| strlist__delete(sl); |
| return NULL; |
| } |
| return sl; |
| } |
| |
| /* Get current perf-probe event names */ |
| struct strlist *probe_file__get_namelist(int fd) |
| { |
| return __probe_file__get_namelist(fd, false); |
| } |
| |
| int probe_file__add_event(int fd, struct probe_trace_event *tev) |
| { |
| int ret = 0; |
| char *buf = synthesize_probe_trace_command(tev); |
| char sbuf[STRERR_BUFSIZE]; |
| |
| if (!buf) { |
| pr_debug("Failed to synthesize probe trace event.\n"); |
| return -EINVAL; |
| } |
| |
| pr_debug("Writing event: %s\n", buf); |
| if (!probe_event_dry_run) { |
| if (write(fd, buf, strlen(buf)) < (int)strlen(buf)) { |
| ret = -errno; |
| pr_warning("Failed to write event: %s\n", |
| str_error_r(errno, sbuf, sizeof(sbuf))); |
| } |
| } |
| free(buf); |
| |
| return ret; |
| } |
| |
| static int __del_trace_probe_event(int fd, struct str_node *ent) |
| { |
| char *p; |
| char buf[128]; |
| int ret; |
| |
| /* Convert from perf-probe event to trace-probe event */ |
| ret = e_snprintf(buf, 128, "-:%s", ent->s); |
| if (ret < 0) |
| goto error; |
| |
| p = strchr(buf + 2, ':'); |
| if (!p) { |
| pr_debug("Internal error: %s should have ':' but not.\n", |
| ent->s); |
| ret = -ENOTSUP; |
| goto error; |
| } |
| *p = '/'; |
| |
| pr_debug("Writing event: %s\n", buf); |
| ret = write(fd, buf, strlen(buf)); |
| if (ret < 0) { |
| ret = -errno; |
| goto error; |
| } |
| |
| return 0; |
| error: |
| pr_warning("Failed to delete event: %s\n", |
| str_error_r(-ret, buf, sizeof(buf))); |
| return ret; |
| } |
| |
| int probe_file__get_events(int fd, struct strfilter *filter, |
| struct strlist *plist) |
| { |
| struct strlist *namelist; |
| struct str_node *ent; |
| const char *p; |
| int ret = -ENOENT; |
| |
| if (!plist) |
| return -EINVAL; |
| |
| namelist = __probe_file__get_namelist(fd, true); |
| if (!namelist) |
| return -ENOENT; |
| |
| strlist__for_each_entry(ent, namelist) { |
| p = strchr(ent->s, ':'); |
| if ((p && strfilter__compare(filter, p + 1)) || |
| strfilter__compare(filter, ent->s)) { |
| ret = strlist__add(plist, ent->s); |
| if (ret == -ENOMEM) { |
| pr_err("strlist__add failed with -ENOMEM\n"); |
| goto out; |
| } |
| ret = 0; |
| } |
| } |
| out: |
| strlist__delete(namelist); |
| |
| return ret; |
| } |
| |
| int probe_file__del_strlist(int fd, struct strlist *namelist) |
| { |
| int ret = 0; |
| struct str_node *ent; |
| |
| strlist__for_each_entry(ent, namelist) { |
| ret = __del_trace_probe_event(fd, ent); |
| if (ret < 0) |
| break; |
| } |
| return ret; |
| } |
| |
| int probe_file__del_events(int fd, struct strfilter *filter) |
| { |
| struct strlist *namelist; |
| int ret; |
| |
| namelist = strlist__new(NULL, NULL); |
| if (!namelist) |
| return -ENOMEM; |
| |
| ret = probe_file__get_events(fd, filter, namelist); |
| if (ret < 0) |
| goto out; |
| |
| ret = probe_file__del_strlist(fd, namelist); |
| out: |
| strlist__delete(namelist); |
| return ret; |
| } |
| |
| /* Caller must ensure to remove this entry from list */ |
| static void probe_cache_entry__delete(struct probe_cache_entry *entry) |
| { |
| if (entry) { |
| BUG_ON(!list_empty(&entry->node)); |
| |
| strlist__delete(entry->tevlist); |
| clear_perf_probe_event(&entry->pev); |
| zfree(&entry->spev); |
| free(entry); |
| } |
| } |
| |
| static struct probe_cache_entry * |
| probe_cache_entry__new(struct perf_probe_event *pev) |
| { |
| struct probe_cache_entry *entry = zalloc(sizeof(*entry)); |
| |
| if (entry) { |
| INIT_LIST_HEAD(&entry->node); |
| entry->tevlist = strlist__new(NULL, NULL); |
| if (!entry->tevlist) |
| zfree(&entry); |
| else if (pev) { |
| entry->spev = synthesize_perf_probe_command(pev); |
| if (!entry->spev || |
| perf_probe_event__copy(&entry->pev, pev) < 0) { |
| probe_cache_entry__delete(entry); |
| return NULL; |
| } |
| } |
| } |
| |
| return entry; |
| } |
| |
| int probe_cache_entry__get_event(struct probe_cache_entry *entry, |
| struct probe_trace_event **tevs) |
| { |
| struct probe_trace_event *tev; |
| struct str_node *node; |
| int ret, i; |
| |
| ret = strlist__nr_entries(entry->tevlist); |
| if (ret > probe_conf.max_probes) |
| return -E2BIG; |
| |
| *tevs = zalloc(ret * sizeof(*tev)); |
| if (!*tevs) |
| return -ENOMEM; |
| |
| i = 0; |
| strlist__for_each_entry(node, entry->tevlist) { |
| tev = &(*tevs)[i++]; |
| ret = parse_probe_trace_command(node->s, tev); |
| if (ret < 0) |
| break; |
| } |
| return i; |
| } |
| |
| /* For the kernel probe caches, pass target = NULL or DSO__NAME_KALLSYMS */ |
| static int probe_cache__open(struct probe_cache *pcache, const char *target, |
| struct nsinfo *nsi) |
| { |
| char cpath[PATH_MAX]; |
| char sbuildid[SBUILD_ID_SIZE]; |
| char *dir_name = NULL; |
| bool is_kallsyms = false; |
| int ret, fd; |
| struct nscookie nsc; |
| |
| if (target && build_id_cache__cached(target)) { |
| /* This is a cached buildid */ |
| strlcpy(sbuildid, target, SBUILD_ID_SIZE); |
| dir_name = build_id_cache__linkname(sbuildid, NULL, 0); |
| goto found; |
| } |
| |
| if (!target || !strcmp(target, DSO__NAME_KALLSYMS)) { |
| target = DSO__NAME_KALLSYMS; |
| is_kallsyms = true; |
| ret = sysfs__sprintf_build_id("/", sbuildid); |
| } else { |
| nsinfo__mountns_enter(nsi, &nsc); |
| ret = filename__sprintf_build_id(target, sbuildid); |
| nsinfo__mountns_exit(&nsc); |
| } |
| |
| if (ret < 0) { |
| pr_debug("Failed to get build-id from %s.\n", target); |
| return ret; |
| } |
| |
| /* If we have no buildid cache, make it */ |
| if (!build_id_cache__cached(sbuildid)) { |
| ret = build_id_cache__add_s(sbuildid, target, nsi, |
| is_kallsyms, NULL); |
| if (ret < 0) { |
| pr_debug("Failed to add build-id cache: %s\n", target); |
| return ret; |
| } |
| } |
| |
| dir_name = build_id_cache__cachedir(sbuildid, target, nsi, is_kallsyms, |
| false); |
| found: |
| if (!dir_name) { |
| pr_debug("Failed to get cache from %s\n", target); |
| return -ENOMEM; |
| } |
| |
| snprintf(cpath, PATH_MAX, "%s/probes", dir_name); |
| fd = open(cpath, O_CREAT | O_RDWR, 0644); |
| if (fd < 0) |
| pr_debug("Failed to open cache(%d): %s\n", fd, cpath); |
| free(dir_name); |
| pcache->fd = fd; |
| |
| return fd; |
| } |
| |
| static int probe_cache__load(struct probe_cache *pcache) |
| { |
| struct probe_cache_entry *entry = NULL; |
| char buf[MAX_CMDLEN], *p; |
| int ret = 0, fddup; |
| FILE *fp; |
| |
| fddup = dup(pcache->fd); |
| if (fddup < 0) |
| return -errno; |
| fp = fdopen(fddup, "r"); |
| if (!fp) { |
| close(fddup); |
| return -EINVAL; |
| } |
| |
| while (!feof(fp)) { |
| if (!fgets(buf, MAX_CMDLEN, fp)) |
| break; |
| p = strchr(buf, '\n'); |
| if (p) |
| *p = '\0'; |
| /* #perf_probe_event or %sdt_event */ |
| if (buf[0] == '#' || buf[0] == '%') { |
| entry = probe_cache_entry__new(NULL); |
| if (!entry) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| if (buf[0] == '%') |
| entry->sdt = true; |
| entry->spev = strdup(buf + 1); |
| if (entry->spev) |
| ret = parse_perf_probe_command(buf + 1, |
| &entry->pev); |
| else |
| ret = -ENOMEM; |
| if (ret < 0) { |
| probe_cache_entry__delete(entry); |
| goto out; |
| } |
| list_add_tail(&entry->node, &pcache->entries); |
| } else { /* trace_probe_event */ |
| if (!entry) { |
| ret = -EINVAL; |
| goto out; |
| } |
| ret = strlist__add(entry->tevlist, buf); |
| if (ret == -ENOMEM) { |
| pr_err("strlist__add failed with -ENOMEM\n"); |
| goto out; |
| } |
| } |
| } |
| out: |
| fclose(fp); |
| return ret; |
| } |
| |
| static struct probe_cache *probe_cache__alloc(void) |
| { |
| struct probe_cache *pcache = zalloc(sizeof(*pcache)); |
| |
| if (pcache) { |
| INIT_LIST_HEAD(&pcache->entries); |
| pcache->fd = -EINVAL; |
| } |
| return pcache; |
| } |
| |
| void probe_cache__purge(struct probe_cache *pcache) |
| { |
| struct probe_cache_entry *entry, *n; |
| |
| list_for_each_entry_safe(entry, n, &pcache->entries, node) { |
| list_del_init(&entry->node); |
| probe_cache_entry__delete(entry); |
| } |
| } |
| |
| void probe_cache__delete(struct probe_cache *pcache) |
| { |
| if (!pcache) |
| return; |
| |
| probe_cache__purge(pcache); |
| if (pcache->fd > 0) |
| close(pcache->fd); |
| free(pcache); |
| } |
| |
| struct probe_cache *probe_cache__new(const char *target, struct nsinfo *nsi) |
| { |
| struct probe_cache *pcache = probe_cache__alloc(); |
| int ret; |
| |
| if (!pcache) |
| return NULL; |
| |
| ret = probe_cache__open(pcache, target, nsi); |
| if (ret < 0) { |
| pr_debug("Cache open error: %d\n", ret); |
| goto out_err; |
| } |
| |
| ret = probe_cache__load(pcache); |
| if (ret < 0) { |
| pr_debug("Cache read error: %d\n", ret); |
| goto out_err; |
| } |
| |
| return pcache; |
| |
| out_err: |
| probe_cache__delete(pcache); |
| return NULL; |
| } |
| |
| static bool streql(const char *a, const char *b) |
| { |
| if (a == b) |
| return true; |
| |
| if (!a || !b) |
| return false; |
| |
| return !strcmp(a, b); |
| } |
| |
| struct probe_cache_entry * |
| probe_cache__find(struct probe_cache *pcache, struct perf_probe_event *pev) |
| { |
| struct probe_cache_entry *entry = NULL; |
| char *cmd = synthesize_perf_probe_command(pev); |
| |
| if (!cmd) |
| return NULL; |
| |
| for_each_probe_cache_entry(entry, pcache) { |
| if (pev->sdt) { |
| if (entry->pev.event && |
| streql(entry->pev.event, pev->event) && |
| (!pev->group || |
| streql(entry->pev.group, pev->group))) |
| goto found; |
| |
| continue; |
| } |
| /* Hit if same event name or same command-string */ |
| if ((pev->event && |
| (streql(entry->pev.group, pev->group) && |
| streql(entry->pev.event, pev->event))) || |
| (!strcmp(entry->spev, cmd))) |
| goto found; |
| } |
| entry = NULL; |
| |
| found: |
| free(cmd); |
| return entry; |
| } |
| |
| struct probe_cache_entry * |
| probe_cache__find_by_name(struct probe_cache *pcache, |
| const char *group, const char *event) |
| { |
| struct probe_cache_entry *entry = NULL; |
| |
| for_each_probe_cache_entry(entry, pcache) { |
| /* Hit if same event name or same command-string */ |
| if (streql(entry->pev.group, group) && |
| streql(entry->pev.event, event)) |
| goto found; |
| } |
| entry = NULL; |
| |
| found: |
| return entry; |
| } |
| |
| int probe_cache__add_entry(struct probe_cache *pcache, |
| struct perf_probe_event *pev, |
| struct probe_trace_event *tevs, int ntevs) |
| { |
| struct probe_cache_entry *entry = NULL; |
| char *command; |
| int i, ret = 0; |
| |
| if (!pcache || !pev || !tevs || ntevs <= 0) { |
| ret = -EINVAL; |
| goto out_err; |
| } |
| |
| /* Remove old cache entry */ |
| entry = probe_cache__find(pcache, pev); |
| if (entry) { |
| list_del_init(&entry->node); |
| probe_cache_entry__delete(entry); |
| } |
| |
| ret = -ENOMEM; |
| entry = probe_cache_entry__new(pev); |
| if (!entry) |
| goto out_err; |
| |
| for (i = 0; i < ntevs; i++) { |
| if (!tevs[i].point.symbol) |
| continue; |
| |
| command = synthesize_probe_trace_command(&tevs[i]); |
| if (!command) |
| goto out_err; |
| ret = strlist__add(entry->tevlist, command); |
| if (ret == -ENOMEM) { |
| pr_err("strlist__add failed with -ENOMEM\n"); |
| goto out_err; |
| } |
| |
| free(command); |
| } |
| list_add_tail(&entry->node, &pcache->entries); |
| pr_debug("Added probe cache: %d\n", ntevs); |
| return 0; |
| |
| out_err: |
| pr_debug("Failed to add probe caches\n"); |
| probe_cache_entry__delete(entry); |
| return ret; |
| } |
| |
| #ifdef HAVE_GELF_GETNOTE_SUPPORT |
| static unsigned long long sdt_note__get_addr(struct sdt_note *note) |
| { |
| return note->bit32 ? |
| (unsigned long long)note->addr.a32[SDT_NOTE_IDX_LOC] : |
| (unsigned long long)note->addr.a64[SDT_NOTE_IDX_LOC]; |
| } |
| |
| static unsigned long long sdt_note__get_ref_ctr_offset(struct sdt_note *note) |
| { |
| return note->bit32 ? |
| (unsigned long long)note->addr.a32[SDT_NOTE_IDX_REFCTR] : |
| (unsigned long long)note->addr.a64[SDT_NOTE_IDX_REFCTR]; |
| } |
| |
| static const char * const type_to_suffix[] = { |
| ":s64", "", "", "", ":s32", "", ":s16", ":s8", |
| "", ":u8", ":u16", "", ":u32", "", "", "", ":u64" |
| }; |
| |
| /* |
| * Isolate the string number and convert it into a decimal value; |
| * this will be an index to get suffix of the uprobe name (defining |
| * the type) |
| */ |
| static int sdt_arg_parse_size(char *n_ptr, const char **suffix) |
| { |
| long type_idx; |
| |
| type_idx = strtol(n_ptr, NULL, 10); |
| if (type_idx < -8 || type_idx > 8) { |
| pr_debug4("Failed to get a valid sdt type\n"); |
| return -1; |
| } |
| |
| *suffix = type_to_suffix[type_idx + 8]; |
| return 0; |
| } |
| |
| static int synthesize_sdt_probe_arg(struct strbuf *buf, int i, const char *arg) |
| { |
| char *op, *desc = strdup(arg), *new_op = NULL; |
| const char *suffix = ""; |
| int ret = -1; |
| |
| if (desc == NULL) { |
| pr_debug4("Allocation error\n"); |
| return ret; |
| } |
| |
| /* |
| * Argument is in N@OP format. N is size of the argument and OP is |
| * the actual assembly operand. N can be omitted; in that case |
| * argument is just OP(without @). |
| */ |
| op = strchr(desc, '@'); |
| if (op) { |
| op[0] = '\0'; |
| op++; |
| |
| if (sdt_arg_parse_size(desc, &suffix)) |
| goto error; |
| } else { |
| op = desc; |
| } |
| |
| ret = arch_sdt_arg_parse_op(op, &new_op); |
| |
| if (ret < 0) |
| goto error; |
| |
| if (ret == SDT_ARG_VALID) { |
| ret = strbuf_addf(buf, " arg%d=%s%s", i + 1, new_op, suffix); |
| if (ret < 0) |
| goto error; |
| } |
| |
| ret = 0; |
| error: |
| free(desc); |
| free(new_op); |
| return ret; |
| } |
| |
| static char *synthesize_sdt_probe_command(struct sdt_note *note, |
| const char *pathname, |
| const char *sdtgrp) |
| { |
| struct strbuf buf; |
| char *ret = NULL; |
| int i, args_count, err; |
| unsigned long long ref_ctr_offset; |
| char *arg; |
| int arg_idx = 0; |
| |
| if (strbuf_init(&buf, 32) < 0) |
| return NULL; |
| |
| err = strbuf_addf(&buf, "p:%s/%s %s:0x%llx", |
| sdtgrp, note->name, pathname, |
| sdt_note__get_addr(note)); |
| |
| ref_ctr_offset = sdt_note__get_ref_ctr_offset(note); |
| if (ref_ctr_offset && err >= 0) |
| err = strbuf_addf(&buf, "(0x%llx)", ref_ctr_offset); |
| |
| if (err < 0) |
| goto error; |
| |
| if (!note->args) |
| goto out; |
| |
| if (note->args) { |
| char **args = argv_split(note->args, &args_count); |
| |
| if (args == NULL) |
| goto error; |
| |
| for (i = 0; i < args_count; ) { |
| /* |
| * FIXUP: Arm64 ELF section '.note.stapsdt' uses string |
| * format "-4@[sp, NUM]" if a probe is to access data in |
| * the stack, e.g. below is an example for the SDT |
| * Arguments: |
| * |
| * Arguments: -4@[sp, 12] -4@[sp, 8] -4@[sp, 4] |
| * |
| * Since the string introduces an extra space character |
| * in the middle of square brackets, the argument is |
| * divided into two items. Fixup for this case, if an |
| * item contains sub string "[sp,", need to concatenate |
| * the two items. |
| */ |
| if (strstr(args[i], "[sp,") && (i+1) < args_count) { |
| err = asprintf(&arg, "%s %s", args[i], args[i+1]); |
| i += 2; |
| } else { |
| err = asprintf(&arg, "%s", args[i]); |
| i += 1; |
| } |
| |
| /* Failed to allocate memory */ |
| if (err < 0) { |
| argv_free(args); |
| goto error; |
| } |
| |
| if (synthesize_sdt_probe_arg(&buf, arg_idx, arg) < 0) { |
| free(arg); |
| argv_free(args); |
| goto error; |
| } |
| |
| free(arg); |
| arg_idx++; |
| } |
| |
| argv_free(args); |
| } |
| |
| out: |
| ret = strbuf_detach(&buf, NULL); |
| error: |
| strbuf_release(&buf); |
| return ret; |
| } |
| |
| int probe_cache__scan_sdt(struct probe_cache *pcache, const char *pathname) |
| { |
| struct probe_cache_entry *entry = NULL; |
| struct list_head sdtlist; |
| struct sdt_note *note; |
| char *buf; |
| char sdtgrp[64]; |
| int ret; |
| |
| INIT_LIST_HEAD(&sdtlist); |
| ret = get_sdt_note_list(&sdtlist, pathname); |
| if (ret < 0) { |
| pr_debug4("Failed to get sdt note: %d\n", ret); |
| return ret; |
| } |
| list_for_each_entry(note, &sdtlist, note_list) { |
| ret = snprintf(sdtgrp, 64, "sdt_%s", note->provider); |
| if (ret < 0) |
| break; |
| /* Try to find same-name entry */ |
| entry = probe_cache__find_by_name(pcache, sdtgrp, note->name); |
| if (!entry) { |
| entry = probe_cache_entry__new(NULL); |
| if (!entry) { |
| ret = -ENOMEM; |
| break; |
| } |
| entry->sdt = true; |
| ret = asprintf(&entry->spev, "%s:%s=%s", sdtgrp, |
| note->name, note->name); |
| if (ret < 0) |
| break; |
| entry->pev.event = strdup(note->name); |
| entry->pev.group = strdup(sdtgrp); |
| list_add_tail(&entry->node, &pcache->entries); |
| } |
| buf = synthesize_sdt_probe_command(note, pathname, sdtgrp); |
| if (!buf) { |
| ret = -ENOMEM; |
| break; |
| } |
| |
| ret = strlist__add(entry->tevlist, buf); |
| |
| free(buf); |
| entry = NULL; |
| |
| if (ret == -ENOMEM) { |
| pr_err("strlist__add failed with -ENOMEM\n"); |
| break; |
| } |
| } |
| if (entry) { |
| list_del_init(&entry->node); |
| probe_cache_entry__delete(entry); |
| } |
| cleanup_sdt_note_list(&sdtlist); |
| return ret; |
| } |
| #endif |
| |
| static int probe_cache_entry__write(struct probe_cache_entry *entry, int fd) |
| { |
| struct str_node *snode; |
| struct stat st; |
| struct iovec iov[3]; |
| const char *prefix = entry->sdt ? "%" : "#"; |
| int ret; |
| /* Save stat for rollback */ |
| ret = fstat(fd, &st); |
| if (ret < 0) |
| return ret; |
| |
| pr_debug("Writing cache: %s%s\n", prefix, entry->spev); |
| iov[0].iov_base = (void *)prefix; iov[0].iov_len = 1; |
| iov[1].iov_base = entry->spev; iov[1].iov_len = strlen(entry->spev); |
| iov[2].iov_base = (void *)"\n"; iov[2].iov_len = 1; |
| ret = writev(fd, iov, 3); |
| if (ret < (int)iov[1].iov_len + 2) |
| goto rollback; |
| |
| strlist__for_each_entry(snode, entry->tevlist) { |
| iov[0].iov_base = (void *)snode->s; |
| iov[0].iov_len = strlen(snode->s); |
| iov[1].iov_base = (void *)"\n"; iov[1].iov_len = 1; |
| ret = writev(fd, iov, 2); |
| if (ret < (int)iov[0].iov_len + 1) |
| goto rollback; |
| } |
| return 0; |
| |
| rollback: |
| /* Rollback to avoid cache file corruption */ |
| if (ret > 0) |
| ret = -1; |
| if (ftruncate(fd, st.st_size) < 0) |
| ret = -2; |
| |
| return ret; |
| } |
| |
| int probe_cache__commit(struct probe_cache *pcache) |
| { |
| struct probe_cache_entry *entry; |
| int ret = 0; |
| |
| /* TBD: if we do not update existing entries, skip it */ |
| ret = lseek(pcache->fd, 0, SEEK_SET); |
| if (ret < 0) |
| goto out; |
| |
| ret = ftruncate(pcache->fd, 0); |
| if (ret < 0) |
| goto out; |
| |
| for_each_probe_cache_entry(entry, pcache) { |
| ret = probe_cache_entry__write(entry, pcache->fd); |
| pr_debug("Cache committed: %d\n", ret); |
| if (ret < 0) |
| break; |
| } |
| out: |
| return ret; |
| } |
| |
| static bool probe_cache_entry__compare(struct probe_cache_entry *entry, |
| struct strfilter *filter) |
| { |
| char buf[128], *ptr = entry->spev; |
| |
| if (entry->pev.event) { |
| snprintf(buf, 128, "%s:%s", entry->pev.group, entry->pev.event); |
| ptr = buf; |
| } |
| return strfilter__compare(filter, ptr); |
| } |
| |
| int probe_cache__filter_purge(struct probe_cache *pcache, |
| struct strfilter *filter) |
| { |
| struct probe_cache_entry *entry, *tmp; |
| |
| list_for_each_entry_safe(entry, tmp, &pcache->entries, node) { |
| if (probe_cache_entry__compare(entry, filter)) { |
| pr_info("Removed cached event: %s\n", entry->spev); |
| list_del_init(&entry->node); |
| probe_cache_entry__delete(entry); |
| } |
| } |
| return 0; |
| } |
| |
| static int probe_cache__show_entries(struct probe_cache *pcache, |
| struct strfilter *filter) |
| { |
| struct probe_cache_entry *entry; |
| |
| for_each_probe_cache_entry(entry, pcache) { |
| if (probe_cache_entry__compare(entry, filter)) |
| printf("%s\n", entry->spev); |
| } |
| return 0; |
| } |
| |
| /* Show all cached probes */ |
| int probe_cache__show_all_caches(struct strfilter *filter) |
| { |
| struct probe_cache *pcache; |
| struct strlist *bidlist; |
| struct str_node *nd; |
| char *buf = strfilter__string(filter); |
| |
| pr_debug("list cache with filter: %s\n", buf); |
| free(buf); |
| |
| bidlist = build_id_cache__list_all(true); |
| if (!bidlist) { |
| pr_debug("Failed to get buildids: %d\n", errno); |
| return -EINVAL; |
| } |
| strlist__for_each_entry(nd, bidlist) { |
| pcache = probe_cache__new(nd->s, NULL); |
| if (!pcache) |
| continue; |
| if (!list_empty(&pcache->entries)) { |
| buf = build_id_cache__origname(nd->s); |
| printf("%s (%s):\n", buf, nd->s); |
| free(buf); |
| probe_cache__show_entries(pcache, filter); |
| } |
| probe_cache__delete(pcache); |
| } |
| strlist__delete(bidlist); |
| |
| return 0; |
| } |
| |
| enum ftrace_readme { |
| FTRACE_README_PROBE_TYPE_X = 0, |
| FTRACE_README_KRETPROBE_OFFSET, |
| FTRACE_README_UPROBE_REF_CTR, |
| FTRACE_README_USER_ACCESS, |
| FTRACE_README_MULTIPROBE_EVENT, |
| FTRACE_README_IMMEDIATE_VALUE, |
| FTRACE_README_END, |
| }; |
| |
| static struct { |
| const char *pattern; |
| bool avail; |
| } ftrace_readme_table[] = { |
| #define DEFINE_TYPE(idx, pat) \ |
| [idx] = {.pattern = pat, .avail = false} |
| DEFINE_TYPE(FTRACE_README_PROBE_TYPE_X, "*type: * x8/16/32/64,*"), |
| DEFINE_TYPE(FTRACE_README_KRETPROBE_OFFSET, "*place (kretprobe): *"), |
| DEFINE_TYPE(FTRACE_README_UPROBE_REF_CTR, "*ref_ctr_offset*"), |
| DEFINE_TYPE(FTRACE_README_USER_ACCESS, "*u]<offset>*"), |
| DEFINE_TYPE(FTRACE_README_MULTIPROBE_EVENT, "*Create/append/*"), |
| DEFINE_TYPE(FTRACE_README_IMMEDIATE_VALUE, "*\\imm-value,*"), |
| }; |
| |
| static bool scan_ftrace_readme(enum ftrace_readme type) |
| { |
| int fd; |
| FILE *fp; |
| char *buf = NULL; |
| size_t len = 0; |
| bool ret = false; |
| static bool scanned = false; |
| |
| if (scanned) |
| goto result; |
| |
| fd = open_trace_file("README", false); |
| if (fd < 0) |
| return ret; |
| |
| fp = fdopen(fd, "r"); |
| if (!fp) { |
| close(fd); |
| return ret; |
| } |
| |
| while (getline(&buf, &len, fp) > 0) |
| for (enum ftrace_readme i = 0; i < FTRACE_README_END; i++) |
| if (!ftrace_readme_table[i].avail) |
| ftrace_readme_table[i].avail = |
| strglobmatch(buf, ftrace_readme_table[i].pattern); |
| scanned = true; |
| |
| fclose(fp); |
| free(buf); |
| |
| result: |
| if (type >= FTRACE_README_END) |
| return false; |
| |
| return ftrace_readme_table[type].avail; |
| } |
| |
| bool probe_type_is_available(enum probe_type type) |
| { |
| if (type >= PROBE_TYPE_END) |
| return false; |
| else if (type == PROBE_TYPE_X) |
| return scan_ftrace_readme(FTRACE_README_PROBE_TYPE_X); |
| |
| return true; |
| } |
| |
| bool kretprobe_offset_is_supported(void) |
| { |
| return scan_ftrace_readme(FTRACE_README_KRETPROBE_OFFSET); |
| } |
| |
| bool uprobe_ref_ctr_is_supported(void) |
| { |
| return scan_ftrace_readme(FTRACE_README_UPROBE_REF_CTR); |
| } |
| |
| bool user_access_is_supported(void) |
| { |
| return scan_ftrace_readme(FTRACE_README_USER_ACCESS); |
| } |
| |
| bool multiprobe_event_is_supported(void) |
| { |
| return scan_ftrace_readme(FTRACE_README_MULTIPROBE_EVENT); |
| } |
| |
| bool immediate_value_is_supported(void) |
| { |
| return scan_ftrace_readme(FTRACE_README_IMMEDIATE_VALUE); |
| } |