// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) // Copyright (C) 2018 Facebook // Author: Yonghong Song <yhs@fb.com> #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <dirent.h> #include <bpf/bpf.h> #include "main.h" /* 0: undecided, 1: supported, 2: not supported */ static int perf_query_supported; static bool has_perf_query_support(void) { __u64 probe_offset, probe_addr; __u32 len, prog_id, fd_type; char buf[256]; int fd; if (perf_query_supported) goto out; fd = open("/", O_RDONLY); if (fd < 0) { p_err("perf_query_support: cannot open directory \"/\" (%s)", strerror(errno)); goto out; } /* the following query will fail as no bpf attachment, * the expected errno is ENOTSUPP */ errno = 0; len = sizeof(buf); bpf_task_fd_query(getpid(), fd, 0, buf, &len, &prog_id, &fd_type, &probe_offset, &probe_addr); if (errno == 524 /* ENOTSUPP */) { perf_query_supported = 1; goto close_fd; } perf_query_supported = 2; p_err("perf_query_support: %s", strerror(errno)); fprintf(stderr, "HINT: non root or kernel doesn't support TASK_FD_QUERY\n"); close_fd: close(fd); out: return perf_query_supported == 1; } static void print_perf_json(int pid, int fd, __u32 prog_id, __u32 fd_type, char *buf, __u64 probe_offset, __u64 probe_addr) { jsonw_start_object(json_wtr); jsonw_int_field(json_wtr, "pid", pid); jsonw_int_field(json_wtr, "fd", fd); jsonw_uint_field(json_wtr, "prog_id", prog_id); switch (fd_type) { case BPF_FD_TYPE_RAW_TRACEPOINT: jsonw_string_field(json_wtr, "fd_type", "raw_tracepoint"); jsonw_string_field(json_wtr, "tracepoint", buf); break; case BPF_FD_TYPE_TRACEPOINT: jsonw_string_field(json_wtr, "fd_type", "tracepoint"); jsonw_string_field(json_wtr, "tracepoint", buf); break; case BPF_FD_TYPE_KPROBE: jsonw_string_field(json_wtr, "fd_type", "kprobe"); if (buf[0] != '\0') { jsonw_string_field(json_wtr, "func", buf); jsonw_lluint_field(json_wtr, "offset", probe_offset); } else { jsonw_lluint_field(json_wtr, "addr", probe_addr); } break; case BPF_FD_TYPE_KRETPROBE: jsonw_string_field(json_wtr, "fd_type", "kretprobe"); if (buf[0] != '\0') { jsonw_string_field(json_wtr, "func", buf); jsonw_lluint_field(json_wtr, "offset", probe_offset); } else { jsonw_lluint_field(json_wtr, "addr", probe_addr); } break; case BPF_FD_TYPE_UPROBE: jsonw_string_field(json_wtr, "fd_type", "uprobe"); jsonw_string_field(json_wtr, "filename", buf); jsonw_lluint_field(json_wtr, "offset", probe_offset); break; case BPF_FD_TYPE_URETPROBE: jsonw_string_field(json_wtr, "fd_type", "uretprobe"); jsonw_string_field(json_wtr, "filename", buf); jsonw_lluint_field(json_wtr, "offset", probe_offset); break; default: break; } jsonw_end_object(json_wtr); } static void print_perf_plain(int pid, int fd, __u32 prog_id, __u32 fd_type, char *buf, __u64 probe_offset, __u64 probe_addr) { printf("pid %d fd %d: prog_id %u ", pid, fd, prog_id); switch (fd_type) { case BPF_FD_TYPE_RAW_TRACEPOINT: printf("raw_tracepoint %s\n", buf); break; case BPF_FD_TYPE_TRACEPOINT: printf("tracepoint %s\n", buf); break; case BPF_FD_TYPE_KPROBE: if (buf[0] != '\0') printf("kprobe func %s offset %llu\n", buf, probe_offset); else printf("kprobe addr %llu\n", probe_addr); break; case BPF_FD_TYPE_KRETPROBE: if (buf[0] != '\0') printf("kretprobe func %s offset %llu\n", buf, probe_offset); else printf("kretprobe addr %llu\n", probe_addr); break; case BPF_FD_TYPE_UPROBE: printf("uprobe filename %s offset %llu\n", buf, probe_offset); break; case BPF_FD_TYPE_URETPROBE: printf("uretprobe filename %s offset %llu\n", buf, probe_offset); break; default: break; } } static int show_proc(void) { struct dirent *proc_de, *pid_fd_de; __u64 probe_offset, probe_addr; __u32 len, prog_id, fd_type; DIR *proc, *pid_fd; int err, pid, fd; const char *pch; char buf[4096]; proc = opendir("/proc"); if (!proc) return -1; while ((proc_de = readdir(proc))) { pid = 0; pch = proc_de->d_name; /* pid should be all numbers */ while (isdigit(*pch)) { pid = pid * 10 + *pch - '0'; pch++; } if (*pch != '\0') continue; err = snprintf(buf, sizeof(buf), "/proc/%s/fd", proc_de->d_name); if (err < 0 || err >= (int)sizeof(buf)) continue; pid_fd = opendir(buf); if (!pid_fd) continue; while ((pid_fd_de = readdir(pid_fd))) { fd = 0; pch = pid_fd_de->d_name; /* fd should be all numbers */ while (isdigit(*pch)) { fd = fd * 10 + *pch - '0'; pch++; } if (*pch != '\0') continue; /* query (pid, fd) for potential perf events */ len = sizeof(buf); err = bpf_task_fd_query(pid, fd, 0, buf, &len, &prog_id, &fd_type, &probe_offset, &probe_addr); if (err < 0) continue; if (json_output) print_perf_json(pid, fd, prog_id, fd_type, buf, probe_offset, probe_addr); else print_perf_plain(pid, fd, prog_id, fd_type, buf, probe_offset, probe_addr); } closedir(pid_fd); } closedir(proc); return 0; } static int do_show(int argc, char **argv) { int err; if (!has_perf_query_support()) return -1; if (json_output) jsonw_start_array(json_wtr); err = show_proc(); if (json_output) jsonw_end_array(json_wtr); return err; } static int do_help(int argc, char **argv) { fprintf(stderr, "Usage: %1$s %2$s { show | list }\n" " %1$s %2$s help\n" "\n" " " HELP_SPEC_OPTIONS " }\n" "", bin_name, argv[-2]); return 0; } static const struct cmd cmds[] = { { "show", do_show }, { "list", do_show }, { "help", do_help }, { 0 } }; int do_perf(int argc, char **argv) { return cmd_select(cmds, argc, argv, do_help); }