blob: 7a3c45ce25a30d3165e301584401ae76a7f3af35 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
*
* Updates:
* Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include "tracefs.h"
#include "tracefs-local.h"
#define DYNEVENTS_EVENTS "dynamic_events"
#define KPROBE_EVENTS "kprobe_events"
#define UPROBE_EVENTS "uprobe_events"
#define SYNTH_EVENTS "synthetic_events"
#define DYNEVENTS_DEFAULT_GROUP "dynamic"
#define EVENT_INDEX(B) (ffs(B) - 1)
struct dyn_events_desc;
static int dyn_generic_parse(struct dyn_events_desc *,
const char *, char *, struct tracefs_dynevent **);
static int dyn_synth_parse(struct dyn_events_desc *,
const char *, char *, struct tracefs_dynevent **);
static int dyn_generic_del(struct dyn_events_desc *, struct tracefs_dynevent *);
static int dyn_synth_del(struct dyn_events_desc *, struct tracefs_dynevent *);
struct dyn_events_desc {
enum tracefs_dynevent_type type;
const char *file;
const char *prefix;
int (*del)(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn);
int (*parse)(struct dyn_events_desc *desc, const char *group,
char *line, struct tracefs_dynevent **ret_dyn);
} dynevents[] = {
{TRACEFS_DYNEVENT_KPROBE, KPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse},
{TRACEFS_DYNEVENT_KRETPROBE, KPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse},
{TRACEFS_DYNEVENT_UPROBE, UPROBE_EVENTS, "p", dyn_generic_del, dyn_generic_parse},
{TRACEFS_DYNEVENT_URETPROBE, UPROBE_EVENTS, "r", dyn_generic_del, dyn_generic_parse},
{TRACEFS_DYNEVENT_EPROBE, "", "e", dyn_generic_del, dyn_generic_parse},
{TRACEFS_DYNEVENT_SYNTH, SYNTH_EVENTS, "", dyn_synth_del, dyn_synth_parse},
};
static int dyn_generic_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
{
char *str;
int ret;
if (dyn->system)
ret = asprintf(&str, "-:%s/%s", dyn->system, dyn->event);
else
ret = asprintf(&str, "-:%s", dyn->event);
if (ret < 0)
return -1;
ret = tracefs_instance_file_append(NULL, desc->file, str);
free(str);
return ret < 0 ? ret : 0;
}
/**
* tracefs_dynevent_free - Free a dynamic event context
* @devent: Pointer to a dynamic event context
*
* The dynamic event, described by this context, is not
* removed from the system by this API. It only frees the memory.
*/
void tracefs_dynevent_free(struct tracefs_dynevent *devent)
{
if (!devent)
return;
free(devent->system);
free(devent->event);
free(devent->address);
free(devent->format);
free(devent->prefix);
free(devent->trace_file);
free(devent);
}
static void parse_prefix(char *word, char **prefix, char **system, char **name)
{
char *sav;
*prefix = NULL;
*system = NULL;
*name = NULL;
*prefix = strtok_r(word, ":", &sav);
*system = strtok_r(NULL, "/", &sav);
if (!(*system))
return;
*name = strtok_r(NULL, " \t", &sav);
if (!(*name)) {
*name = *system;
*system = NULL;
}
}
/*
* Parse lines from dynamic_events, kprobe_events and uprobe_events files
* PREFIX[:[SYSTEM/]EVENT] [ADDRSS] [FORMAT]
*/
static int dyn_generic_parse(struct dyn_events_desc *desc, const char *group,
char *line, struct tracefs_dynevent **ret_dyn)
{
struct tracefs_dynevent *dyn;
char *word;
char *format = NULL;
char *address = NULL;
char *system;
char *prefix;
char *event;
char *sav;
if (strncmp(line, desc->prefix, strlen(desc->prefix)))
return -1;
word = strtok_r(line, " \t", &sav);
if (!word || *word == '\0')
return -1;
parse_prefix(word, &prefix, &system, &event);
if (!prefix)
return -1;
if (desc->type != TRACEFS_DYNEVENT_SYNTH) {
address = strtok_r(NULL, " \t", &sav);
if (!address || *address == '\0')
return -1;
}
format = strtok_r(NULL, "", &sav);
/* KPROBEs and UPROBEs share the same prefix, check the format */
if (desc->type & (TRACEFS_DYNEVENT_UPROBE | TRACEFS_DYNEVENT_URETPROBE)) {
if (!strchr(address, '/'))
return -1;
}
if (group && (!system || strcmp(group, system) != 0))
return -1;
if (!ret_dyn)
return 0;
dyn = calloc(1, sizeof(*dyn));
if (!dyn)
return -1;
dyn->type = desc->type;
dyn->trace_file = strdup(desc->file);
if (!dyn->trace_file)
goto error;
dyn->prefix = strdup(prefix);
if (!dyn->prefix)
goto error;
if (system) {
dyn->system = strdup(system);
if (!dyn->system)
goto error;
}
if (event) {
dyn->event = strdup(event);
if (!dyn->event)
goto error;
}
if (address) {
dyn->address = strdup(address);
if (!dyn->address)
goto error;
}
if (format) {
dyn->format = strdup(format);
if (!dyn->format)
goto error;
}
*ret_dyn = dyn;
return 0;
error:
tracefs_dynevent_free(dyn);
return -1;
}
static int dyn_synth_del(struct dyn_events_desc *desc, struct tracefs_dynevent *dyn)
{
char *str;
int ret;
if (!strcmp(desc->file, DYNEVENTS_EVENTS))
return dyn_generic_del(desc, dyn);
ret = asprintf(&str, "!%s", dyn->event);
if (ret < 0)
return -1;
ret = tracefs_instance_file_append(NULL, desc->file, str);
free(str);
return ret < 0 ? ret : 0;
}
/*
* Parse lines from synthetic_events file
* EVENT ARG [ARG]
*/
static int dyn_synth_parse(struct dyn_events_desc *desc, const char *group,
char *line, struct tracefs_dynevent **ret_dyn)
{
struct tracefs_dynevent *dyn;
char *format;
char *event;
char *sav;
if (!strcmp(desc->file, DYNEVENTS_EVENTS))
return dyn_generic_parse(desc, group, line, ret_dyn);
/* synthetic_events file has slightly different syntax */
event = strtok_r(line, " \t", &sav);
if (!event || *event == '\0')
return -1;
format = strtok_r(NULL, "", &sav);
if (!format || *format == '\0')
return -1;
if (!ret_dyn)
return 0;
dyn = calloc(1, sizeof(*dyn));
if (!dyn)
return -1;
dyn->type = desc->type;
dyn->trace_file = strdup(desc->file);
if (!dyn->trace_file)
goto error;
dyn->event = strdup(event);
if (!dyn->event)
goto error;
dyn->format = strdup(format+1);
if (!dyn->format)
goto error;
*ret_dyn = dyn;
return 0;
error:
tracefs_dynevent_free(dyn);
return -1;
}
static void init_devent_desc(void)
{
int i;
BUILD_BUG_ON(ARRAY_SIZE(dynevents) != EVENT_INDEX(TRACEFS_DYNEVENT_MAX));
if (!tracefs_file_exists(NULL, DYNEVENTS_EVENTS))
return;
/* Use ftrace dynamic_events, if available */
for (i = 0; i < EVENT_INDEX(TRACEFS_DYNEVENT_MAX); i++)
dynevents[i].file = DYNEVENTS_EVENTS;
dynevents[EVENT_INDEX(TRACEFS_DYNEVENT_SYNTH)].prefix = "s";
}
static struct dyn_events_desc *get_devent_desc(enum tracefs_dynevent_type type)
{
static bool init;
if (type >= TRACEFS_DYNEVENT_MAX)
return NULL;
if (!init) {
init_devent_desc();
init = true;
}
return &dynevents[EVENT_INDEX(type)];
}
/**
* dynevent_alloc - Allocate new dynamic event
* @type: Type of the dynamic event
* @system: The system name (NULL for the default dynamic)
* @event: Name of the event
* @addr: The function and offset (or address) to insert the probe
* @format: The format string to define the probe.
*
* Allocate a dynamic event context that will be in the @system group
* (or dynamic if @system is NULL). Have the name of @event and
* will be associated to @addr, if applicable for that event type
* (function name, with or without offset, or a address). And the @format will
* define the format of the kprobe.
* The dynamic event is not created in the system.
*
* Return a pointer to a dynamic event context on success, or NULL on error.
* The returned pointer must be freed with tracefs_dynevent_free()
*
* errno will be set to EINVAL if event is NULL.
*/
__hidden struct tracefs_dynevent *
dynevent_alloc(enum tracefs_dynevent_type type, const char *system,
const char *event, const char *address, const char *format)
{
struct tracefs_dynevent *devent;
struct dyn_events_desc *desc;
if (!event) {
errno = EINVAL;
return NULL;
}
desc = get_devent_desc(type);
if (!desc || !desc->file) {
errno = ENOTSUP;
return NULL;
}
devent = calloc(1, sizeof(*devent));
if (!devent)
return NULL;
devent->type = type;
devent->trace_file = strdup(desc->file);
if (!devent->trace_file)
goto err;
if (!system)
system = DYNEVENTS_DEFAULT_GROUP;
devent->system = strdup(system);
if (!devent->system)
goto err;
devent->event = strdup(event);
if (!devent->event)
goto err;
devent->prefix = strdup(desc->prefix);
if (!devent->prefix)
goto err;
if (address) {
devent->address = strdup(address);
if (!devent->address)
goto err;
}
if (format) {
devent->format = strdup(format);
if (!devent->format)
goto err;
}
return devent;
err:
tracefs_dynevent_free(devent);
return NULL;
}
/**
* tracefs_dynevent_create - Create a dynamic event in the system
* @devent: Pointer to a dynamic event context, describing the event
*
* Return 0 on success, or -1 on error.
*/
int tracefs_dynevent_create(struct tracefs_dynevent *devent)
{
char *str;
int ret;
if (!devent)
return -1;
if (devent->system && devent->system[0])
ret = asprintf(&str, "%s%s%s/%s %s %s\n",
devent->prefix, strlen(devent->prefix) ? ":" : "",
devent->system, devent->event,
devent->address ? devent->address : "",
devent->format ? devent->format : "");
else
ret = asprintf(&str, "%s%s%s %s %s\n",
devent->prefix, strlen(devent->prefix) ? ":" : "",
devent->event,
devent->address ? devent->address : "",
devent->format ? devent->format : "");
if (ret < 0)
return -1;
ret = tracefs_instance_file_append(NULL, devent->trace_file, str);
free(str);
return ret < 0 ? ret : 0;
}
static void disable_events(const char *system, const char *event,
char **list)
{
struct tracefs_instance *instance;
int i;
/*
* Note, this will not fail even on error.
* That is because even if something fails, it may still
* work enough to clear the kprobes. If that's the case
* the clearing after the loop will succeed and the function
* is a success, even though other parts had failed. If
* one of the kprobe events is enabled in one of the
* instances that fail, then the clearing will fail too
* and the function will return an error.
*/
tracefs_event_disable(NULL, system, event);
/* No need to test results */
if (!list)
return;
for (i = 0; list[i]; i++) {
instance = tracefs_instance_alloc(NULL, list[i]);
/* If this fails, try the next one */
if (!instance)
continue;
tracefs_event_disable(instance, system, event);
tracefs_instance_free(instance);
}
}
/**
* tracefs_dynevent_destroy - Remove a dynamic event from the system
* @devent: A dynamic event context, describing the dynamic event that will be deleted.
* @force: Will attempt to disable all events before removing them.
*
* The dynamic event context is not freed by this API. It only removes the event from the system.
* If there are any enabled events, and @force is not set, then it will error with -1 and errno
* to be EBUSY.
*
* Return 0 on success, or -1 on error.
*/
int tracefs_dynevent_destroy(struct tracefs_dynevent *devent, bool force)
{
struct dyn_events_desc *desc;
char **instance_list;
if (!devent)
return -1;
if (force) {
instance_list = tracefs_instances(NULL);
disable_events(devent->system, devent->event, instance_list);
tracefs_list_free(instance_list);
}
desc = get_devent_desc(devent->type);
if (!desc)
return -1;
return desc->del(desc, devent);
}
static int get_all_dynevents(enum tracefs_dynevent_type type, const char *system,
struct tracefs_dynevent ***ret_all)
{
struct dyn_events_desc *desc;
struct tracefs_dynevent *devent, **tmp, **all = NULL;
char *content;
int count = 0;
char *line;
char *next;
int ret;
desc = get_devent_desc(type);
if (!desc)
return -1;
content = tracefs_instance_file_read(NULL, desc->file, NULL);
if (!content)
return -1;
line = content;
do {
next = strchr(line, '\n');
if (next)
*next = '\0';
ret = desc->parse(desc, system, line, ret_all ? &devent : NULL);
if (!ret) {
if (ret_all) {
tmp = realloc(all, (count + 1) * sizeof(*tmp));
if (!tmp)
goto error;
all = tmp;
all[count] = devent;
}
count++;
}
line = next + 1;
} while (next);
free(content);
if (ret_all)
*ret_all = all;
return count;
error:
free(content);
free(all);
return -1;
}
/**
* tracefs_dynevent_list_free - Deletes an array of pointers to dynamic event contexts
* @events: An array of pointers to dynamic event contexts. The last element of the array
* must be a NULL pointer.
*/
void tracefs_dynevent_list_free(struct tracefs_dynevent **events)
{
int i;
if (!events)
return;
for (i = 0; events[i]; i++)
tracefs_dynevent_free(events[i]);
free(events);
}
/**
* tracefs_dynevent_get_all - return an array of pointers to dynamic events of given types
* @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
* are considered.
* @system: Get events from that system only. If @system is NULL, events from all systems
* are returned.
*
* Returns an array of pointers to dynamic events of given types that exist in the system.
* The array must be freed with tracefs_dynevent_list_free(). If there are no events a NULL
* pointer is returned.
*/
struct tracefs_dynevent **
tracefs_dynevent_get_all(unsigned int types, const char *system)
{
struct tracefs_dynevent **events, **tmp, **all_events = NULL;
int count, all = 0;
int i;
for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) {
if (types) {
if (i > types)
break;
if (!(types & i))
continue;
}
count = get_all_dynevents(i, system, &events);
if (count > 0) {
tmp = realloc(all_events, (all + count + 1) * sizeof(*tmp));
if (!tmp)
goto error;
all_events = tmp;
memcpy(all_events + all, events, count * sizeof(*events));
all += count;
/* Add a NULL pointer at the end */
all_events[all] = NULL;
free(events);
}
}
return all_events;
error:
if (all_events) {
for (i = 0; i < all; i++)
free(all_events[i]);
free(all_events);
}
return NULL;
}
/**
* tracefs_dynevent_get - return a single dynamic event if it exists
* @type; Dynamic event type
* @system: Get events from that system only. May be NULL.
* @event: Get event of the system type (may not be NULL)
*
* Returns the dynamic event of the given @type and @system for with the @event
* name. If @system is NULL, it will return the first dynamic event that it finds
* that matches the @event name.
*
* The returned event must be freed with tracefs_dynevent_free().
* NULL is returned if no event match is found, or other error.
*/
struct tracefs_dynevent *
tracefs_dynevent_get(enum tracefs_dynevent_type type, const char *system,
const char *event)
{
struct tracefs_dynevent **events;
struct tracefs_dynevent *devent = NULL;
int count;
int i;
if (!event) {
errno = -EINVAL;
return NULL;
}
count = get_all_dynevents(type, system, &events);
if (count <= 0)
return NULL;
for (i = 0; i < count; i++) {
if (strcmp(events[i]->event, event) == 0)
break;
}
if (i < count) {
devent = events[i];
events[i] = NULL;
}
tracefs_dynevent_list_free(events);
return devent;
}
/**
* tracefs_dynevent_destroy_all - removes all dynamic events of given types from the system
* @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
* are considered.
* @force: Will attempt to disable all events before removing them.
*
* Will remove all dynamic events of the given types from the system. If there are any enabled
* events, and @force is not set, then the removal of these will fail. If @force is set, then
* it will attempt to disable all the events in all instances before removing them.
*
* Returns zero if all requested events are removed successfully, or -1 if some of them are not
* removed.
*/
int tracefs_dynevent_destroy_all(unsigned int types, bool force)
{
struct tracefs_dynevent **all;
int ret = 0;
int i;
all = tracefs_dynevent_get_all(types, NULL);
if (!all)
return 0;
for (i = 0; all[i]; i++) {
if (tracefs_dynevent_destroy(all[i], force))
ret = -1;
}
tracefs_dynevent_list_free(all);
return ret;
}
/**
* dynevent_get_count - Count dynamic events of given types and system
* @types: Dynamic event type, or bitmask of dynamic event types. If 0 is passed, all types
* are considered.
* @system: Count events from that system only. If @system is NULL, events from all systems
* are counted.
*
* Return the count of requested dynamic events
*/
__hidden int dynevent_get_count(unsigned int types, const char *system)
{
int count, all = 0;
int i;
for (i = 1; i < TRACEFS_DYNEVENT_MAX; i <<= 1) {
if (types) {
if (i > types)
break;
if (!(types & i))
continue;
}
count = get_all_dynevents(i, system, NULL);
if (count > 0)
all += count;
}
return all;
}
static enum tracefs_dynevent_type
dynevent_info(struct tracefs_dynevent *dynevent, char **system,
char **event, char **prefix, char **addr, char **format)
{
char **lv[] = { system, event, prefix, addr, format };
char **rv[] = { &dynevent->system, &dynevent->event, &dynevent->prefix,
&dynevent->address, &dynevent->format };
int i;
for (i = 0; i < ARRAY_SIZE(lv); i++) {
if (lv[i]) {
if (*rv[i]) {
*lv[i] = strdup(*rv[i]);
if (!*lv[i])
goto error;
} else {
*lv[i] = NULL;
}
}
}
return dynevent->type;
error:
for (i--; i >= 0; i--) {
if (lv[i])
free(*lv[i]);
}
return TRACEFS_DYNEVENT_UNKNOWN;
}
/**
* tracefs_dynevent_info - return details of a dynamic event
* @dynevent: A dynamic event context, describing given dynamic event.
* @group: return, group in which the dynamic event is configured
* @event: return, name of the dynamic event
* @prefix: return, prefix string of the dynamic event
* @addr: return, the function and offset (or address) of the dynamic event
* @format: return, the format string of the dynamic event
*
* Returns the type of the dynamic event, or TRACEFS_DYNEVENT_UNKNOWN in case of an error.
* Any of the @group, @event, @prefix, @addr and @format parameters are optional.
* If a valid pointer is passed, in case of success - a string is allocated and returned.
* These strings must be freed with free().
*/
enum tracefs_dynevent_type
tracefs_dynevent_info(struct tracefs_dynevent *dynevent, char **system,
char **event, char **prefix, char **addr, char **format)
{
if (!dynevent)
return TRACEFS_DYNEVENT_UNKNOWN;
return dynevent_info(dynevent, system, event, prefix, addr, format);
}
/**
* tracefs_dynevent_get_event - return tep event representing the given dynamic event
* @tep: a handle to the trace event parser context that holds the events
* @dynevent: a dynamic event context, describing given dynamic event.
*
* Returns a pointer to a tep event describing the given dynamic event. The pointer
* is managed by the @tep handle and must not be freed. In case of an error, or in case
* the requested dynamic event is missing in the @tep handler - NULL is returned.
*/
struct tep_event *
tracefs_dynevent_get_event(struct tep_handle *tep, struct tracefs_dynevent *dynevent)
{
if (!tep || !dynevent || !dynevent->event)
return NULL;
return get_tep_event(tep, dynevent->system, dynevent->event);
}