1013 lines
24 KiB
C
1013 lines
24 KiB
C
/* 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);
|
|
}
|