blob: 69c58403bbe35af73bbe85ae1786ae2222c027ca [file] [log] [blame]
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021, Oracle and/or its affiliates. */
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/btf.h>
#include "ksnoop.h"
#include "ksnoop.skel.h"
#ifndef KSNOOP_VERSION
#define KSNOOP_VERSION "0.1"
#endif
static volatile sig_atomic_t exiting = 0;
static struct btf *vmlinux_btf;
static const char *bin_name;
static int pages = PAGES_DEFAULT;
enum log_level {
DEBUG,
WARN,
ERROR,
};
static enum log_level log_level = WARN;
static bool verbose = false;
static __u32 filter_pid;
static bool stack_mode;
static void __p(enum log_level level, char *level_str, char *fmt, ...)
{
va_list ap;
if (level < log_level)
return;
va_start(ap, fmt);
fprintf(stderr, "%s: ", level_str);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
fflush(stderr);
}
#define p_err(fmt, ...) __p(ERROR, "Error", fmt, ##__VA_ARGS__)
#define p_warn(fmt, ...) __p(WARNING, "Warn", fmt, ##__VA_ARGS__)
#define p_debug(fmt, ...) __p(DEBUG, "Debug", fmt, ##__VA_ARGS__)
static int do_version(int argc, char **argv)
{
printf("%s v%s\n", bin_name, KSNOOP_VERSION);
return 0;
}
static int cmd_help(int argc, char **argv)
{
fprintf(stderr,
"Usage: %s [OPTIONS] [COMMAND | help] FUNC\n"
" COMMAND := { trace | info }\n"
" FUNC := { name | name(ARG[,ARG]*) }\n"
" ARG := { arg | arg [PRED] | arg->member [PRED] }\n"
" PRED := { == | != | > | >= | < | <= value }\n"
" OPTIONS := { {-d|--debug} | {-v|--verbose} | {-V|--version} |\n"
" {-p|--pid filter_pid}|\n"
" {-P|--pages nr_pages} }\n"
" {-s|--stack}\n",
bin_name);
fprintf(stderr,
"Examples:\n"
" %s info ip_send_skb\n"
" %s trace ip_send_skb\n"
" %s trace \"ip_send_skb(skb, return)\"\n"
" %s trace \"ip_send_skb(skb->sk, return)\"\n"
" %s trace \"ip_send_skb(skb->len > 128, skb)\"\n"
" %s trace -s udp_sendmsg ip_send_skb\n",
bin_name, bin_name, bin_name, bin_name, bin_name, bin_name);
return 0;
}
static void usage(void)
{
cmd_help(0, NULL);
exit(1);
}
static void type_to_value(struct btf *btf, char *name, __u32 type_id,
struct value *val)
{
const struct btf_type *type;
__s32 id = type_id;
if (strlen(val->name) == 0) {
if (name)
strncpy(val->name, name,
sizeof(val->name) - 1);
else
val->name[0] = '\0';
}
do {
type = btf__type_by_id(btf, id);
switch (BTF_INFO_KIND(type->info)) {
case BTF_KIND_CONST:
case BTF_KIND_VOLATILE:
case BTF_KIND_RESTRICT:
id = type->type;
break;
case BTF_KIND_PTR:
val->flags |= KSNOOP_F_PTR;
id = type->type;
break;
default:
val->type_id = id;
goto done;
}
} while (id >= 0);
val->type_id = KSNOOP_ID_UNKNOWN;
return;
done:
val->size = btf__resolve_size(btf, val->type_id);
}
static int member_to_value(struct btf *btf, const char *name, __u32 type_id,
struct value *val, int lvl)
{
const struct btf_member *member;
const struct btf_type *type;
const char *pname;
__s32 id = type_id;
int i, nmembers;
__u8 kind;
/* type_to_value has already stripped qualifiers, so
* we either have a base type, a struct, union, etc.
* Only struct/unions have named members so anything
* else is invalid.
*/
p_debug("Looking for member '%s' in type id %d", name, type_id);
type = btf__type_by_id(btf, id);
pname = btf__str_by_offset(btf, type->name_off);
if (strlen(pname) == 0)
pname = "<anon>";
kind = BTF_INFO_KIND(type->info);
switch (kind) {
case BTF_KIND_STRUCT:
case BTF_KIND_UNION:
nmembers = BTF_INFO_VLEN(type->info);
p_debug("Checking %d members...", nmembers);
for (member = (struct btf_member *)(type + 1), i = 0;
i < nmembers;
member++, i++) {
const char *mname;
__u16 offset;
type = btf__type_by_id(btf, member->type);
mname = btf__str_by_offset(btf, member->name_off);
offset = member->offset / 8;
p_debug("Checking member '%s' type %d offset %d",
mname, member->type, offset);
/* anonymous struct member? */
kind = BTF_INFO_KIND(type->info);
if (strlen(mname) == 0 &&
(kind == BTF_KIND_STRUCT ||
kind == BTF_KIND_UNION)) {
p_debug("Checking anon struct/union %d",
member->type);
val->offset += offset;
if (!member_to_value(btf, name, member->type,
val, lvl + 1))
return 0;
val->offset -= offset;
continue;
}
if (strcmp(mname, name) == 0) {
val->offset += offset;
val->flags |= KSNOOP_F_MEMBER;
type_to_value(btf, NULL, member->type, val);
p_debug("Member '%s', offset %d, flags %x size %d",
mname, val->offset, val->flags,
val->size);
return 0;
}
}
if (lvl > 0)
break;
p_err("No member '%s' found in %s [%d], offset %d", name, pname,
id, val->offset);
break;
default:
p_err("'%s' is not a struct/union", pname);
break;
}
return -ENOENT;
}
static int get_func_btf(struct btf *btf, struct func *func)
{
const struct btf_param *param;
const struct btf_type *type;
__u8 i;
func->id = btf__find_by_name_kind(btf, func->name, BTF_KIND_FUNC);
if (func->id <= 0) {
p_err("Cannot find function '%s' in BTF: %s",
func->name, strerror(-func->id));
return -ENOENT;
}
type = btf__type_by_id(btf, func->id);
if (!type || BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
p_err("Error looking up function type via id '%d'", func->id);
return -EINVAL;
}
type = btf__type_by_id(btf, type->type);
if (!type || BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
p_err("Error looking up function proto type via id '%d'",
func->id);
return -EINVAL;
}
for (param = (struct btf_param *)(type + 1), i = 0;
i < BTF_INFO_VLEN(type->info) && i < MAX_ARGS;
param++, i++) {
type_to_value(btf,
(char *)btf__str_by_offset(btf, param->name_off),
param->type, &func->args[i]);
p_debug("arg #%d: <name '%s', type id '%u'>",
i + 1, func->args[i].name, func->args[i].type_id);
}
/* real number of args, even if it is > number we recorded. */
func->nr_args = BTF_INFO_VLEN(type->info);
type_to_value(btf, KSNOOP_RETURN_NAME, type->type,
&func->args[KSNOOP_RETURN]);
p_debug("return value: type id '%u'>",
func->args[KSNOOP_RETURN].type_id);
return 0;
}
static int predicate_to_value(char *predicate, struct value *val)
{
char pred[MAX_STR];
long v;
if (!predicate)
return 0;
p_debug("checking predicate '%s' for '%s'", predicate, val->name);
if (sscanf(predicate, "%[!=><]%li", pred, &v) != 2) {
p_err("Invalid specification; expected predicate, not '%s'",
predicate);
return -EINVAL;
}
if (!(val->flags & KSNOOP_F_PTR) &&
(val->size == 0 || val->size > sizeof(__u64))) {
p_err("'%s' (size %d) does not support predicate comparison",
val->name, val->size);
return -EINVAL;
}
val->predicate_value = (__u64)v;
if (strcmp(pred, "==") == 0) {
val->flags |= KSNOOP_F_PREDICATE_EQ;
goto out;
} else if (strcmp(pred, "!=") == 0) {
val->flags |= KSNOOP_F_PREDICATE_NOTEQ;
goto out;
}
if (pred[0] == '>')
val->flags |= KSNOOP_F_PREDICATE_GT;
else if (pred[0] == '<')
val->flags |= KSNOOP_F_PREDICATE_LT;
if (strlen(pred) == 1)
goto out;
if (pred[1] != '=') {
p_err("Invalid predicate specification '%s'", predicate);
return -EINVAL;
}
val->flags |= KSNOOP_F_PREDICATE_EQ;
out:
p_debug("predicate '%s', flags 0x%x value %x",
pred, val->flags, val->predicate_value);
return 0;
}
static int trace_to_value(struct btf *btf, struct func *func, char *argname,
char *membername, char *predicate, struct value *val)
{
__u8 i;
if (strlen(membername) > 0)
snprintf(val->name, sizeof(val->name), "%s->%s",
argname, membername);
else
strncpy(val->name, argname, sizeof(val->name));
for (i = 0; i < MAX_TRACES; i++) {
if (!func->args[i].name)
continue;
if (strcmp(argname, func->args[i].name) != 0)
continue;
p_debug("setting base arg for val %s to %d", val->name, i);
val->base_arg = i;
if (strlen(membername) > 0) {
if (member_to_value(btf, membername,
func->args[i].type_id, val, 0))
return -ENOENT;
} else {
val->type_id = func->args[i].type_id;
val->flags |= func->args[i].flags;
val->size = func->args[i].size;
}
return predicate_to_value(predicate, val);
}
p_err("Could not find '%s' in arguments/return value for '%s'",
argname, func->name);
return -ENOENT;
}
static struct btf *get_btf(const char *name)
{
struct btf *mod_btf;
int err;
p_debug("getting BTF for %s",
name && strlen(name) > 0 ? name : "vmlinux");
if (!vmlinux_btf) {
vmlinux_btf = btf__load_vmlinux_btf();
if (!vmlinux_btf) {
err = -errno;
p_err("No BTF, cannot determine type info: %s", strerror(-err));
return NULL;
}
}
if (!name || strlen(name) == 0)
return vmlinux_btf;
mod_btf = btf__load_module_btf(name, vmlinux_btf);
if (!mod_btf) {
err = -errno;
p_err("No BTF for module '%s': %s", name, strerror(-err));
return NULL;
}
return mod_btf;
}
static void copy_without_spaces(char *target, char *src)
{
for (; *src != '\0'; src++)
if (!isspace(*src))
*(target++) = *src;
*target = '\0';
}
static char *type_id_to_str(struct btf *btf, __s32 type_id, char *str)
{
const struct btf_type *type;
const char *name = "";
char *prefix = "";
char *suffix = " ";
char *ptr = "";
str[0] = '\0';
switch (type_id) {
case 0:
name = "void";
break;
case KSNOOP_ID_UNKNOWN:
name = "?";
break;
default:
do {
type = btf__type_by_id(btf, type_id);
if (!type) {
name = "?";
break;
}
switch (BTF_INFO_KIND(type->info)) {
case BTF_KIND_CONST:
case BTF_KIND_VOLATILE:
case BTF_KIND_RESTRICT:
type_id = type->type;
break;
case BTF_KIND_PTR:
ptr = "* ";
type_id = type->type;
break;
case BTF_KIND_ARRAY:
suffix = "[]";
type_id = type->type;
break;
case BTF_KIND_STRUCT:
prefix = "struct ";
name = btf__str_by_offset(btf, type->name_off);
break;
case BTF_KIND_UNION:
prefix = "union ";
name = btf__str_by_offset(btf, type->name_off);
break;
case BTF_KIND_ENUM:
prefix = "enum ";
name = btf__str_by_offset(btf, type->name_off);
break;
case BTF_KIND_TYPEDEF:
name = btf__str_by_offset(btf, type->name_off);
break;
default:
name = btf__str_by_offset(btf, type->name_off);
break;
}
} while (type_id >= 0 && strlen(name) == 0);
break;
}
snprintf(str, MAX_STR, "%s%s%s%s", prefix, name, suffix, ptr);
return str;
}
static char *value_to_str(struct btf *btf, struct value *val, char *str)
{
str = type_id_to_str(btf, val->type_id, str);
if (val->flags & KSNOOP_F_PTR)
strncat(str, "*", MAX_STR);
if (strlen(val->name) > 0 &&
strcmp(val->name, KSNOOP_RETURN_NAME) != 0)
strncat(str, val->name, MAX_STR);
return str;
}
/* based heavily on bpf_object__read_kallsyms_file() in libbpf.c */
static int get_func_ip_mod(struct func *func)
{
char sym_type, sym_name[MAX_STR], mod_info[MAX_STR];
unsigned long long sym_addr;
int ret, err = 0;
FILE *f;
f = fopen("/proc/kallsyms", "r");
if (!f) {
err = errno;
p_err("failed to open /proc/kallsyms: %d", strerror(err));
return err;
}
while (true) {
ret = fscanf(f, "%llx %c %128s%[^\n]\n",
&sym_addr, &sym_type, sym_name, mod_info);
if (ret == EOF && feof(f))
break;
if (ret < 3) {
p_err("failed to read kallsyms entry: %d", ret);
err = -EINVAL;
goto out;
}
if (strcmp(func->name, sym_name) != 0)
continue;
func->ip = sym_addr;
func->mod[0] = '\0';
/* get module name from [modname] */
if (ret == 4) {
if (sscanf(mod_info, "%*[\t ][%[^]]", func->mod) < 1) {
p_err("failed to read module name");
err = -EINVAL;
goto out;
}
}
p_debug("%s = <ip %llx, mod %s>", func->name, func->ip,
strlen(func->mod) > 0 ? func->mod : "vmlinux");
break;
}
out:
fclose(f);
return err;
}
static void trace_printf(void *ctx, const char *fmt, va_list args)
{
vprintf(fmt, args);
}
#define VALID_NAME "%[A-Za-z0-9\\-_]"
#define ARGDATA "%[^)]"
static int parse_trace(char *str, struct trace *trace)
{
__u8 i, nr_predicates = 0, nr_entry = 0, nr_return = 0;
char argname[MAX_NAME], membername[MAX_NAME];
char tracestr[MAX_STR], argdata[MAX_STR];
struct func *func = &trace->func;
struct btf_dump_opts opts = { };
char *arg, *saveptr;
int ret;
copy_without_spaces(tracestr, str);
p_debug("Parsing trace '%s'", tracestr);
trace->filter_pid = (__u32)filter_pid;
if (filter_pid)
p_debug("Using pid %lu as filter", trace->filter_pid);
trace->btf = vmlinux_btf;
ret = sscanf(tracestr, VALID_NAME "(" ARGDATA ")", func->name, argdata);
if (ret <= 0)
usage();
if (ret == 1) {
if (strlen(tracestr) > strlen(func->name)) {
p_err("Invalid function specification '%s'", tracestr);
usage();
}
argdata[0] = '\0';
p_debug("got func '%s'", func->name);
} else {
if (strlen(tracestr) >
strlen(func->name) + strlen(argdata) + 2) {
p_err("Invalid function specification '%s'", tracestr);
usage();
}
p_debug("got func '%s', args '%s'", func->name, argdata);
trace->flags |= KSNOOP_F_CUSTOM;
}
ret = get_func_ip_mod(func);
if (ret) {
p_err("could not get address of '%s'", func->name);
return ret;
}
trace->btf = get_btf(func->mod);
if (!trace->btf) {
ret = -errno;
p_err("could not get BTF for '%s': %s",
strlen(func->mod) ? func->mod : "vmlinux",
strerror(-ret));
return -ENOENT;
}
trace->dump = btf_dump__new(trace->btf, NULL, &opts, trace_printf);
if (!trace->dump) {
ret = -errno;
p_err("could not create BTF dump : %n", strerror(-ret));
return -EINVAL;
}
ret = get_func_btf(trace->btf, func);
if (ret) {
p_debug("unexpected return value '%d' getting function", ret);
return ret;
}
for (arg = strtok_r(argdata, ",", &saveptr), i = 0;
arg;
arg = strtok_r(NULL, ",", &saveptr), i++) {
char *predicate = NULL;
ret = sscanf(arg, VALID_NAME "->" VALID_NAME,
argname, membername);
if (ret == 2) {
if (strlen(arg) >
strlen(argname) + strlen(membername) + 2) {
predicate = arg + strlen(argname) +
strlen(membername) + 2;
}
p_debug("'%s' dereferences '%s', predicate '%s'",
argname, membername, predicate);
} else {
if (strlen(arg) > strlen(argname))
predicate = arg + strlen(argname);
p_debug("'%s' arg, predcate '%s'", argname, predicate);
membername[0] = '\0';
}
if (i >= MAX_TRACES) {
p_err("Too many arguments; up to %d are supported",
MAX_TRACES);
return -EINVAL;
}
if (trace_to_value(trace->btf, func, argname, membername,
predicate, &trace->traces[i]))
return -EINVAL;
if (predicate)
nr_predicates++;
if (trace->traces[i].base_arg == KSNOOP_RETURN)
nr_return++;
else
nr_entry++;
trace->nr_traces++;
}
if (trace->nr_traces > 0) {
trace->flags |= KSNOOP_F_CUSTOM;
p_debug("custom trace with %d args", trace->nr_traces);
/* If we have one or more predicates _and_ references to
* entry and return values, we need to activate "stash"
* mode where arg traces are stored on entry and not
* sent until return to ensure predicates are satisfied.
*/
if (nr_predicates > 0 && nr_entry > 0 && nr_return > 0) {
trace->flags |= KSNOOP_F_STASH;
p_debug("activating stash mode on entry");
}
} else {
p_debug("Standard trace, function with %d arguments",
func->nr_args);
/* copy function arg/return value to trace specification. */
memcpy(trace->traces, func->args, sizeof(trace->traces));
for (i = 0; i < MAX_TRACES; i++)
trace->traces[i].base_arg = i;
trace->nr_traces = MAX_TRACES;
}
return 0;
}
static int parse_traces(int argc, char **argv, struct trace **traces)
{
__u8 i;
if (argc == 0)
usage();
if (argc > MAX_FUNC_TRACES) {
p_err("A maximum of %d traces are supported", MAX_FUNC_TRACES);
return -EINVAL;
}
*traces = calloc(argc, sizeof(struct trace));
if (!*traces) {
p_err("Could not allocate %d traces", argc);
return -ENOMEM;
}
for (i = 0; i < argc; i++) {
if (parse_trace(argv[i], &((*traces)[i])))
return -EINVAL;
if (!stack_mode || i == 0)
continue;
/* tell stack mode trace which function to expect next */
(*traces)[i].prev_ip = (*traces)[i-1].func.ip;
(*traces)[i-1].next_ip = (*traces)[i].func.ip;
}
return i;
}
static int cmd_info(int argc, char **argv)
{
struct trace *traces = NULL;
char str[MAX_STR];
int nr_traces;
__u8 i, j;
nr_traces = parse_traces(argc, argv, &traces);
if (nr_traces < 0)
return nr_traces;
for (i = 0; i < nr_traces; i++) {
struct func *func = &traces[i].func;
printf("%s%s(",
value_to_str(traces[i].btf, &func->args[KSNOOP_RETURN],
str),
func->name);
for (j = 0; j < func->nr_args; j++) {
if (j > 0)
printf(", ");
printf("%s", value_to_str(traces[i].btf, &func->args[j],
str));
}
if (func->nr_args > MAX_ARGS)
printf(" /* and %d more args that are not traceable */",
func->nr_args - MAX_ARGS);
printf(");\n");
}
free(traces);
return 0;
}
static void trace_handler(void *ctx, int cpu, void *data, __u32 size)
{
struct trace *trace = data;
int i, shown, ret;
p_debug("got trace, size %d", size);
if (size < (sizeof(*trace) - MAX_TRACE_BUF)) {
p_err("\t/* trace buffer size '%u' < min %ld */",
size, sizeof(trace) - MAX_TRACE_BUF);
return;
}
printf("%16lld %4d %8u %s(\n", trace->time, trace->cpu, trace->pid,
trace->func.name);
for (i = 0, shown = 0; i < trace->nr_traces; i++) {
DECLARE_LIBBPF_OPTS(btf_dump_type_data_opts, opts);
bool entry = trace->data_flags & KSNOOP_F_ENTRY;
struct value *val = &trace->traces[i];
struct trace_data *data = &trace->trace_data[i];
opts.indent_level = 36;
opts.indent_str = " ";
/* skip if it's entry data and trace data is for return, or
* if it's return and trace data is entry; only exception in
* the latter case is if we stashed data; in such cases we
* want to see it as it's a mix of entry/return data with
* predicates.
*/
if ((entry && !base_arg_is_entry(val->base_arg)) ||
(!entry && base_arg_is_entry(val->base_arg) &&
!(trace->flags & KSNOOP_F_STASH)))
continue;
if (val->type_id == 0)
continue;
if (shown > 0)
printf(",\n");
printf("%34s %s = ", "", val->name);
if (val->flags & KSNOOP_F_PTR)
printf("*(0x%llx)", data->raw_value);
printf("\n");
if (data->err_type_id != 0) {
char typestr[MAX_STR];
printf("%36s /* Cannot show '%s' as '%s%s'; invalid/userspace ptr? */\n",
"",
val->name,
type_id_to_str(trace->btf,
val->type_id,
typestr),
val->flags & KSNOOP_F_PTR ?
" *" : "");
} else {
ret = btf_dump__dump_type_data
(trace->dump, val->type_id,
trace->buf + data->buf_offset,
data->buf_len, &opts);
/* truncated? */
if (ret == -E2BIG)
printf("%36s... /* %d bytes of %d */", "",
data->buf_len,
val->size);
}
shown++;
}
printf("\n%31s);\n\n", "");
fflush(stdout);
}
static void lost_handler(void *ctx, int cpu, __u64 cnt)
{
p_err("\t/* lost %llu events */", cnt);
}
static void sig_int(int signo)
{
exiting = 1;
}
static int add_traces(struct bpf_map *func_map, struct trace *traces,
int nr_traces)
{
int i, j, ret, nr_cpus = libbpf_num_possible_cpus();
struct trace *map_traces;
map_traces = calloc(nr_cpus, sizeof(struct trace));
if (!map_traces) {
p_err("Could not allocate memory for %d traces", nr_traces);
return -ENOMEM;
}
for (i = 0; i < nr_traces; i++) {
for (j = 0; j < nr_cpus; j++)
memcpy(&map_traces[j], &traces[i],
sizeof(map_traces[j]));
ret = bpf_map_update_elem(bpf_map__fd(func_map),
&traces[i].func.ip,
map_traces,
BPF_NOEXIST);
if (ret) {
p_err("Could not add map entry for '%s': %s",
traces[i].func.name, strerror(-ret));
break;
}
}
free(map_traces);
return ret;
}
static int attach_traces(struct ksnoop_bpf *skel, struct trace *traces,
int nr_traces)
{
int i, ret;
for (i = 0; i < nr_traces; i++) {
traces[i].links[0] =
bpf_program__attach_kprobe(skel->progs.kprobe_entry,
false,
traces[i].func.name);
if (!traces[i].links[0]) {
ret = -errno;
p_err("Could not attach kprobe to '%s': %s",
traces[i].func.name, strerror(-ret));
return ret;
}
p_debug("Attached kprobe for '%s'", traces[i].func.name);
traces[i].links[1] =
bpf_program__attach_kprobe(skel->progs.kprobe_return,
true,
traces[i].func.name);
if (!traces[i].links[1]) {
ret = -errno;
p_err("Could not attach kretprobe to '%s': %s",
traces[i].func.name, strerror(-ret));
return ret;
}
p_debug("Attached kretprobe for '%s'", traces[i].func.name);
}
return 0;
}
static int cmd_trace(int argc, char **argv)
{
struct bpf_map *perf_map, *func_map;
struct perf_buffer *pb = NULL;
struct ksnoop_bpf *skel;
int i, nr_traces, ret = -1;
struct trace *traces = NULL;
nr_traces = parse_traces(argc, argv, &traces);
if (nr_traces < 0)
return nr_traces;
skel = ksnoop_bpf__open_and_load();
if (!skel) {
ret = -errno;
p_err("Could not load ksnoop BPF: %s", strerror(-ret));
return 1;
}
perf_map = skel->maps.ksnoop_perf_map;
if (!perf_map) {
p_err("Could not find '%s'", "ksnoop_perf_map");
goto cleanup;
}
func_map = bpf_object__find_map_by_name(skel->obj, "ksnoop_func_map");
if (!func_map) {
p_err("Could not find '%s'", "ksnoop_func_map");
goto cleanup;
}
if (add_traces(func_map, traces, nr_traces)) {
p_err("Could not add traces to '%s'", "ksnoop_func_map");
goto cleanup;
}
if (attach_traces(skel, traces, nr_traces)) {
p_err("Could not attach %d traces", nr_traces);
goto cleanup;
}
pb = perf_buffer__new(bpf_map__fd(perf_map), pages,
trace_handler, lost_handler, NULL, NULL);
if (!pb) {
ret = -errno;
p_err("Could not create perf buffer: %s", strerror(-ret));
goto cleanup;
}
printf("%16s %4s %8s %s\n", "TIME", "CPU", "PID", "FUNCTION/ARGS");
if (signal(SIGINT, sig_int) == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
ret = 1;
goto cleanup;
}
while (!exiting) {
ret = perf_buffer__poll(pb, 1);
if (ret < 0 && ret != -EINTR) {
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-ret));
goto cleanup;
}
/* reset ret to return 0 if exiting */
ret = 0;
}
cleanup:
for (i = 0; i < nr_traces; i++) {
bpf_link__destroy(traces[i].links[0]);
bpf_link__destroy(traces[i].links[1]);
}
free(traces);
perf_buffer__free(pb);
ksnoop_bpf__destroy(skel);
return ret;
}
struct cmd {
const char *cmd;
int (*func)(int argc, char **argv);
};
struct cmd cmds[] = {
{ "info", cmd_info },
{ "trace", cmd_trace },
{ "help", cmd_help },
{ NULL, NULL }
};
static int cmd_select(int argc, char **argv)
{
int i;
for (i = 0; cmds[i].cmd; i++) {
if (strncmp(*argv, cmds[i].cmd, strlen(*argv)) == 0)
return cmds[i].func(argc - 1, argv + 1);
}
return cmd_trace(argc, argv);
}
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
if (level == LIBBPF_DEBUG && !verbose)
return 0;
return vfprintf(stderr, format, args);
}
int main(int argc, char *argv[])
{
static const struct option options[] = {
{ "debug", no_argument, NULL, 'd' },
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "pages", required_argument, NULL, 'P' },
{ "pid", required_argument, NULL, 'p' },
{ 0 }
};
int opt;
bin_name = argv[0];
while ((opt = getopt_long(argc, argv, "dvhp:P:sV", options,
NULL)) >= 0) {
switch (opt) {
case 'd':
verbose = true;
log_level = DEBUG;
break;
case 'v':
verbose = true;
log_level = DEBUG;
break;
case 'h':
return cmd_help(argc, argv);
case 'V':
return do_version(argc, argv);
case 'p':
filter_pid = atoi(optarg);
break;
case 'P':
pages = atoi(optarg);
break;
case 's':
stack_mode = true;
break;
default:
p_err("unrecognized option '%s'", argv[optind - 1]);
usage();
}
}
if (argc == 1)
usage();
argc -= optind;
argv += optind;
if (argc < 0)
usage();
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
libbpf_set_print(libbpf_print_fn);
return cmd_select(argc, argv);
}