// SPDX-License-Identifier: GPL-2.0/* * trace_events_synth - synthetic trace events * * Copyright (C) 2015, 2020 Tom Zanussi <tom.zanussi@linux.intel.com> */ #include <linux/module.h>#include <linux/kallsyms.h>#include <linux/security.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/stacktrace.h>#include <linux/rculist.h>#include <linux/tracefs.h> /* for gfp flag names */#include <linux/trace_events.h>#include <trace/events/mmflags.h> #include "trace_synth.h" static int create_synth_event(int argc, const char **argv);static int synth_event_show(struct seq_file *m, struct dyn_event *ev);static int synth_event_release(struct dyn_event *ev);static bool synth_event_is_busy(struct dyn_event *ev);static bool synth_event_match(const char *system, const char *event, int argc, const char **argv, struct dyn_event *ev); static struct dyn_event_operations synth_event_ops = { .create = create_synth_event, .show = synth_event_show, .is_busy = synth_event_is_busy, .free = synth_event_release, .match = synth_event_match,}; static bool is_synth_event(struct dyn_event *ev){ return ev->ops == &synth_event_ops;} static struct synth_event *to_synth_event(struct dyn_event *ev){ return container_of(ev, struct synth_event, devent);} static bool synth_event_is_busy(struct dyn_event *ev){ struct synth_event *event = to_synth_event(ev); return event->ref != 0;} static bool synth_event_match(const char *system, const char *event, int argc, const char **argv, struct dyn_event *ev){ struct synth_event *sev = to_synth_event(ev); return strcmp(sev->name, event) == 0 && (!system || strcmp(system, SYNTH_SYSTEM) == 0);} struct synth_trace_event { struct trace_entry ent; u64 fields[];}; static int synth_event_define_fields(struct trace_event_call *call){ struct synth_trace_event trace; int offset = offsetof(typeof(trace), fields); struct synth_event *event = call->data; unsigned int i, size, n_u64; char *name, *type; bool is_signed; int ret = 0; for (i = 0, n_u64 = 0; i < event->n_fields; i++) { size = event->fields[i]->size; is_signed = event->fields[i]->is_signed; type = event->fields[i]->type; name = event->fields[i]->name; ret = trace_define_field(call, type, name, offset, size, is_signed, FILTER_OTHER); if (ret) break; event->fields[i]->offset = n_u64; if (event->fields[i]->is_string) { offset += STR_VAR_LEN_MAX; n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { offset += sizeof(u64); n_u64++; } } event->n_u64 = n_u64; return ret;} static bool synth_field_signed(char *type){ if (str_has_prefix(type, "u")) return false; if (strcmp(type, "gfp_t") == 0) return false; return true;} static int synth_field_is_string(char *type){ if (strstr(type, "char[") != NULL) return true; return false;} static int synth_field_string_size(char *type){ char buf[4], *end, *start; unsigned int len; int size, err; start = strstr(type, "char["); if (start == NULL) return -EINVAL; start += sizeof("char[") - 1; end = strchr(type, ']'); if (!end || end < start) return -EINVAL; len = end - start; if (len > 3) return -EINVAL; strncpy(buf, start, len); buf[len] = '\0'; err = kstrtouint(buf, 0, &size); if (err) return err; if (size > STR_VAR_LEN_MAX) return -EINVAL; return size;} static int synth_field_size(char *type){ int size = 0; if (strcmp(type, "s64") == 0) size = sizeof(s64); else if (strcmp(type, "u64") == 0) size = sizeof(u64); else if (strcmp(type, "s32") == 0) size = sizeof(s32); else if (strcmp(type, "u32") == 0) size = sizeof(u32); else if (strcmp(type, "s16") == 0) size = sizeof(s16); else if (strcmp(type, "u16") == 0) size = sizeof(u16); else if (strcmp(type, "s8") == 0) size = sizeof(s8); else if (strcmp(type, "u8") == 0) size = sizeof(u8); else if (strcmp(type, "char") == 0) size = sizeof(char); else if (strcmp(type, "unsigned char") == 0) size = sizeof(unsigned char); else if (strcmp(type, "int") == 0) size = sizeof(int); else if (strcmp(type, "unsigned int") == 0) size = sizeof(unsigned int); else if (strcmp(type, "long") == 0) size = sizeof(long); else if (strcmp(type, "unsigned long") == 0) size = sizeof(unsigned long); else if (strcmp(type, "pid_t") == 0) size = sizeof(pid_t); else if (strcmp(type, "gfp_t") == 0) size = sizeof(gfp_t); else if (synth_field_is_string(type)) size = synth_field_string_size(type); return size;} static const char *synth_field_fmt(char *type){ const char *fmt = "%llu"; if (strcmp(type, "s64") == 0) fmt = "%lld"; else if (strcmp(type, "u64") == 0) fmt = "%llu"; else if (strcmp(type, "s32") == 0) fmt = "%d"; else if (strcmp(type, "u32") == 0) fmt = "%u"; else if (strcmp(type, "s16") == 0) fmt = "%d"; else if (strcmp(type, "u16") == 0) fmt = "%u"; else if (strcmp(type, "s8") == 0) fmt = "%d"; else if (strcmp(type, "u8") == 0) fmt = "%u"; else if (strcmp(type, "char") == 0) fmt = "%d"; else if (strcmp(type, "unsigned char") == 0) fmt = "%u"; else if (strcmp(type, "int") == 0) fmt = "%d"; else if (strcmp(type, "unsigned int") == 0) fmt = "%u"; else if (strcmp(type, "long") == 0) fmt = "%ld"; else if (strcmp(type, "unsigned long") == 0) fmt = "%lu"; else if (strcmp(type, "pid_t") == 0) fmt = "%d"; else if (strcmp(type, "gfp_t") == 0) fmt = "%x"; else if (synth_field_is_string(type)) fmt = "%s"; return fmt;} static void print_synth_event_num_val(struct trace_seq *s, char *print_fmt, char *name, int size, u64 val, char *space){ switch (size) { case 1: trace_seq_printf(s, print_fmt, name, (u8)val, space); break; case 2: trace_seq_printf(s, print_fmt, name, (u16)val, space); break; case 4: trace_seq_printf(s, print_fmt, name, (u32)val, space); break; default: trace_seq_printf(s, print_fmt, name, val, space); break; }} static enum print_line_t print_synth_event(struct trace_iterator *iter, int flags, struct trace_event *event){ struct trace_array *tr = iter->tr; struct trace_seq *s = &iter->seq; struct synth_trace_event *entry; struct synth_event *se; unsigned int i, n_u64; char print_fmt[32]; const char *fmt; entry = (struct synth_trace_event *)iter->ent; se = container_of(event, struct synth_event, call.event); trace_seq_printf(s, "%s: ", se->name); for (i = 0, n_u64 = 0; i < se->n_fields; i++) { if (trace_seq_has_overflowed(s)) goto end; fmt = synth_field_fmt(se->fields[i]->type); /* parameter types */ if (tr && tr->trace_flags & TRACE_ITER_VERBOSE) trace_seq_printf(s, "%s ", fmt); snprintf(print_fmt, sizeof(print_fmt), "%%s=%s%%s", fmt); /* parameter values */ if (se->fields[i]->is_string) { trace_seq_printf(s, print_fmt, se->fields[i]->name, (char *)&entry->fields[n_u64], i == se->n_fields - 1 ? "" : " "); n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { struct trace_print_flags __flags[] = { __def_gfpflag_names, {-1, NULL} }; char *space = (i == se->n_fields - 1 ? "" : " "); print_synth_event_num_val(s, print_fmt, se->fields[i]->name, se->fields[i]->size, entry->fields[n_u64], space); if (strcmp(se->fields[i]->type, "gfp_t") == 0) { trace_seq_puts(s, " ("); trace_print_flags_seq(s, "|", entry->fields[n_u64], __flags); trace_seq_putc(s, ')'); } n_u64++; } }end: trace_seq_putc(s, '\n'); return trace_handle_return(s);} static struct trace_event_functions synth_event_funcs = { .trace = print_synth_event}; static notrace void trace_event_raw_event_synth(void *__data, u64 *var_ref_vals, unsigned int *var_ref_idx){ struct trace_event_file *trace_file = __data; struct synth_trace_event *entry; struct trace_event_buffer fbuffer; struct trace_buffer *buffer; struct synth_event *event; unsigned int i, n_u64, val_idx; int fields_size = 0; event = trace_file->event_call->data; if (trace_trigger_soft_disabled(trace_file)) return; fields_size = event->n_u64 * sizeof(u64); /* * Avoid ring buffer recursion detection, as this event * is being performed within another event. */ buffer = trace_file->tr->array_buffer.buffer; ring_buffer_nest_start(buffer); entry = trace_event_buffer_reserve(&fbuffer, trace_file, sizeof(*entry) + fields_size); if (!entry) goto out; for (i = 0, n_u64 = 0; i < event->n_fields; i++) { val_idx = var_ref_idx[i]; if (event->fields[i]->is_string) { char *str_val = (char *)(long)var_ref_vals[val_idx]; char *str_field = (char *)&entry->fields[n_u64]; strscpy(str_field, str_val, STR_VAR_LEN_MAX); n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { struct synth_field *field = event->fields[i]; u64 val = var_ref_vals[val_idx]; switch (field->size) { case 1: *(u8 *)&entry->fields[n_u64] = (u8)val; break; case 2: *(u16 *)&entry->fields[n_u64] = (u16)val; break; case 4: *(u32 *)&entry->fields[n_u64] = (u32)val; break; default: entry->fields[n_u64] = val; break; } n_u64++; } } trace_event_buffer_commit(&fbuffer);out: ring_buffer_nest_end(buffer);} static void free_synth_event_print_fmt(struct trace_event_call *call){ if (call) { kfree(call->print_fmt); call->print_fmt = NULL; }} static int __set_synth_event_print_fmt(struct synth_event *event, char *buf, int len){ const char *fmt; int pos = 0; int i; /* When len=0, we just calculate the needed length */#define LEN_OR_ZERO (len ? len - pos : 0) pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); for (i = 0; i < event->n_fields; i++) { fmt = synth_field_fmt(event->fields[i]->type); pos += snprintf(buf + pos, LEN_OR_ZERO, "%s=%s%s", event->fields[i]->name, fmt, i == event->n_fields - 1 ? "" : ", "); } pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); for (i = 0; i < event->n_fields; i++) { pos += snprintf(buf + pos, LEN_OR_ZERO, ", REC->%s", event->fields[i]->name); } #undef LEN_OR_ZERO /* return the length of print_fmt */ return pos;} static int set_synth_event_print_fmt(struct trace_event_call *call){ struct synth_event *event = call->data; char *print_fmt; int len; /* First: called with 0 length to calculate the needed length */ len = __set_synth_event_print_fmt(event, NULL, 0); print_fmt = kmalloc(len + 1, GFP_KERNEL); if (!print_fmt) return -ENOMEM; /* Second: actually write the @print_fmt */ __set_synth_event_print_fmt(event, print_fmt, len + 1); call->print_fmt = print_fmt; return 0;} static void free_synth_field(struct synth_field *field){ kfree(field->type); kfree(field->name); kfree(field);} static struct synth_field *parse_synth_field(int argc, const char **argv, int *consumed){ struct synth_field *field; const char *prefix = NULL, *field_type = argv[0], *field_name, *array; int len, ret = 0; if (field_type[0] == ';') field_type++; if (!strcmp(field_type, "unsigned")) { if (argc < 3) return ERR_PTR(-EINVAL); prefix = "unsigned "; field_type = argv[1]; field_name = argv[2]; *consumed = 3; } else { field_name = argv[1]; *consumed = 2; } field = kzalloc(sizeof(*field), GFP_KERNEL); if (!field) return ERR_PTR(-ENOMEM); len = strlen(field_name); array = strchr(field_name, '['); if (array) len -= strlen(array); else if (field_name[len - 1] == ';') len--; field->name = kmemdup_nul(field_name, len, GFP_KERNEL); if (!field->name) { ret = -ENOMEM; goto free; } Lines 1–500 of 1789. Load more
// SPDX-License-Identifier: GPL-2.0 /* * trace_events_synth - synthetic trace events * * Copyright (C) 2015, 2020 Tom Zanussi <tom.zanussi@linux.intel.com> */ #include <linux/module.h> #include <linux/kallsyms.h> #include <linux/security.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/stacktrace.h> #include <linux/rculist.h> #include <linux/tracefs.h> /* for gfp flag names */ #include <linux/trace_events.h> #include <trace/events/mmflags.h> #include "trace_synth.h" static int create_synth_event(int argc, const char **argv); static int synth_event_show(struct seq_file *m, struct dyn_event *ev); static int synth_event_release(struct dyn_event *ev); static bool synth_event_is_busy(struct dyn_event *ev); static bool synth_event_match(const char *system, const char *event, int argc, const char **argv, struct dyn_event *ev); static struct dyn_event_operations synth_event_ops = { .create = create_synth_event, .show = synth_event_show, .is_busy = synth_event_is_busy, .free = synth_event_release, .match = synth_event_match, }; static bool is_synth_event(struct dyn_event *ev) { return ev->ops == &synth_event_ops; } static struct synth_event *to_synth_event(struct dyn_event *ev) { return container_of(ev, struct synth_event, devent); } static bool synth_event_is_busy(struct dyn_event *ev) { struct synth_event *event = to_synth_event(ev); return event->ref != 0; } static bool synth_event_match(const char *system, const char *event, int argc, const char **argv, struct dyn_event *ev) { struct synth_event *sev = to_synth_event(ev); return strcmp(sev->name, event) == 0 && (!system || strcmp(system, SYNTH_SYSTEM) == 0); } struct synth_trace_event { struct trace_entry ent; u64 fields[]; }; static int synth_event_define_fields(struct trace_event_call *call) { struct synth_trace_event trace; int offset = offsetof(typeof(trace), fields); struct synth_event *event = call->data; unsigned int i, size, n_u64; char *name, *type; bool is_signed; int ret = 0; for (i = 0, n_u64 = 0; i < event->n_fields; i++) { size = event->fields[i]->size; is_signed = event->fields[i]->is_signed; type = event->fields[i]->type; name = event->fields[i]->name; ret = trace_define_field(call, type, name, offset, size, is_signed, FILTER_OTHER); if (ret) break; event->fields[i]->offset = n_u64; if (event->fields[i]->is_string) { offset += STR_VAR_LEN_MAX; n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { offset += sizeof(u64); n_u64++; } } event->n_u64 = n_u64; return ret; } static bool synth_field_signed(char *type) { if (str_has_prefix(type, "u")) return false; if (strcmp(type, "gfp_t") == 0) return false; return true; } static int synth_field_is_string(char *type) { if (strstr(type, "char[") != NULL) return true; return false; } static int synth_field_string_size(char *type) { char buf[4], *end, *start; unsigned int len; int size, err; start = strstr(type, "char["); if (start == NULL) return -EINVAL; start += sizeof("char[") - 1; end = strchr(type, ']'); if (!end || end < start) return -EINVAL; len = end - start; if (len > 3) return -EINVAL; strncpy(buf, start, len); buf[len] = '\0'; err = kstrtouint(buf, 0, &size); if (err) return err; if (size > STR_VAR_LEN_MAX) return -EINVAL; return size; } static int synth_field_size(char *type) { int size = 0; if (strcmp(type, "s64") == 0) size = sizeof(s64); else if (strcmp(type, "u64") == 0) size = sizeof(u64); else if (strcmp(type, "s32") == 0) size = sizeof(s32); else if (strcmp(type, "u32") == 0) size = sizeof(u32); else if (strcmp(type, "s16") == 0) size = sizeof(s16); else if (strcmp(type, "u16") == 0) size = sizeof(u16); else if (strcmp(type, "s8") == 0) size = sizeof(s8); else if (strcmp(type, "u8") == 0) size = sizeof(u8); else if (strcmp(type, "char") == 0) size = sizeof(char); else if (strcmp(type, "unsigned char") == 0) size = sizeof(unsigned char); else if (strcmp(type, "int") == 0) size = sizeof(int); else if (strcmp(type, "unsigned int") == 0) size = sizeof(unsigned int); else if (strcmp(type, "long") == 0) size = sizeof(long); else if (strcmp(type, "unsigned long") == 0) size = sizeof(unsigned long); else if (strcmp(type, "pid_t") == 0) size = sizeof(pid_t); else if (strcmp(type, "gfp_t") == 0) size = sizeof(gfp_t); else if (synth_field_is_string(type)) size = synth_field_string_size(type); return size; } static const char *synth_field_fmt(char *type) { const char *fmt = "%llu"; if (strcmp(type, "s64") == 0) fmt = "%lld"; else if (strcmp(type, "u64") == 0) fmt = "%llu"; else if (strcmp(type, "s32") == 0) fmt = "%d"; else if (strcmp(type, "u32") == 0) fmt = "%u"; else if (strcmp(type, "s16") == 0) fmt = "%d"; else if (strcmp(type, "u16") == 0) fmt = "%u"; else if (strcmp(type, "s8") == 0) fmt = "%d"; else if (strcmp(type, "u8") == 0) fmt = "%u"; else if (strcmp(type, "char") == 0) fmt = "%d"; else if (strcmp(type, "unsigned char") == 0) fmt = "%u"; else if (strcmp(type, "int") == 0) fmt = "%d"; else if (strcmp(type, "unsigned int") == 0) fmt = "%u"; else if (strcmp(type, "long") == 0) fmt = "%ld"; else if (strcmp(type, "unsigned long") == 0) fmt = "%lu"; else if (strcmp(type, "pid_t") == 0) fmt = "%d"; else if (strcmp(type, "gfp_t") == 0) fmt = "%x"; else if (synth_field_is_string(type)) fmt = "%s"; return fmt; } static void print_synth_event_num_val(struct trace_seq *s, char *print_fmt, char *name, int size, u64 val, char *space) { switch (size) { case 1: trace_seq_printf(s, print_fmt, name, (u8)val, space); break; case 2: trace_seq_printf(s, print_fmt, name, (u16)val, space); break; case 4: trace_seq_printf(s, print_fmt, name, (u32)val, space); break; default: trace_seq_printf(s, print_fmt, name, val, space); break; } } static enum print_line_t print_synth_event(struct trace_iterator *iter, int flags, struct trace_event *event) { struct trace_array *tr = iter->tr; struct trace_seq *s = &iter->seq; struct synth_trace_event *entry; struct synth_event *se; unsigned int i, n_u64; char print_fmt[32]; const char *fmt; entry = (struct synth_trace_event *)iter->ent; se = container_of(event, struct synth_event, call.event); trace_seq_printf(s, "%s: ", se->name); for (i = 0, n_u64 = 0; i < se->n_fields; i++) { if (trace_seq_has_overflowed(s)) goto end; fmt = synth_field_fmt(se->fields[i]->type); /* parameter types */ if (tr && tr->trace_flags & TRACE_ITER_VERBOSE) trace_seq_printf(s, "%s ", fmt); snprintf(print_fmt, sizeof(print_fmt), "%%s=%s%%s", fmt); /* parameter values */ if (se->fields[i]->is_string) { trace_seq_printf(s, print_fmt, se->fields[i]->name, (char *)&entry->fields[n_u64], i == se->n_fields - 1 ? "" : " "); n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { struct trace_print_flags __flags[] = { __def_gfpflag_names, {-1, NULL} }; char *space = (i == se->n_fields - 1 ? "" : " "); print_synth_event_num_val(s, print_fmt, se->fields[i]->name, se->fields[i]->size, entry->fields[n_u64], space); if (strcmp(se->fields[i]->type, "gfp_t") == 0) { trace_seq_puts(s, " ("); trace_print_flags_seq(s, "|", entry->fields[n_u64], __flags); trace_seq_putc(s, ')'); } n_u64++; } } end: trace_seq_putc(s, '\n'); return trace_handle_return(s); } static struct trace_event_functions synth_event_funcs = { .trace = print_synth_event }; static notrace void trace_event_raw_event_synth(void *__data, u64 *var_ref_vals, unsigned int *var_ref_idx) { struct trace_event_file *trace_file = __data; struct synth_trace_event *entry; struct trace_event_buffer fbuffer; struct trace_buffer *buffer; struct synth_event *event; unsigned int i, n_u64, val_idx; int fields_size = 0; event = trace_file->event_call->data; if (trace_trigger_soft_disabled(trace_file)) return; fields_size = event->n_u64 * sizeof(u64); /* * Avoid ring buffer recursion detection, as this event * is being performed within another event. */ buffer = trace_file->tr->array_buffer.buffer; ring_buffer_nest_start(buffer); entry = trace_event_buffer_reserve(&fbuffer, trace_file, sizeof(*entry) + fields_size); if (!entry) goto out; for (i = 0, n_u64 = 0; i < event->n_fields; i++) { val_idx = var_ref_idx[i]; if (event->fields[i]->is_string) { char *str_val = (char *)(long)var_ref_vals[val_idx]; char *str_field = (char *)&entry->fields[n_u64]; strscpy(str_field, str_val, STR_VAR_LEN_MAX); n_u64 += STR_VAR_LEN_MAX / sizeof(u64); } else { struct synth_field *field = event->fields[i]; u64 val = var_ref_vals[val_idx]; switch (field->size) { case 1: *(u8 *)&entry->fields[n_u64] = (u8)val; break; case 2: *(u16 *)&entry->fields[n_u64] = (u16)val; break; case 4: *(u32 *)&entry->fields[n_u64] = (u32)val; break; default: entry->fields[n_u64] = val; break; } n_u64++; } } trace_event_buffer_commit(&fbuffer); out: ring_buffer_nest_end(buffer); } static void free_synth_event_print_fmt(struct trace_event_call *call) { if (call) { kfree(call->print_fmt); call->print_fmt = NULL; } } static int __set_synth_event_print_fmt(struct synth_event *event, char *buf, int len) { const char *fmt; int pos = 0; int i; /* When len=0, we just calculate the needed length */ #define LEN_OR_ZERO (len ? len - pos : 0) pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); for (i = 0; i < event->n_fields; i++) { fmt = synth_field_fmt(event->fields[i]->type); pos += snprintf(buf + pos, LEN_OR_ZERO, "%s=%s%s", event->fields[i]->name, fmt, i == event->n_fields - 1 ? "" : ", "); } pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); for (i = 0; i < event->n_fields; i++) { pos += snprintf(buf + pos, LEN_OR_ZERO, ", REC->%s", event->fields[i]->name); } #undef LEN_OR_ZERO /* return the length of print_fmt */ return pos; } static int set_synth_event_print_fmt(struct trace_event_call *call) { struct synth_event *event = call->data; char *print_fmt; int len; /* First: called with 0 length to calculate the needed length */ len = __set_synth_event_print_fmt(event, NULL, 0); print_fmt = kmalloc(len + 1, GFP_KERNEL); if (!print_fmt) return -ENOMEM; /* Second: actually write the @print_fmt */ __set_synth_event_print_fmt(event, print_fmt, len + 1); call->print_fmt = print_fmt; return 0; } static void free_synth_field(struct synth_field *field) { kfree(field->type); kfree(field->name); kfree(field); } static struct synth_field *parse_synth_field(int argc, const char **argv, int *consumed) { struct synth_field *field; const char *prefix = NULL, *field_type = argv[0], *field_name, *array; int len, ret = 0; if (field_type[0] == ';') field_type++; if (!strcmp(field_type, "unsigned")) { if (argc < 3) return ERR_PTR(-EINVAL); prefix = "unsigned "; field_type = argv[1]; field_name = argv[2]; *consumed = 3; } else { field_name = argv[1]; *consumed = 2; } field = kzalloc(sizeof(*field), GFP_KERNEL); if (!field) return ERR_PTR(-ENOMEM); len = strlen(field_name); array = strchr(field_name, '['); if (array) len -= strlen(array); else if (field_name[len - 1] == ';') len--; field->name = kmemdup_nul(field_name, len, GFP_KERNEL); if (!field->name) { ret = -ENOMEM; goto free; }