// SPDX-License-Identifier: LGPL-2.1 /* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * * * The parts for function graph printing was taken and modified from the * Linux Kernel that were written by * - Copyright (C) 2009 Frederic Weisbecker, * Frederic Weisbecker gave his permission to relicense the code to * the Lesser General Public License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "event-parse.h" #include "event-parse-local.h" #include "event-utils.h" #include "trace-seq.h" static int is_flag_field; static int is_symbolic_field; static int show_warning = 1; #define do_warning(fmt, ...) \ do { \ if (show_warning) \ tep_warning(fmt, ##__VA_ARGS__);\ } while (0) #define do_warning_event(event, fmt, ...) \ do { \ if (!show_warning) \ continue; \ \ if (event) \ tep_warning("[%s:%s] " fmt, event->system, \ event->name, ##__VA_ARGS__); \ else \ tep_warning(fmt, ##__VA_ARGS__); \ } while (0) /** * init_input_buf - init buffer for parsing * @buf: buffer to parse * @size: the size of the buffer * * Initializes the internal buffer that tep_read_token() will parse. */ __hidden void init_input_buf(struct tep_handle *tep, const char *buf, unsigned long long size) { tep->input_buf = buf; tep->input_buf_siz = size; tep->input_buf_ptr = 0; } __hidden const char *get_input_buf(struct tep_handle *tep) { return tep->input_buf; } __hidden unsigned long long get_input_buf_ptr(struct tep_handle *tep) { return tep->input_buf_ptr; } struct event_handler { struct event_handler *next; int id; const char *sys_name; const char *event_name; tep_event_handler_func func; void *context; }; struct func_params { struct func_params *next; enum tep_func_arg_type type; }; struct tep_function_handler { struct tep_function_handler *next; enum tep_func_arg_type ret_type; char *name; tep_func_handler func; struct func_params *params; int nr_args; }; static unsigned long long process_defined_func(struct trace_seq *s, void *data, int size, struct tep_event *event, struct tep_print_arg *arg); static void free_func_handle(struct tep_function_handler *func); void breakpoint(void) { static int x; x++; } static const char *get_event_type(enum tep_event_type type) { switch (type) { case TEP_EVENT_ERROR: return "ERROR"; case TEP_EVENT_NONE: return "NONE"; case TEP_EVENT_SPACE: return "SPACE"; case TEP_EVENT_NEWLINE: return "NEWLINE"; case TEP_EVENT_OP: return "OP"; case TEP_EVENT_DELIM: return "DELIM"; case TEP_EVENT_ITEM: return "ITEM"; case TEP_EVENT_DQUOTE: return "DQUOTE"; case TEP_EVENT_SQUOTE: return "SQUOTE"; } return "(UNKNOWN)"; } static struct tep_print_arg *alloc_arg(void) { return calloc(1, sizeof(struct tep_print_arg)); } struct tep_cmdline { char *comm; int pid; }; static int cmdline_cmp(const void *a, const void *b) { const struct tep_cmdline *ca = a; const struct tep_cmdline *cb = b; if (ca->pid < cb->pid) return -1; if (ca->pid > cb->pid) return 1; return 0; } /* Looking for where to place the key */ static int cmdline_slot_cmp(const void *a, const void *b) { const struct tep_cmdline *ca = a; const struct tep_cmdline *cb = b; const struct tep_cmdline *cb1 = cb + 1; if (ca->pid < cb->pid) return -1; if (ca->pid > cb->pid) { if (ca->pid <= cb1->pid) return 0; return 1; } return 0; } struct cmdline_list { struct cmdline_list *next; char *comm; int pid; }; static int cmdline_init(struct tep_handle *tep) { struct cmdline_list *cmdlist = tep->cmdlist; struct cmdline_list *item; struct tep_cmdline *cmdlines; int i; cmdlines = malloc(sizeof(*cmdlines) * tep->cmdline_count); if (!cmdlines) return -1; i = 0; while (cmdlist) { cmdlines[i].pid = cmdlist->pid; cmdlines[i].comm = cmdlist->comm; i++; item = cmdlist; cmdlist = cmdlist->next; free(item); } qsort(cmdlines, tep->cmdline_count, sizeof(*cmdlines), cmdline_cmp); tep->cmdlines = cmdlines; tep->cmdlist = NULL; return 0; } static const char *find_cmdline(struct tep_handle *tep, int pid) { const struct tep_cmdline *comm; struct tep_cmdline key; if (!pid) return ""; if (!tep->cmdlines && cmdline_init(tep)) return ""; key.pid = pid; comm = bsearch(&key, tep->cmdlines, tep->cmdline_count, sizeof(*tep->cmdlines), cmdline_cmp); if (comm) return comm->comm; return "<...>"; } /** * tep_is_pid_registered - return if a pid has a cmdline registered * @tep: a handle to the trace event parser context * @pid: The pid to check if it has a cmdline registered with. * * Returns true if the pid has a cmdline mapped to it * false otherwise. */ bool tep_is_pid_registered(struct tep_handle *tep, int pid) { const struct tep_cmdline *comm; struct tep_cmdline key; if (!pid) return true; if (!tep->cmdlines && cmdline_init(tep)) return false; key.pid = pid; comm = bsearch(&key, tep->cmdlines, tep->cmdline_count, sizeof(*tep->cmdlines), cmdline_cmp); if (comm) return true; return false; } /* * If the command lines have been converted to an array, then * we must add this pid. This is much slower than when cmdlines * are added before the array is initialized. */ static int add_new_comm(struct tep_handle *tep, const char *comm, int pid, bool override) { struct tep_cmdline *cmdlines = tep->cmdlines; struct tep_cmdline *cmdline; struct tep_cmdline key; char *new_comm; int cnt; if (!pid) return 0; /* avoid duplicates */ key.pid = pid; cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count, sizeof(*tep->cmdlines), cmdline_cmp); if (cmdline) { if (!override) { errno = EEXIST; return -1; } new_comm = strdup(comm); if (!new_comm) { errno = ENOMEM; return -1; } free(cmdline->comm); cmdline->comm = new_comm; return 0; } cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (tep->cmdline_count + 1)); if (!cmdlines) { errno = ENOMEM; return -1; } tep->cmdlines = cmdlines; key.comm = strdup(comm); if (!key.comm) { errno = ENOMEM; return -1; } if (!tep->cmdline_count) { /* no entries yet */ tep->cmdlines[0] = key; tep->cmdline_count++; return 0; } /* Now find where we want to store the new cmdline */ cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count - 1, sizeof(*tep->cmdlines), cmdline_slot_cmp); cnt = tep->cmdline_count; if (cmdline) { /* cmdline points to the one before the spot we want */ cmdline++; cnt -= cmdline - tep->cmdlines; } else { /* The new entry is either before or after the list */ if (key.pid > tep->cmdlines[tep->cmdline_count - 1].pid) { tep->cmdlines[tep->cmdline_count++] = key; return 0; } cmdline = &tep->cmdlines[0]; } memmove(cmdline + 1, cmdline, (cnt * sizeof(*cmdline))); *cmdline = key; tep->cmdline_count++; return 0; } static int _tep_register_comm(struct tep_handle *tep, const char *comm, int pid, bool override) { struct cmdline_list *item; if (tep->cmdlines) return add_new_comm(tep, comm, pid, override); item = malloc(sizeof(*item)); if (!item) return -1; if (comm) item->comm = strdup(comm); else item->comm = strdup("<...>"); if (!item->comm) { free(item); return -1; } item->pid = pid; item->next = tep->cmdlist; tep->cmdlist = item; tep->cmdline_count++; return 0; } /** * tep_register_comm - register a pid / comm mapping * @tep: a handle to the trace event parser context * @comm: the command line to register * @pid: the pid to map the command line to * * This adds a mapping to search for command line names with * a given pid. The comm is duplicated. If a command with the same pid * already exist, -1 is returned and errno is set to EEXIST */ int tep_register_comm(struct tep_handle *tep, const char *comm, int pid) { return _tep_register_comm(tep, comm, pid, false); } /** * tep_override_comm - register a pid / comm mapping * @tep: a handle to the trace event parser context * @comm: the command line to register * @pid: the pid to map the command line to * * This adds a mapping to search for command line names with * a given pid. The comm is duplicated. If a command with the same pid * already exist, the command string is udapted with the new one */ int tep_override_comm(struct tep_handle *tep, const char *comm, int pid) { if (!tep->cmdlines && cmdline_init(tep)) { errno = ENOMEM; return -1; } return _tep_register_comm(tep, comm, pid, true); } /** * tep_parse_saved_cmdlines - parse the comms from the saved_cmdlines file * @tep: a handle to the trace event parser * @buf: A string buffer that holds the content of saved_cmdlines and ends with '\0' * * This is a helper function to parse the comms in the tracefs saved_cmdlines * file (stored in a string buffer) and load the comms into the @tep handler * such that comm name matches an process ID (pid). This is used to show * the names of the processes as the events only hold the pid. * * Returns 0 on success, and -1 on error. */ int tep_parse_saved_cmdlines(struct tep_handle *tep, const char *buf) { char *copy; char *comm; char *line; char *next = NULL; int pid; int ret = -1; int n; copy = strdup(buf); if (!copy) return -1; line = strtok_r(copy, "\n", &next); while (line) { errno = 0; n = sscanf(line, "%d %m[^\n]s", &pid, &comm); if (errno || n != 2 || !comm) goto out; tep_register_comm(tep, comm, pid); free(comm); line = strtok_r(NULL, "\n", &next); } ret = 0; out: free(copy); return ret; } struct func_map { unsigned long long addr; char *func; char *mod; }; struct func_list { struct func_list *next; unsigned long long addr; char *func; char *mod; }; static int func_cmp(const void *a, const void *b) { const struct func_map *fa = a; const struct func_map *fb = b; if (fa->addr < fb->addr) return -1; if (fa->addr > fb->addr) return 1; return 0; } /* * We are searching for a record in between, not an exact * match. */ static int func_bcmp(const void *a, const void *b) { const struct func_map *fa = a; const struct func_map *fb = b; if ((fa->addr == fb->addr) || (fa->addr > fb->addr && fa->addr < (fb+1)->addr)) return 0; if (fa->addr < fb->addr) return -1; return 1; } static int func_map_init(struct tep_handle *tep) { struct func_list *funclist; struct func_list *item; struct func_map *func_map; int i; func_map = malloc(sizeof(*func_map) * (tep->func_count + 1)); if (!func_map) return -1; funclist = tep->funclist; i = 0; while (funclist) { func_map[i].func = funclist->func; func_map[i].addr = funclist->addr; func_map[i].mod = funclist->mod; i++; item = funclist; funclist = funclist->next; free(item); } qsort(func_map, tep->func_count, sizeof(*func_map), func_cmp); /* * Add a special record at the end. */ func_map[tep->func_count].func = NULL; func_map[tep->func_count].addr = 0; func_map[tep->func_count].mod = NULL; tep->func_map = func_map; tep->funclist = NULL; return 0; } static struct func_map * __find_func(struct tep_handle *tep, unsigned long long addr) { struct func_map *func; struct func_map key; if (!tep->func_map) func_map_init(tep); key.addr = addr; func = bsearch(&key, tep->func_map, tep->func_count, sizeof(*tep->func_map), func_bcmp); return func; } struct func_resolver { tep_func_resolver_t *func; void *priv; struct func_map map; }; /** * tep_set_function_resolver - set an alternative function resolver * @tep: a handle to the trace event parser context * @resolver: function to be used * @priv: resolver function private state. * * Some tools may have already a way to resolve kernel functions, allow them to * keep using it instead of duplicating all the entries inside tep->funclist. */ int tep_set_function_resolver(struct tep_handle *tep, tep_func_resolver_t *func, void *priv) { struct func_resolver *resolver = malloc(sizeof(*resolver)); if (resolver == NULL) return -1; resolver->func = func; resolver->priv = priv; free(tep->func_resolver); tep->func_resolver = resolver; return 0; } /** * tep_reset_function_resolver - reset alternative function resolver * @tep: a handle to the trace event parser context * * Stop using whatever alternative resolver was set, use the default * one instead. */ void tep_reset_function_resolver(struct tep_handle *tep) { free(tep->func_resolver); tep->func_resolver = NULL; } static struct func_map * find_func(struct tep_handle *tep, unsigned long long addr) { struct func_map *map; if (!tep->func_resolver) return __find_func(tep, addr); map = &tep->func_resolver->map; map->mod = NULL; map->addr = addr; map->func = tep->func_resolver->func(tep->func_resolver->priv, &map->addr, &map->mod); if (map->func == NULL) return NULL; return map; } /** * tep_find_function_info - find a function by a given address * @tep: a handle to the trace event parser context * @addr: the address to find the function with * @name: Return the name of the function (if found) * @start: Return the start of the function (if found) * @size: Return the size of the function (if found) * * Returns 1 if found, and 0 if it is not. * If found then @name will point to the name of the function. * @start: will contain the starting address of the function. * @size: will contain the size of the function. */ int tep_find_function_info(struct tep_handle *tep, unsigned long long addr, const char **name, unsigned long long *start, unsigned long *size) { struct func_map *map; map = find_func(tep, addr); if (!map) return 0; if (name) *name = map->func; if (start) *start = map->addr; if (size) { if (!tep->func_resolver) *size = map[1].addr - map->addr; else *size = 0; } return 1; } /** * tep_find_function - find a function by a given address * @tep: a handle to the trace event parser context * @addr: the address to find the function with * * Returns a pointer to the function stored that has the given * address. Note, the address does not have to be exact, it * will select the function that would contain the address. */ const char *tep_find_function(struct tep_handle *tep, unsigned long long addr) { struct func_map *map; map = find_func(tep, addr); if (!map) return NULL; return map->func; } /** * tep_find_function_address - find a function address by a given address * @tep: a handle to the trace event parser context * @addr: the address to find the function with * * Returns the address the function starts at. This can be used in * conjunction with tep_find_function to print both the function * name and the function offset. */ unsigned long long tep_find_function_address(struct tep_handle *tep, unsigned long long addr) { struct func_map *map; map = find_func(tep, addr); if (!map) return 0; return map->addr; } /** * tep_register_function - register a function with a given address * @tep: a handle to the trace event parser context * @function: the function name to register * @addr: the address the function starts at * @mod: the kernel module the function may be in (NULL for none) * * This registers a function name with an address and module. * The @func passed in is duplicated. */ int tep_register_function(struct tep_handle *tep, char *func, unsigned long long addr, char *mod) { struct func_list *item = malloc(sizeof(*item)); if (!item) return -1; item->next = tep->funclist; item->func = strdup(func); if (!item->func) goto out_free; if (mod) { item->mod = strdup(mod); if (!item->mod) goto out_free_func; } else item->mod = NULL; item->addr = addr; tep->funclist = item; tep->func_count++; return 0; out_free_func: free(item->func); item->func = NULL; out_free: free(item); errno = ENOMEM; return -1; } /** * tep_parse_kallsyms - load functions from a read of /proc/kallsyms * @tep: a handle to the trace event parser * @kallsyms: A string buffer that holds the content of /proc/kallsyms and ends with '\0' * * This is a helper function to parse the Linux kernel /proc/kallsyms * format (stored in a string buffer) and load the functions into * the @tep handler such that function IP addresses can be mapped to * their name when parsing events with %pS in the print format field. * * Returns 0 on success, and -1 on error. */ int tep_parse_kallsyms(struct tep_handle *tep, const char *kallsyms) { unsigned long long addr; char *copy; char *func; char *line; char *next = NULL; char *mod; char ch; int ret = -1; copy = strdup(kallsyms); if (!copy) return -1; line = strtok_r(copy, "\n", &next); while (line) { int func_start, func_end = 0; int mod_start, mod_end = 0; int n; mod = NULL; errno = 0; n = sscanf(line, "%16llx %c %n%*s%n%*1[\t][%n%*s%n", &addr, &ch, &func_start, &func_end, &mod_start, &mod_end); if (errno) goto out; if (n != 2 || !func_end) { tep_warning("Failed to parse kallsyms n=%d func_end=%d", n, func_end); goto out; } func = line + func_start; /* * Hacks for * - arm arch that adds a lot of bogus '$a' functions * - x86-64 that reports per-cpu variable offsets as absolute */ if (func[0] != '$' && ch != 'A' && ch != 'a') { line[func_end] = 0; if (mod_end) { mod = line + mod_start; /* truncate the extra ']' */ line[mod_end - 1] = 0; } tep_register_function(tep, func, addr, mod); } line = strtok_r(NULL, "\n", &next); } free(line); ret = 0; out: free(copy); return ret; } /** * tep_print_funcs - print out the stored functions * @tep: a handle to the trace event parser context * * This prints out the stored functions. */ void tep_print_funcs(struct tep_handle *tep) { int i; if (!tep->func_map) func_map_init(tep); for (i = 0; i < (int)tep->func_count; i++) { printf("%016llx %s", tep->func_map[i].addr, tep->func_map[i].func); if (tep->func_map[i].mod) printf(" [%s]\n", tep->func_map[i].mod); else printf("\n"); } } struct printk_map { unsigned long long addr; char *printk; }; struct printk_list { struct printk_list *next; unsigned long long addr; char *printk; }; static int printk_cmp(const void *a, const void *b) { const struct printk_map *pa = a; const struct printk_map *pb = b; if (pa->addr < pb->addr) return -1; if (pa->addr > pb->addr) return 1; return 0; } static int printk_map_init(struct tep_handle *tep) { struct printk_list *printklist; struct printk_list *item; struct printk_map *printk_map; int i; printk_map = malloc(sizeof(*printk_map) * (tep->printk_count + 1)); if (!printk_map) return -1; printklist = tep->printklist; i = 0; while (printklist) { printk_map[i].printk = printklist->printk; printk_map[i].addr = printklist->addr; i++; item = printklist; printklist = printklist->next; free(item); } qsort(printk_map, tep->printk_count, sizeof(*printk_map), printk_cmp); tep->printk_map = printk_map; tep->printklist = NULL; return 0; } static struct printk_map * find_printk(struct tep_handle *tep, unsigned long long addr) { struct printk_map *printk; struct printk_map key; if (!tep->printk_map && printk_map_init(tep)) return NULL; key.addr = addr; printk = bsearch(&key, tep->printk_map, tep->printk_count, sizeof(*tep->printk_map), printk_cmp); return printk; } /** * tep_register_print_string - register a string by its address * @tep: a handle to the trace event parser context * @fmt: the string format to register * @addr: the address the string was located at * * This registers a string by the address it was stored in the kernel. * The @fmt passed in is duplicated. */ int tep_register_print_string(struct tep_handle *tep, const char *fmt, unsigned long long addr) { struct printk_list *item = malloc(sizeof(*item)); char *p; if (!item) return -1; item->next = tep->printklist; item->addr = addr; /* Strip off quotes and '\n' from the end */ if (fmt[0] == '"') fmt++; item->printk = strdup(fmt); if (!item->printk) goto out_free; p = item->printk + strlen(item->printk) - 1; if (*p == '"') *p = 0; p -= 2; if (strcmp(p, "\\n") == 0) *p = 0; tep->printklist = item; tep->printk_count++; return 0; out_free: free(item); errno = ENOMEM; return -1; } /** * tep_print_printk - print out the stored strings * @tep: a handle to the trace event parser context * * This prints the string formats that were stored. */ void tep_print_printk(struct tep_handle *tep) { int i; if (!tep->printk_map) printk_map_init(tep); for (i = 0; i < (int)tep->printk_count; i++) { printf("%016llx %s\n", tep->printk_map[i].addr, tep->printk_map[i].printk); } } /** * tep_parse_printk_formats - Parse the address to strings * @tep: a handle to the trace event parser * @buf: A string buffer that holds the content of printk_formats and ends with '\0' * * This is a helper function to parse the address to printk formats in * the kernel. Some events use %s to a kernel address that holds a constant * string. The printk_formats file has a mapping of these addresses to the * strings that are in the kernel. This parses the content of that file * and registers those strings and their addresses so that the parsing of * events can display the string as the event only has the address of the string. * * Returns 0 on success, and -1 on error. */ int tep_parse_printk_formats(struct tep_handle *tep, const char *buf) { unsigned long long addr; char *addr_str; char *printk; char *copy; char *line; char *next; char *fmt; int ret = -1; copy = strdup(buf); if (!copy) return -1; line = strtok_r(copy, "\n", &next); while (line) { addr_str = strtok_r(line, ":", &fmt); if (!addr_str) { tep_warning("printk format with empty entry"); break; } addr = strtoull(addr_str, NULL, 16); /* fmt still has a space, skip it */ printk = strdup(fmt+1); if (!printk) goto out; line = strtok_r(NULL, "\n", &next); tep_register_print_string(tep, printk, addr); free(printk); } ret = 0; out: free(copy); return ret; } static struct tep_event *alloc_event(void) { return calloc(1, sizeof(struct tep_event)); } static int add_event(struct tep_handle *tep, struct tep_event *event) { int i; struct tep_event **events = realloc(tep->events, sizeof(event) * (tep->nr_events + 1)); if (!events) return -1; tep->events = events; for (i = 0; i < tep->nr_events; i++) { if (tep->events[i]->id > event->id) break; } if (i < tep->nr_events) memmove(&tep->events[i + 1], &tep->events[i], sizeof(event) * (tep->nr_events - i)); tep->events[i] = event; tep->nr_events++; event->tep = tep; return 0; } static int event_item_type(enum tep_event_type type) { switch (type) { case TEP_EVENT_ITEM ... TEP_EVENT_SQUOTE: return 1; case TEP_EVENT_ERROR ... TEP_EVENT_DELIM: default: return 0; } } static void free_flag_sym(struct tep_print_flag_sym *fsym) { struct tep_print_flag_sym *next; while (fsym) { next = fsym->next; free(fsym->value); free(fsym->str); free(fsym); fsym = next; } } static void free_arg(struct tep_print_arg *arg) { struct tep_print_arg *farg; if (!arg) return; switch (arg->type) { case TEP_PRINT_ATOM: free(arg->atom.atom); break; case TEP_PRINT_FIELD: free(arg->field.name); break; case TEP_PRINT_FLAGS: free_arg(arg->flags.field); free(arg->flags.delim); free_flag_sym(arg->flags.flags); break; case TEP_PRINT_SYMBOL: free_arg(arg->symbol.field); free_flag_sym(arg->symbol.symbols); break; case TEP_PRINT_HEX: case TEP_PRINT_HEX_STR: free_arg(arg->hex.field); free_arg(arg->hex.size); break; case TEP_PRINT_INT_ARRAY: free_arg(arg->int_array.field); free_arg(arg->int_array.count); free_arg(arg->int_array.el_size); break; case TEP_PRINT_TYPE: free(arg->typecast.type); free_arg(arg->typecast.item); break; case TEP_PRINT_STRING: case TEP_PRINT_BSTRING: free(arg->string.string); break; case TEP_PRINT_BITMASK: case TEP_PRINT_CPUMASK: free(arg->bitmask.bitmask); break; case TEP_PRINT_DYNAMIC_ARRAY: case TEP_PRINT_DYNAMIC_ARRAY_LEN: free(arg->dynarray.index); break; case TEP_PRINT_OP: free(arg->op.op); free_arg(arg->op.left); free_arg(arg->op.right); break; case TEP_PRINT_FUNC: while (arg->func.args) { farg = arg->func.args; arg->func.args = farg->next; free_arg(farg); } break; case TEP_PRINT_NULL: default: break; } free(arg); } static enum tep_event_type get_type(int ch) { if (ch == '\n') return TEP_EVENT_NEWLINE; if (isspace(ch)) return TEP_EVENT_SPACE; if (isalnum(ch) || ch == '_') return TEP_EVENT_ITEM; if (ch == '\'') return TEP_EVENT_SQUOTE; if (ch == '"') return TEP_EVENT_DQUOTE; if (!isprint(ch)) return TEP_EVENT_NONE; if (ch == '(' || ch == ')' || ch == ',') return TEP_EVENT_DELIM; return TEP_EVENT_OP; } static int __read_char(struct tep_handle *tep) { if (tep->input_buf_ptr >= tep->input_buf_siz) return -1; return tep->input_buf[tep->input_buf_ptr++]; } /** * peek_char - peek at the next character that will be read * * Returns the next character read, or -1 if end of buffer. */ __hidden int peek_char(struct tep_handle *tep) { if (tep->input_buf_ptr >= tep->input_buf_siz) return -1; return tep->input_buf[tep->input_buf_ptr]; } static int extend_token(char **tok, char *buf, int size) { char *newtok = realloc(*tok, size); if (!newtok) { free(*tok); *tok = NULL; return -1; } if (!*tok) strcpy(newtok, buf); else strcat(newtok, buf); *tok = newtok; return 0; } static enum tep_event_type force_token(struct tep_handle *tep, const char *str, char **tok); static enum tep_event_type __read_token(struct tep_handle *tep, char **tok) { char buf[BUFSIZ]; int ch, last_ch, quote_ch, next_ch; int i = 0; int tok_size = 0; enum tep_event_type type; *tok = NULL; ch = __read_char(tep); if (ch < 0) return TEP_EVENT_NONE; type = get_type(ch); if (type == TEP_EVENT_NONE) return type; buf[i++] = ch; switch (type) { case TEP_EVENT_NEWLINE: case TEP_EVENT_DELIM: if (asprintf(tok, "%c", ch) < 0) return TEP_EVENT_ERROR; return type; case TEP_EVENT_OP: switch (ch) { case '-': next_ch = peek_char(tep); if (next_ch == '>') { buf[i++] = __read_char(tep); break; } /* fall through */ case '+': case '|': case '&': case '>': case '<': last_ch = ch; ch = peek_char(tep); if (ch != last_ch) goto test_equal; buf[i++] = __read_char(tep); switch (last_ch) { case '>': case '<': goto test_equal; default: break; } break; case '!': case '=': goto test_equal; default: /* what should we do instead? */ break; } buf[i] = 0; *tok = strdup(buf); return type; test_equal: ch = peek_char(tep); if (ch == '=') buf[i++] = __read_char(tep); goto out; case TEP_EVENT_DQUOTE: case TEP_EVENT_SQUOTE: /* don't keep quotes */ i--; quote_ch = ch; last_ch = 0; concat: do { if (i == (BUFSIZ - 1)) { buf[i] = 0; tok_size += BUFSIZ; if (extend_token(tok, buf, tok_size) < 0) return TEP_EVENT_NONE; i = 0; } last_ch = ch; ch = __read_char(tep); buf[i++] = ch; /* the '\' '\' will cancel itself */ if (ch == '\\' && last_ch == '\\') last_ch = 0; /* Break out if the file is corrupted and giving non print chars */ if (ch <= 0) break; } while ((ch != quote_ch && isprint(ch)) || last_ch == '\\' || ch == '\n'); /* remove the last quote */ i--; if (ch <= 0) type = TEP_EVENT_NONE; /* * For strings (double quotes) check the next token. * If it is another string, concatinate the two. */ if (type == TEP_EVENT_DQUOTE) { unsigned long long save_input_buf_ptr = tep->input_buf_ptr; do { ch = __read_char(tep); } while (isspace(ch)); if (ch == '"') goto concat; tep->input_buf_ptr = save_input_buf_ptr; } goto out; case TEP_EVENT_ERROR ... TEP_EVENT_SPACE: case TEP_EVENT_ITEM: default: break; } while (get_type(peek_char(tep)) == type) { if (i == (BUFSIZ - 1)) { buf[i] = 0; tok_size += BUFSIZ; if (extend_token(tok, buf, tok_size) < 0) return TEP_EVENT_NONE; i = 0; } ch = __read_char(tep); buf[i++] = ch; } out: buf[i] = 0; if (extend_token(tok, buf, tok_size + i + 1) < 0) return TEP_EVENT_NONE; if (type == TEP_EVENT_ITEM) { /* * Older versions of the kernel has a bug that * creates invalid symbols and will break the mac80211 * parsing. This is a work around to that bug. * * See Linux kernel commit: * 811cb50baf63461ce0bdb234927046131fc7fa8b */ if (strcmp(*tok, "LOCAL_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token(tep, "\"%s\" ", tok); } else if (strcmp(*tok, "STA_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token(tep, "\" sta:%pM\" ", tok); } else if (strcmp(*tok, "VIF_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token(tep, "\" vif:%p(%d)\" ", tok); } } return type; } static enum tep_event_type force_token(struct tep_handle *tep, const char *str, char **tok) { const char *save_input_buf; unsigned long long save_input_buf_ptr; unsigned long long save_input_buf_siz; enum tep_event_type type; /* save off the current input pointers */ save_input_buf = tep->input_buf; save_input_buf_ptr = tep->input_buf_ptr; save_input_buf_siz = tep->input_buf_siz; init_input_buf(tep, str, strlen(str)); type = __read_token(tep, tok); /* reset back to original token */ tep->input_buf = save_input_buf; tep->input_buf_ptr = save_input_buf_ptr; tep->input_buf_siz = save_input_buf_siz; return type; } /** * free_token - free a token returned by tep_read_token * @token: the token to free */ __hidden void free_token(char *tok) { if (tok) free(tok); } /** * read_token - access to utilities to use the tep parser * @tok: The token to return * * This will parse tokens from the string given by * tep_init_data(). * * Returns the token type. */ __hidden enum tep_event_type read_token(struct tep_handle *tep, char **tok) { enum tep_event_type type; for (;;) { type = __read_token(tep, tok); if (type != TEP_EVENT_SPACE) return type; free_token(*tok); } /* not reached */ *tok = NULL; return TEP_EVENT_NONE; } /* no newline */ static enum tep_event_type read_token_item(struct tep_handle *tep, char **tok) { enum tep_event_type type; for (;;) { type = __read_token(tep, tok); if (type != TEP_EVENT_SPACE && type != TEP_EVENT_NEWLINE) return type; free_token(*tok); *tok = NULL; } /* not reached */ *tok = NULL; return TEP_EVENT_NONE; } static int test_type(enum tep_event_type type, enum tep_event_type expect) { if (type != expect) { do_warning("Error: expected type %d (%s) but read %d (%s)", expect, get_event_type(expect), type, get_event_type(type)); return -1; } return 0; } static int test_type_token(enum tep_event_type type, const char *token, enum tep_event_type expect, const char *expect_tok) { if (type != expect) { do_warning("Error: expected type %d (%s) but read %d (%s)", expect, get_event_type(expect), type, get_event_type(type)); return -1; } if (strcmp(token, expect_tok) != 0) { do_warning("Error: expected '%s' but read '%s'", expect_tok, token); return -1; } return 0; } static int __read_expect_type(struct tep_handle *tep, enum tep_event_type expect, char **tok, int newline_ok) { enum tep_event_type type; if (newline_ok) type = read_token(tep, tok); else type = read_token_item(tep, tok); return test_type(type, expect); } static int read_expect_type(struct tep_handle *tep, enum tep_event_type expect, char **tok) { return __read_expect_type(tep, expect, tok, 1); } static int __read_expected(struct tep_handle *tep, enum tep_event_type expect, const char *str, int newline_ok) { enum tep_event_type type; char *token; int ret; if (newline_ok) type = read_token(tep, &token); else type = read_token_item(tep, &token); ret = test_type_token(type, token, expect, str); free_token(token); return ret; } static int read_expected(struct tep_handle *tep, enum tep_event_type expect, const char *str) { return __read_expected(tep, expect, str, 1); } static int read_expected_item(struct tep_handle *tep, enum tep_event_type expect, const char *str) { return __read_expected(tep, expect, str, 0); } static char *event_read_name(struct tep_handle *tep) { char *token; if (read_expected(tep, TEP_EVENT_ITEM, "name") < 0) return NULL; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return NULL; if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; return token; fail: free_token(token); return NULL; } static int event_read_id(struct tep_handle *tep) { char *token; int id; if (read_expected_item(tep, TEP_EVENT_ITEM, "ID") < 0) return -1; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return -1; if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; id = strtoul(token, NULL, 0); free_token(token); return id; fail: free_token(token); return -1; } static int field_is_string(struct tep_format_field *field) { if ((field->flags & TEP_FIELD_IS_ARRAY) && (strstr(field->type, "char") || strstr(field->type, "u8") || strstr(field->type, "s8"))) return 1; return 0; } static int field_is_dynamic(struct tep_format_field *field) { if (strncmp(field->type, "__data_loc", 10) == 0) return 1; return 0; } static int field_is_relative_dynamic(struct tep_format_field *field) { if (strncmp(field->type, "__rel_loc", 9) == 0) return 1; return 0; } static int field_is_long(struct tep_format_field *field) { /* includes long long */ if (strstr(field->type, "long")) return 1; return 0; } static unsigned int type_size(const char *name) { /* This covers all TEP_FIELD_IS_STRING types. */ static struct { const char *type; unsigned int size; } table[] = { { "u8", 1 }, { "u16", 2 }, { "u32", 4 }, { "u64", 8 }, { "s8", 1 }, { "s16", 2 }, { "s32", 4 }, { "s64", 8 }, { "char", 1 }, { }, }; int i; for (i = 0; table[i].type; i++) { if (!strcmp(table[i].type, name)) return table[i].size; } return 0; } static int append(char **buf, const char *delim, const char *str) { char *new_buf; new_buf = realloc(*buf, strlen(*buf) + strlen(delim) + strlen(str) + 1); if (!new_buf) return -1; strcat(new_buf, delim); strcat(new_buf, str); *buf = new_buf; return 0; } static int event_read_fields(struct tep_handle *tep, struct tep_event *event, struct tep_format_field **fields) { struct tep_format_field *field = NULL; enum tep_event_type type; char *token; char *last_token; char *delim = " "; int count = 0; int ret; do { unsigned int size_dynamic = 0; type = read_token(tep, &token); if (type == TEP_EVENT_NEWLINE) { free_token(token); return count; } count++; if (test_type_token(type, token, TEP_EVENT_ITEM, "field")) goto fail; free_token(token); type = read_token(tep, &token); /* * The ftrace fields may still use the "special" name. * Just ignore it. */ if (event->flags & TEP_EVENT_FL_ISFTRACE && type == TEP_EVENT_ITEM && strcmp(token, "special") == 0) { free_token(token); type = read_token(tep, &token); } if (test_type_token(type, token, TEP_EVENT_OP, ":") < 0) goto fail; free_token(token); if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; last_token = token; field = calloc(1, sizeof(*field)); if (!field) goto fail; field->event = event; /* read the rest of the type */ for (;;) { type = read_token(tep, &token); if (type == TEP_EVENT_ITEM || (type == TEP_EVENT_OP && strcmp(token, "*") == 0) || /* * Some of the ftrace fields are broken and have * an illegal "." in them. */ (event->flags & TEP_EVENT_FL_ISFTRACE && type == TEP_EVENT_OP && strcmp(token, ".") == 0)) { if (strcmp(token, "*") == 0) field->flags |= TEP_FIELD_IS_POINTER; if (field->type) { ret = append(&field->type, delim, last_token); free(last_token); if (ret < 0) goto fail; } else field->type = last_token; last_token = token; delim = " "; continue; } /* Handle __attribute__((user)) */ if ((type == TEP_EVENT_DELIM) && strcmp("__attribute__", last_token) == 0 && token[0] == '(') { int depth = 1; int ret; ret = append(&field->type, " ", last_token); ret |= append(&field->type, "", "("); if (ret < 0) goto fail; delim = " "; while ((type = read_token(tep, &token)) != TEP_EVENT_NONE) { if (type == TEP_EVENT_DELIM) { if (token[0] == '(') depth++; else if (token[0] == ')') depth--; if (!depth) break; ret = append(&field->type, "", token); delim = ""; } else { ret = append(&field->type, delim, token); delim = " "; } if (ret < 0) goto fail; free(last_token); last_token = token; } continue; } break; } if (!field->type) { do_warning_event(event, "%s: no type found", __func__); goto fail; } field->name = field->alias = last_token; if (test_type(type, TEP_EVENT_OP)) goto fail; if (strcmp(token, "[") == 0) { enum tep_event_type last_type = type; char *brackets = token; field->flags |= TEP_FIELD_IS_ARRAY; type = read_token(tep, &token); if (type == TEP_EVENT_ITEM) field->arraylen = strtoul(token, NULL, 0); else field->arraylen = 0; while (strcmp(token, "]") != 0) { const char *delim; if (last_type == TEP_EVENT_ITEM && type == TEP_EVENT_ITEM) delim = " "; else delim = ""; last_type = type; ret = append(&brackets, delim, token); if (ret < 0) { free(brackets); goto fail; } /* We only care about the last token */ field->arraylen = strtoul(token, NULL, 0); free_token(token); type = read_token(tep, &token); if (type == TEP_EVENT_NONE) { free(brackets); do_warning_event(event, "failed to find token"); goto fail; } } free_token(token); ret = append(&brackets, "", "]"); if (ret < 0) { free(brackets); goto fail_expect; } /* add brackets to type */ type = read_token(tep, &token); /* * If the next token is not an OP, then it is of * the format: type [] item; */ if (type == TEP_EVENT_ITEM) { ret = append(&field->type, " ", field->name); if (ret < 0) { free(brackets); goto fail; } ret = append(&field->type, "", brackets); size_dynamic = type_size(field->name); free_token(field->name); field->name = field->alias = token; type = read_token(tep, &token); } else { ret = append(&field->type, "", brackets); if (ret < 0) { free(brackets); goto fail; } } free(brackets); } if (field_is_string(field)) field->flags |= TEP_FIELD_IS_STRING; if (field_is_dynamic(field)) field->flags |= TEP_FIELD_IS_DYNAMIC; if (field_is_relative_dynamic(field)) field->flags |= TEP_FIELD_IS_DYNAMIC | TEP_FIELD_IS_RELATIVE; if (field_is_long(field)) field->flags |= TEP_FIELD_IS_LONG; if (test_type_token(type, token, TEP_EVENT_OP, ";")) goto fail; free_token(token); if (read_expected(tep, TEP_EVENT_ITEM, "offset") < 0) goto fail_expect; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(tep, TEP_EVENT_ITEM, &token)) goto fail; field->offset = strtoul(token, NULL, 0); free_token(token); if (read_expected(tep, TEP_EVENT_OP, ";") < 0) goto fail_expect; if (read_expected(tep, TEP_EVENT_ITEM, "size") < 0) goto fail_expect; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(tep, TEP_EVENT_ITEM, &token)) goto fail; field->size = strtoul(token, NULL, 0); free_token(token); /* * The old data format before dynamic arrays had dynamic * strings defined with just a 2 byte offset (the length * is defined by the strlen() of the string. To process them * correctly, check if the field is dynamic and has a size of * 2 bytes. All current dynamic events have a size of 4. */ if ((field->flags & TEP_FIELD_IS_DYNAMIC) && field->size == 2) field->flags |= TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY; if (read_expected(tep, TEP_EVENT_OP, ";") < 0) goto fail_expect; type = read_token(tep, &token); if (type != TEP_EVENT_NEWLINE) { /* newer versions of the kernel have a "signed" type */ if (test_type_token(type, token, TEP_EVENT_ITEM, "signed")) goto fail; free_token(token); if (read_expected(tep, TEP_EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(tep, TEP_EVENT_ITEM, &token)) goto fail; if (strtoul(token, NULL, 0)) field->flags |= TEP_FIELD_IS_SIGNED; free_token(token); if (read_expected(tep, TEP_EVENT_OP, ";") < 0) goto fail_expect; if (read_expect_type(tep, TEP_EVENT_NEWLINE, &token)) goto fail; } free_token(token); if (field->flags & (TEP_FIELD_IS_ARRAY | TEP_FIELD_IS_DYNAMIC)) { if (field->arraylen) field->elementsize = field->size / field->arraylen; else if (field->flags & TEP_FIELD_IS_DYNAMIC) field->elementsize = size_dynamic; else if (field->flags & TEP_FIELD_IS_STRING) field->elementsize = 1; else if (field->flags & TEP_FIELD_IS_LONG) field->elementsize = event->tep ? event->tep->long_size : sizeof(long); } else field->elementsize = field->size; *fields = field; fields = &field->next; field = NULL; } while (1); return 0; fail: free_token(token); fail_expect: if (field) { free(field->type); free(field->name); free(field); } return -1; } static int event_read_format(struct tep_event *event) { char *token; int ret; if (read_expected_item(event->tep, TEP_EVENT_ITEM, "format") < 0) return -1; if (read_expected(event->tep, TEP_EVENT_OP, ":") < 0) return -1; if (read_expect_type(event->tep, TEP_EVENT_NEWLINE, &token)) goto fail; free_token(token); ret = event_read_fields(event->tep, event, &event->format.common_fields); if (ret < 0) return ret; event->format.nr_common = ret; ret = event_read_fields(event->tep, event, &event->format.fields); if (ret < 0) return ret; event->format.nr_fields = ret; return 0; fail: free_token(token); return -1; } static enum tep_event_type process_arg_token(struct tep_event *event, struct tep_print_arg *arg, char **tok, enum tep_event_type type); static enum tep_event_type process_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; char *token; type = read_token(event->tep, &token); *tok = token; return process_arg_token(event, arg, tok, type); } static enum tep_event_type process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok); /* * For __print_symbolic() and __print_flags, we need to completely * evaluate the first argument, which defines what to print next. */ static enum tep_event_type process_field_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; type = process_arg(event, arg, tok); while (type == TEP_EVENT_OP) { type = process_op(event, arg, tok); } return type; } static enum tep_event_type process_cond(struct tep_event *event, struct tep_print_arg *top, char **tok) { struct tep_print_arg *arg, *left, *right; enum tep_event_type type; char *token = NULL; arg = alloc_arg(); left = alloc_arg(); right = alloc_arg(); if (!arg || !left || !right) { do_warning_event(event, "%s: not enough memory!", __func__); /* arg will be freed at out_free */ free_arg(left); free_arg(right); goto out_free; } arg->type = TEP_PRINT_OP; arg->op.left = left; arg->op.right = right; *tok = NULL; type = process_arg(event, left, &token); again: if (type == TEP_EVENT_ERROR) goto out_free; /* Handle other operations in the arguments */ if (type == TEP_EVENT_OP && strcmp(token, ":") != 0) { type = process_op(event, left, &token); goto again; } if (test_type_token(type, token, TEP_EVENT_OP, ":")) goto out_free; arg->op.op = token; type = process_arg(event, right, &token); top->op.right = arg; *tok = token; return type; out_free: /* Top may point to itself */ top->op.right = NULL; free_token(token); free_arg(arg); return TEP_EVENT_ERROR; } static enum tep_event_type process_array(struct tep_event *event, struct tep_print_arg *top, char **tok) { struct tep_print_arg *arg; enum tep_event_type type; char *token = NULL; arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); /* '*tok' is set to top->op.op. No need to free. */ *tok = NULL; return TEP_EVENT_ERROR; } *tok = NULL; type = process_arg(event, arg, &token); if (test_type_token(type, token, TEP_EVENT_OP, "]")) goto out_free; top->op.right = arg; free_token(token); type = read_token_item(event->tep, &token); *tok = token; return type; out_free: free_token(token); free_arg(arg); return TEP_EVENT_ERROR; } static int get_op_prio(char *op) { if (strlen(op) == 1) { switch (op[0]) { case '~': case '!': return 4; case '*': case '/': case '%': return 6; case '+': case '-': return 7; /* '>>' and '<<' are 8 */ case '<': case '>': return 9; /* '==' and '!=' are 10 */ case '&': return 11; case '^': return 12; case '|': return 13; case '?': return 16; default: do_warning("unknown op '%c'", op[0]); return -1; } } else { if (strcmp(op, "++") == 0 || strcmp(op, "--") == 0) { return 3; } else if (strcmp(op, ">>") == 0 || strcmp(op, "<<") == 0) { return 8; } else if (strcmp(op, ">=") == 0 || strcmp(op, "<=") == 0) { return 9; } else if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0) { return 10; } else if (strcmp(op, "&&") == 0) { return 14; } else if (strcmp(op, "||") == 0) { return 15; } else { do_warning("unknown op '%s'", op); return -1; } } } static int set_op_prio(struct tep_print_arg *arg) { /* single ops are the greatest */ if (!arg->op.left || arg->op.left->type == TEP_PRINT_NULL) arg->op.prio = 0; else arg->op.prio = get_op_prio(arg->op.op); return arg->op.prio; } static int consolidate_op_arg(struct tep_print_arg *arg) { unsigned long long val, left, right; int ret = 0; if (arg->type != TEP_PRINT_OP) return 0; if (arg->op.left) ret = consolidate_op_arg(arg->op.left); if (ret < 0) return ret; if (arg->op.right) ret = consolidate_op_arg(arg->op.right); if (ret < 0) return ret; if (!arg->op.left || !arg->op.right) return 0; if (arg->op.left->type != TEP_PRINT_ATOM || arg->op.right->type != TEP_PRINT_ATOM) return 0; /* Two atoms, we can do the operation now. */ left = strtoull(arg->op.left->atom.atom, NULL, 0); right = strtoull(arg->op.right->atom.atom, NULL, 0); switch (arg->op.op[0]) { case '>': switch (arg->op.op[1]) { case '>': val = left >> right; break; case '=': val = left >= right; break; default: val = left > right; break; } break; case '<': switch (arg->op.op[1]) { case '<': val = left << right; break; case '=': val = left <= right; break; default: val = left < right; break; } break; case '&': switch (arg->op.op[1]) { case '&': val = left && right; break; default: val = left & right; break; } break; case '|': switch (arg->op.op[1]) { case '|': val = left || right; break; default: val = left | right; break; } break; case '-': val = left - right; break; case '+': val = left + right; break; case '*': val = left * right; break; case '^': val = left ^ right; break; case '/': val = left / right; break; case '%': val = left % right; break; case '=': /* Only '==' is called here */ val = left == right; break; case '!': /* Only '!=' is called here. */ val = left != right; break; default: return 0; } free_arg(arg->op.left); free_arg(arg->op.right); arg->type = TEP_PRINT_ATOM; free(arg->op.op); return asprintf(&arg->atom.atom, "%lld", val) < 0 ? -1 : 0; } /* Note, *tok does not get freed, but will most likely be saved */ static enum tep_event_type process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_print_arg *left, *right = NULL; enum tep_event_type type; char *token; /* the op is passed in via tok */ token = *tok; if (arg->type == TEP_PRINT_OP && !arg->op.left) { /* handle single op */ switch (token[0]) { case '~': case '!': case '+': case '-': break; default: do_warning_event(event, "bad op token %s", token); goto out_free; } if (token[1]) { do_warning_event(event, "bad op token %s", token); goto out_free; } /* make an empty left */ left = alloc_arg(); if (!left) goto out_warn_free; left->type = TEP_PRINT_NULL; arg->op.left = left; right = alloc_arg(); if (!right) goto out_warn_free; arg->op.right = right; /* do not free the token, it belongs to an op */ *tok = NULL; type = process_arg(event, right, tok); } else if (strcmp(token, "?") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; /* copy the top arg to the left */ *left = *arg; arg->type = TEP_PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.right = NULL; arg->op.prio = 0; /* it will set arg->op.right */ type = process_cond(event, arg, tok); } else if (strcmp(token, ">>") == 0 || strcmp(token, "<<") == 0 || strcmp(token, "&") == 0 || strcmp(token, "|") == 0 || strcmp(token, "&&") == 0 || strcmp(token, "||") == 0 || strcmp(token, "-") == 0 || strcmp(token, "+") == 0 || strcmp(token, "*") == 0 || strcmp(token, "^") == 0 || strcmp(token, "/") == 0 || strcmp(token, "%") == 0 || strcmp(token, "<") == 0 || strcmp(token, ">") == 0 || strcmp(token, "<=") == 0 || strcmp(token, ">=") == 0 || strcmp(token, "==") == 0 || strcmp(token, "!=") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; /* copy the top arg to the left */ *left = *arg; arg->type = TEP_PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.right = NULL; if (set_op_prio(arg) == -1) { event->flags |= TEP_EVENT_FL_FAILED; /* arg->op.op (= token) will be freed at out_free */ arg->op.op = NULL; goto out_free; } type = read_token_item(event->tep, &token); *tok = token; /* could just be a type pointer */ if ((strcmp(arg->op.op, "*") == 0) && type == TEP_EVENT_DELIM && (strcmp(token, ")") == 0)) { int ret; if (left->type != TEP_PRINT_ATOM) { do_warning_event(event, "bad pointer type"); goto out_free; } ret = append(&left->atom.atom, " ", "*"); if (ret < 0) goto out_warn_free; free(arg->op.op); *arg = *left; free(left); return type; } right = alloc_arg(); if (!right) goto out_warn_free; type = process_arg_token(event, right, tok, type); if (type == TEP_EVENT_ERROR) { free_arg(right); /* token was freed in process_arg_token() via *tok */ token = NULL; goto out_free; } if (right->type == TEP_PRINT_OP && get_op_prio(arg->op.op) < get_op_prio(right->op.op)) { struct tep_print_arg tmp; /* rotate ops according to the priority */ arg->op.right = right->op.left; tmp = *arg; *arg = *right; *right = tmp; arg->op.left = right; } else { arg->op.right = right; } } else if (strcmp(token, "[") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; *left = *arg; arg->type = TEP_PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.right = NULL; arg->op.prio = 0; /* it will set arg->op.right */ type = process_array(event, arg, tok); } else { do_warning_event(event, "unknown op '%s'", token); event->flags |= TEP_EVENT_FL_FAILED; /* the arg is now the left side */ goto out_free; } if (type == TEP_EVENT_OP && strcmp(*tok, ":") != 0) { int prio; /* higher prios need to be closer to the root */ prio = get_op_prio(*tok); if (prio > arg->op.prio) return process_op(event, arg, tok); return process_op(event, right, tok); } return type; out_warn_free: do_warning_event(event, "%s: not enough memory!", __func__); out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_entry(struct tep_event *event __maybe_unused, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; char *field; char *token; type = read_token_item(event->tep, &token); /* * Check if REC happens to be surrounded by parenthesis, and * return if that's the case, as "(REC)->" is valid. * but return TEP_EVENT_ITEM. */ if (type == TEP_EVENT_DELIM && strcmp(token, ")") == 0) { *tok = token; return TEP_EVENT_ITEM; } if (test_type_token(type, token, TEP_EVENT_OP, "->")) goto out_free; free_token(token); if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto out_free; field = token; arg->type = TEP_PRINT_FIELD; arg->field.name = field; arg->field.field = tep_find_any_field(event, arg->field.name); if (is_flag_field) { arg->field.field->flags |= TEP_FIELD_IS_FLAG; is_flag_field = 0; } else if (is_symbolic_field) { arg->field.field->flags |= TEP_FIELD_IS_SYMBOLIC; is_symbolic_field = 0; } type = read_token(event->tep, &token); *tok = token; return type; out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static int alloc_and_process_delim(struct tep_event *event, char *next_token, struct tep_print_arg **print_arg) { struct tep_print_arg *field; enum tep_event_type type; char *token; int ret = 0; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); errno = ENOMEM; return -1; } type = process_arg(event, field, &token); /* We do allow operators */ if (type == TEP_EVENT_OP) { type = process_op(event, field, &token); if (consolidate_op_arg(field) < 0) type = TEP_EVENT_ERROR; if (type == TEP_EVENT_ERROR) goto out_error; } if (test_type_token(type, token, TEP_EVENT_DELIM, next_token)) goto out_error; *print_arg = field; out_free_token: free_token(token); return ret; out_error: errno = EINVAL; ret = -1; free_arg(field); goto out_free_token; } static char *arg_eval (struct tep_print_arg *arg); static unsigned long long eval_type_str(unsigned long long val, const char *type, int pointer) { int sign = 0; char *ref; int len; len = strlen(type); if (len < 2) { do_warning("invalid type: %s", type); return val; } if (pointer) { if (type[len-1] != '*') { do_warning("pointer expected with non pointer type"); return val; } ref = malloc(len); if (!ref) { do_warning("%s: not enough memory!", __func__); return val; } memcpy(ref, type, len); /* chop off the " *" */ ref[len - 2] = 0; val = eval_type_str(val, ref, 0); free(ref); return val; } /* check if this is a pointer */ if (type[len - 1] == '*') return val; /* Try to figure out the arg size*/ if (strncmp(type, "struct", 6) == 0) /* all bets off */ return val; if (strcmp(type, "u8") == 0) return val & 0xff; if (strcmp(type, "u16") == 0) return val & 0xffff; if (strcmp(type, "u32") == 0) return val & 0xffffffff; if (strcmp(type, "u64") == 0 || strcmp(type, "s64") == 0) return val; if (strcmp(type, "s8") == 0) return (unsigned long long)(char)val & 0xff; if (strcmp(type, "s16") == 0) return (unsigned long long)(short)val & 0xffff; if (strcmp(type, "s32") == 0) return (unsigned long long)(int)val & 0xffffffff; if (strncmp(type, "unsigned ", 9) == 0) { sign = 0; type += 9; } if (strcmp(type, "char") == 0) { if (sign) return (unsigned long long)(char)val & 0xff; else return val & 0xff; } if (strcmp(type, "short") == 0) { if (sign) return (unsigned long long)(short)val & 0xffff; else return val & 0xffff; } if (strcmp(type, "int") == 0) { if (sign) return (unsigned long long)(int)val & 0xffffffff; else return val & 0xffffffff; } return val; } /* * Try to figure out the type. */ static unsigned long long eval_type(unsigned long long val, struct tep_print_arg *arg, int pointer) { if (arg->type != TEP_PRINT_TYPE) { do_warning("expected type argument"); return 0; } return eval_type_str(val, arg->typecast.type, pointer); } static int arg_num_eval(struct tep_print_arg *arg, long long *val) { long long left, right; int ret = 1; switch (arg->type) { case TEP_PRINT_ATOM: *val = strtoll(arg->atom.atom, NULL, 0); break; case TEP_PRINT_TYPE: ret = arg_num_eval(arg->typecast.item, val); if (!ret) break; *val = eval_type(*val, arg, 0); break; case TEP_PRINT_OP: switch (arg->op.op[0]) { case '|': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1]) *val = left || right; else *val = left | right; break; case '&': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1]) *val = left && right; else *val = left & right; break; case '<': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case 0: *val = left < right; break; case '<': *val = left << right; break; case '=': *val = left <= right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '>': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case 0: *val = left > right; break; case '>': *val = left >> right; break; case '=': *val = left >= right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '=': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1] != '=') { do_warning("unknown op '%s'", arg->op.op); ret = 0; } else *val = left == right; break; case '!': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case '=': *val = left != right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '-': /* check for negative */ if (arg->op.left->type == TEP_PRINT_NULL) left = 0; else ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; *val = left - right; break; case '+': if (arg->op.left->type == TEP_PRINT_NULL) left = 0; else ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; *val = left + right; break; case '~': ret = arg_num_eval(arg->op.right, &right); if (!ret) break; *val = ~right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case TEP_PRINT_NULL: case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL: case TEP_PRINT_STRING: case TEP_PRINT_BSTRING: case TEP_PRINT_BITMASK: case TEP_PRINT_CPUMASK: default: do_warning("invalid eval type %d", arg->type); ret = 0; } return ret; } static char *arg_eval (struct tep_print_arg *arg) { long long val; static char buf[24]; switch (arg->type) { case TEP_PRINT_ATOM: return arg->atom.atom; case TEP_PRINT_TYPE: return arg_eval(arg->typecast.item); case TEP_PRINT_OP: if (!arg_num_eval(arg, &val)) break; sprintf(buf, "%lld", val); return buf; case TEP_PRINT_NULL: case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL: case TEP_PRINT_STRING: case TEP_PRINT_BSTRING: case TEP_PRINT_BITMASK: case TEP_PRINT_CPUMASK: default: do_warning("invalid eval type %d", arg->type); break; } return NULL; } static enum tep_event_type process_fields(struct tep_event *event, struct tep_print_flag_sym **list, char **tok) { enum tep_event_type type; struct tep_print_arg *arg = NULL; struct tep_print_flag_sym *field; char *token = *tok; char *value; do { free_token(token); type = read_token_item(event->tep, &token); if (test_type_token(type, token, TEP_EVENT_OP, "{")) break; arg = alloc_arg(); if (!arg) goto out_free; free_token(token); type = process_arg(event, arg, &token); if (type == TEP_EVENT_OP) type = process_op(event, arg, &token); if (type == TEP_EVENT_ERROR) goto out_free; if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) goto out_free; field = calloc(1, sizeof(*field)); if (!field) goto out_free; value = arg_eval(arg); if (value == NULL) goto out_free_field; field->value = strdup(value); if (field->value == NULL) goto out_free_field; free_arg(arg); arg = alloc_arg(); if (!arg) goto out_free; free_token(token); type = process_arg(event, arg, &token); if (test_type_token(type, token, TEP_EVENT_OP, "}")) goto out_free_field; value = arg_eval(arg); if (value == NULL) goto out_free_field; field->str = strdup(value); if (field->str == NULL) goto out_free_field; free_arg(arg); arg = NULL; *list = field; list = &field->next; free_token(token); type = read_token_item(event->tep, &token); } while (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0); *tok = token; return type; out_free_field: free_flag_sym(field); out_free: free_arg(arg); free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_flags(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_print_arg *field; enum tep_event_type type; char *token = NULL; memset(arg, 0, sizeof(*arg)); arg->type = TEP_PRINT_FLAGS; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } type = process_field_arg(event, field, &token); /* Handle operations in the first argument */ while (type == TEP_EVENT_OP) type = process_op(event, field, &token); if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) goto out_free_field; free_token(token); arg->flags.field = field; type = read_token_item(event->tep, &token); if (event_item_type(type)) { arg->flags.delim = token; type = read_token_item(event->tep, &token); } if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) goto out_free; type = process_fields(event, &arg->flags.flags, &token); if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(event->tep, tok); return type; out_free_field: free_arg(field); out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_symbols(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_print_arg *field; enum tep_event_type type; char *token = NULL; memset(arg, 0, sizeof(*arg)); arg->type = TEP_PRINT_SYMBOL; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } type = process_field_arg(event, field, &token); if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) goto out_free_field; arg->symbol.field = field; type = process_fields(event, &arg->symbol.symbols, &token); if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(event->tep, tok); return type; out_free_field: free_arg(field); out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_hex_common(struct tep_event *event, struct tep_print_arg *arg, char **tok, enum tep_print_arg_type type) { memset(arg, 0, sizeof(*arg)); arg->type = type; if (alloc_and_process_delim(event, ",", &arg->hex.field)) goto out; if (alloc_and_process_delim(event, ")", &arg->hex.size)) goto free_field; return read_token_item(event->tep, tok); free_field: free_arg(arg->hex.field); arg->hex.field = NULL; out: *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_hex(struct tep_event *event, struct tep_print_arg *arg, char **tok) { return process_hex_common(event, arg, tok, TEP_PRINT_HEX); } static enum tep_event_type process_hex_str(struct tep_event *event, struct tep_print_arg *arg, char **tok) { return process_hex_common(event, arg, tok, TEP_PRINT_HEX_STR); } static enum tep_event_type process_int_array(struct tep_event *event, struct tep_print_arg *arg, char **tok) { memset(arg, 0, sizeof(*arg)); arg->type = TEP_PRINT_INT_ARRAY; if (alloc_and_process_delim(event, ",", &arg->int_array.field)) goto out; if (alloc_and_process_delim(event, ",", &arg->int_array.count)) goto free_field; if (alloc_and_process_delim(event, ")", &arg->int_array.el_size)) goto free_size; return read_token_item(event->tep, tok); free_size: free_arg(arg->int_array.count); arg->int_array.count = NULL; free_field: free_arg(arg->int_array.field); arg->int_array.field = NULL; out: *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_dynamic_array(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_format_field *field; enum tep_event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = TEP_PRINT_DYNAMIC_ARRAY; /* * The item within the parenthesis is another field that holds * the index into where the array starts. */ type = read_token(event->tep, &token); *tok = token; if (type != TEP_EVENT_ITEM) goto out_free; /* Find the field */ field = tep_find_field(event, token); if (!field) goto out_free; arg->dynarray.field = field; arg->dynarray.index = 0; if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0) goto out_free; free_token(token); type = read_token_item(event->tep, &token); *tok = token; if (type != TEP_EVENT_OP || strcmp(token, "[") != 0) return type; free_token(token); arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); *tok = NULL; return TEP_EVENT_ERROR; } type = process_arg(event, arg, &token); if (type == TEP_EVENT_ERROR) goto out_free_arg; if (!test_type_token(type, token, TEP_EVENT_OP, "]")) goto out_free_arg; free_token(token); type = read_token_item(event->tep, tok); return type; out_free_arg: free_arg(arg); out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_dynamic_array_len(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_format_field *field; enum tep_event_type type; char *token; if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto out_free; arg->type = TEP_PRINT_DYNAMIC_ARRAY_LEN; /* Find the field */ field = tep_find_field(event, token); if (!field) goto out_free; arg->dynarray.field = field; arg->dynarray.index = 0; if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0) goto out_err; free_token(token); type = read_token(event->tep, &token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_paren(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_print_arg *item_arg; enum tep_event_type type; char *token; type = process_arg(event, arg, &token); if (type == TEP_EVENT_ERROR) goto out_free; if (type == TEP_EVENT_OP) type = process_op(event, arg, &token); if (type == TEP_EVENT_ERROR) goto out_free; /* * If REC is surrounded by parenthesis, the process_arg() * will return TEP_EVENT_ITEM with token == ")". In * this case, we need to continue processing the item * and return. */ if (type == TEP_EVENT_ITEM && strcmp(token, ")") == 0) { free_token(token); return process_entry(event, arg, tok); } if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(event->tep, &token); /* * If the next token is an item or another open paren, then * this was a typecast. */ if (event_item_type(type) || (type == TEP_EVENT_DELIM && strcmp(token, "(") == 0)) { /* make this a typecast and contine */ /* prevous must be an atom */ if (arg->type != TEP_PRINT_ATOM) { do_warning_event(event, "previous needed to be TEP_PRINT_ATOM"); goto out_free; } item_arg = alloc_arg(); if (!item_arg) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } arg->type = TEP_PRINT_TYPE; arg->typecast.type = arg->atom.atom; arg->typecast.item = item_arg; type = process_arg_token(event, item_arg, &token, type); } *tok = token; return type; out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_str(struct tep_event *event, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; char *token; if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto out_free; arg->type = TEP_PRINT_STRING; arg->string.string = token; arg->string.offset = -1; arg->string.field = NULL; if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0) goto out_err; type = read_token(event->tep, &token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_bitmask(struct tep_event *event, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; char *token; if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto out_free; arg->type = TEP_PRINT_BITMASK; arg->bitmask.bitmask = token; arg->bitmask.offset = -1; arg->bitmask.field = NULL; if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0) goto out_err; type = read_token(event->tep, &token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_cpumask(struct tep_event *event __maybe_unused, struct tep_print_arg *arg, char **tok) { enum tep_event_type type = process_bitmask(event, arg, tok); if (type != TEP_EVENT_ERROR) arg->type = TEP_PRINT_CPUMASK; return type; } static struct tep_function_handler * find_func_handler(struct tep_handle *tep, char *func_name) { struct tep_function_handler *func; if (!tep) return NULL; for (func = tep->func_handlers; func; func = func->next) { if (strcmp(func->name, func_name) == 0) break; } return func; } static void remove_func_handler(struct tep_handle *tep, char *func_name) { struct tep_function_handler *func; struct tep_function_handler **next; next = &tep->func_handlers; while ((func = *next)) { if (strcmp(func->name, func_name) == 0) { *next = func->next; free_func_handle(func); break; } next = &func->next; } } static enum tep_event_type process_func_handler(struct tep_event *event, struct tep_function_handler *func, struct tep_print_arg *arg, char **tok) { struct tep_print_arg **next_arg; struct tep_print_arg *farg; enum tep_event_type type; char *token; int i; arg->type = TEP_PRINT_FUNC; arg->func.func = func; *tok = NULL; next_arg = &(arg->func.args); for (i = 0; i < func->nr_args; i++) { farg = alloc_arg(); if (!farg) { do_warning_event(event, "%s: not enough memory!", __func__); return TEP_EVENT_ERROR; } type = process_arg(event, farg, &token); if (i < (func->nr_args - 1)) { if (type != TEP_EVENT_DELIM || strcmp(token, ",") != 0) { do_warning_event(event, "Error: function '%s()' expects %d arguments but event %s only uses %d", func->name, func->nr_args, event->name, i + 1); goto err; } } else { if (type != TEP_EVENT_DELIM || strcmp(token, ")") != 0) { do_warning_event(event, "Error: function '%s()' only expects %d arguments but event %s has more", func->name, func->nr_args, event->name); goto err; } } *next_arg = farg; next_arg = &(farg->next); free_token(token); } type = read_token(event->tep, &token); *tok = token; return type; err: free_arg(farg); free_token(token); return TEP_EVENT_ERROR; } static enum tep_event_type process_builtin_expect(struct tep_event *event, struct tep_print_arg *arg, char **tok) { enum tep_event_type type; char *token = NULL; /* Handle __builtin_expect( cond, #) */ type = process_arg(event, arg, &token); if (type != TEP_EVENT_DELIM || token[0] != ',') goto out_free; free_token(token); /* We don't care what the second parameter is of the __builtin_expect() */ if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto out_free; if (read_expected(event->tep, TEP_EVENT_DELIM, ")") < 0) goto out_free; free_token(token); type = read_token_item(event->tep, tok); return type; out_free: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_sizeof(struct tep_event *event, struct tep_print_arg *arg, char **tok) { struct tep_format_field *field; enum tep_event_type type; char *token = NULL; bool ok = false; int ret; type = read_token_item(event->tep, &token); arg->type = TEP_PRINT_ATOM; /* We handle some sizeof types */ if (strcmp(token, "unsigned") == 0) { free_token(token); type = read_token_item(event->tep, &token); if (type == TEP_EVENT_ERROR) goto error; if (type != TEP_EVENT_ITEM) ok = true; } if (ok || strcmp(token, "int") == 0) { arg->atom.atom = strdup("4"); } else if (strcmp(token, "long") == 0) { free_token(token); type = read_token_item(event->tep, &token); if (token && strcmp(token, "long") == 0) { arg->atom.atom = strdup("8"); } else { switch (event->tep->long_size) { case 4: arg->atom.atom = strdup("4"); break; case 8: arg->atom.atom = strdup("8"); break; default: /* long size not defined yet, fail to parse it */ goto error; } /* The token is the next token */ ok = true; } } else if (strcmp(token, "REC") == 0) { free_token(token); type = read_token_item(event->tep, &token); if (test_type_token(type, token, TEP_EVENT_OP, "->")) goto error; free_token(token); if (read_expect_type(event->tep, TEP_EVENT_ITEM, &token) < 0) goto error; field = tep_find_any_field(event, token); /* Can't handle arrays (yet) */ if (!field || field->flags & TEP_FIELD_IS_ARRAY) goto error; ret = asprintf(&arg->atom.atom, "%d", field->size); if (ret < 0) goto error; } else if (!ok) { goto error; } if (!ok) { free_token(token); type = read_token_item(event->tep, tok); } if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) goto error; free_token(token); return read_token_item(event->tep, tok); error: free_token(token); *tok = NULL; return TEP_EVENT_ERROR; } static enum tep_event_type process_function(struct tep_event *event, struct tep_print_arg *arg, char *token, char **tok) { struct tep_function_handler *func; if (strcmp(token, "__print_flags") == 0) { free_token(token); is_flag_field = 1; return process_flags(event, arg, tok); } if (strcmp(token, "__print_symbolic") == 0) { free_token(token); is_symbolic_field = 1; return process_symbols(event, arg, tok); } if (strcmp(token, "__print_hex") == 0) { free_token(token); return process_hex(event, arg, tok); } if (strcmp(token, "__print_hex_str") == 0) { free_token(token); return process_hex_str(event, arg, tok); } if (strcmp(token, "__print_array") == 0) { free_token(token); return process_int_array(event, arg, tok); } if (strcmp(token, "__get_str") == 0 || strcmp(token, "__get_rel_str") == 0) { free_token(token); return process_str(event, arg, tok); } if (strcmp(token, "__get_bitmask") == 0 || strcmp(token, "__get_rel_bitmask") == 0) { free_token(token); return process_bitmask(event, arg, tok); } if (strcmp(token, "__get_cpumask") == 0 || strcmp(token, "__get_rel_cpumask") == 0) { free_token(token); return process_cpumask(event, arg, tok); } if (strcmp(token, "__get_dynamic_array") == 0 || strcmp(token, "__get_rel_dynamic_array") == 0 || strcmp(token, "__get_sockaddr") == 0 || strcmp(token, "__get_sockaddr_rel") == 0) { free_token(token); return process_dynamic_array(event, arg, tok); } if (strcmp(token, "__get_dynamic_array_len") == 0 || strcmp(token, "__get_rel_dynamic_array_len") == 0) { free_token(token); return process_dynamic_array_len(event, arg, tok); } if (strcmp(token, "__builtin_expect") == 0) { free_token(token); return process_builtin_expect(event, arg, tok); } if (strcmp(token, "sizeof") == 0) { free_token(token); return process_sizeof(event, arg, tok); } func = find_func_handler(event->tep, token); if (func) { free_token(token); return process_func_handler(event, func, arg, tok); } do_warning_event(event, "function %s not defined", token); free_token(token); return TEP_EVENT_ERROR; } static enum tep_event_type process_arg_token(struct tep_event *event, struct tep_print_arg *arg, char **tok, enum tep_event_type type) { char *token; char *atom; token = *tok; switch (type) { case TEP_EVENT_ITEM: if (strcmp(token, "REC") == 0) { free_token(token); type = process_entry(event, arg, &token); break; } atom = token; /* test the next token */ type = read_token_item(event->tep, &token); /* * If the next token is a parenthesis, then this * is a function. */ if (type == TEP_EVENT_DELIM && strcmp(token, "(") == 0) { free_token(token); token = NULL; /* this will free atom. */ type = process_function(event, arg, atom, &token); break; } /* atoms can be more than one token long */ while (type == TEP_EVENT_ITEM) { int ret; ret = append(&atom, " ", token); if (ret < 0) { free(atom); *tok = NULL; free_token(token); return TEP_EVENT_ERROR; } free_token(token); type = read_token_item(event->tep, &token); } arg->type = TEP_PRINT_ATOM; arg->atom.atom = atom; break; case TEP_EVENT_DQUOTE: case TEP_EVENT_SQUOTE: arg->type = TEP_PRINT_ATOM; arg->atom.atom = token; type = read_token_item(event->tep, &token); break; case TEP_EVENT_DELIM: if (strcmp(token, "(") == 0) { free_token(token); type = process_paren(event, arg, &token); break; } case TEP_EVENT_OP: /* handle single ops */ arg->type = TEP_PRINT_OP; arg->op.op = token; arg->op.left = NULL; type = process_op(event, arg, &token); /* On error, the op is freed */ if (type == TEP_EVENT_ERROR) arg->op.op = NULL; /* return error type if errored */ break; case TEP_EVENT_ERROR ... TEP_EVENT_NEWLINE: default: do_warning_event(event, "unexpected type %d", type); return TEP_EVENT_ERROR; } *tok = token; return type; } static int event_read_print_args(struct tep_event *event, struct tep_print_arg **list) { enum tep_event_type type = TEP_EVENT_ERROR; struct tep_print_arg *arg; char *token; int args = 0; do { if (type == TEP_EVENT_NEWLINE) { type = read_token_item(event->tep, &token); continue; } arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); return -1; } type = process_arg(event, arg, &token); if (type == TEP_EVENT_ERROR) { free_token(token); free_arg(arg); return -1; } *list = arg; args++; if (type == TEP_EVENT_OP) { type = process_op(event, arg, &token); free_token(token); if (consolidate_op_arg(arg) < 0) type = TEP_EVENT_ERROR; if (type == TEP_EVENT_ERROR) { *list = NULL; free_arg(arg); return -1; } list = &arg->next; continue; } if (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0) { free_token(token); *list = arg; list = &arg->next; continue; } break; } while (type != TEP_EVENT_NONE); if (type != TEP_EVENT_NONE && type != TEP_EVENT_ERROR) free_token(token); return args; } static int event_read_print(struct tep_event *event) { enum tep_event_type type; char *token; int ret; if (read_expected_item(event->tep, TEP_EVENT_ITEM, "print") < 0) return -1; if (read_expected(event->tep, TEP_EVENT_ITEM, "fmt") < 0) return -1; if (read_expected(event->tep, TEP_EVENT_OP, ":") < 0) return -1; if (read_expect_type(event->tep, TEP_EVENT_DQUOTE, &token) < 0) goto fail; concat: event->print_fmt.format = token; event->print_fmt.args = NULL; /* ok to have no arg */ type = read_token_item(event->tep, &token); if (type == TEP_EVENT_NONE) return 0; /* Handle concatenation of print lines */ if (type == TEP_EVENT_DQUOTE) { char *cat; if (asprintf(&cat, "%s%s", event->print_fmt.format, token) < 0) goto fail; free_token(token); free_token(event->print_fmt.format); event->print_fmt.format = NULL; token = cat; goto concat; } if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) goto fail; free_token(token); ret = event_read_print_args(event, &event->print_fmt.args); if (ret < 0) return -1; return ret; fail: free_token(token); return -1; } /** * tep_find_common_field - return a common field by event * @event: handle for the event * @name: the name of the common field to return * * Returns a common field from the event by the given @name. * This only searches the common fields and not all field. */ struct tep_format_field * tep_find_common_field(struct tep_event *event, const char *name) { struct tep_format_field *format; for (format = event->format.common_fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * tep_find_field - find a non-common field * @event: handle for the event * @name: the name of the non-common field * * Returns a non-common field by the given @name. * This does not search common fields. */ struct tep_format_field * tep_find_field(struct tep_event *event, const char *name) { struct tep_format_field *format; for (format = event->format.fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * tep_find_any_field - find any field by name * @event: handle for the event * @name: the name of the field * * Returns a field by the given @name. * This searches the common field names first, then * the non-common ones if a common one was not found. */ struct tep_format_field * tep_find_any_field(struct tep_event *event, const char *name) { struct tep_format_field *format; format = tep_find_common_field(event, name); if (format) return format; return tep_find_field(event, name); } /** * tep_read_number - read a number from data * @tep: a handle to the trace event parser context * @ptr: the raw data * @size: the size of the data that holds the number * * Returns the number (converted to host) from the * raw data. */ unsigned long long tep_read_number(struct tep_handle *tep, const void *ptr, int size) { unsigned long long val; switch (size) { case 1: return *(unsigned char *)ptr; case 2: return data2host2(tep, *(unsigned short *)ptr); case 4: return data2host4(tep, *(unsigned int *)ptr); case 8: memcpy(&val, (ptr), sizeof(unsigned long long)); return data2host8(tep, val); default: /* BUG! */ return 0; } } /** * tep_read_number_field - read a number from data * @field: a handle to the field * @data: the raw data to read * @value: the value to place the number in * * Reads raw data according to a field offset and size, * and translates it into @value. * * Returns 0 on success, -1 otherwise. */ int tep_read_number_field(struct tep_format_field *field, const void *data, unsigned long long *value) { if (!field) return -1; switch (field->size) { case 1: case 2: case 4: case 8: *value = tep_read_number(field->event->tep, data + field->offset, field->size); return 0; default: return -1; } } static int get_common_info(struct tep_handle *tep, const char *type, int *offset, int *size) { struct tep_event *event; struct tep_format_field *field; /* * All events should have the same common elements. * Pick any event to find where the type is; */ if (!tep->events) { do_warning("no event_list!"); return -1; } event = tep->events[0]; field = tep_find_common_field(event, type); if (!field) return -1; *offset = field->offset; *size = field->size; return 0; } static int __parse_common(struct tep_handle *tep, void *data, int *size, int *offset, const char *name) { int ret; if (!*size) { ret = get_common_info(tep, name, offset, size); if (ret < 0) return ret; } return tep_read_number(tep, data + *offset, *size); } static int trace_parse_common_type(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->type_size, &tep->type_offset, "common_type"); } static int parse_common_pid(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->pid_size, &tep->pid_offset, "common_pid"); } static int parse_common_pc(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->pc_size, &tep->pc_offset, "common_preempt_count"); } static int parse_common_flags(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->flags_size, &tep->flags_offset, "common_flags"); } static int parse_common_lock_depth(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->ld_size, &tep->ld_offset, "common_lock_depth"); } static int parse_common_migrate_disable(struct tep_handle *tep, void *data) { return __parse_common(tep, data, &tep->ld_size, &tep->ld_offset, "common_migrate_disable"); } static int events_id_cmp(const void *a, const void *b); /** * tep_find_event - find an event by given id * @tep: a handle to the trace event parser context * @id: the id of the event * * Returns an event that has a given @id. */ struct tep_event *tep_find_event(struct tep_handle *tep, int id) { struct tep_event **eventptr; struct tep_event key; struct tep_event *pkey = &key; /* Check cache first */ if (tep->last_event && tep->last_event->id == id) return tep->last_event; key.id = id; eventptr = bsearch(&pkey, tep->events, tep->nr_events, sizeof(*tep->events), events_id_cmp); if (eventptr) { tep->last_event = *eventptr; return *eventptr; } return NULL; } /** * tep_find_event_by_name - find an event by given name * @tep: a handle to the trace event parser context * @sys: the system name to search for * @name: the name of the event to search for * * This returns an event with a given @name and under the system * @sys. If @sys is NULL the first event with @name is returned. */ struct tep_event * tep_find_event_by_name(struct tep_handle *tep, const char *sys, const char *name) { struct tep_event *event = NULL; int i; if (tep->last_event && strcmp(tep->last_event->name, name) == 0 && (!sys || strcmp(tep->last_event->system, sys) == 0)) return tep->last_event; for (i = 0; i < tep->nr_events; i++) { event = tep->events[i]; if (strcmp(event->name, name) == 0) { if (!sys) break; if (strcmp(event->system, sys) == 0) break; } } if (i == tep->nr_events) event = NULL; tep->last_event = event; return event; } static unsigned long long test_for_symbol(struct tep_handle *tep, struct tep_print_arg *arg) { unsigned long long val = 0; struct func_list *item = tep->funclist; char *func; int i; if (isdigit(arg->atom.atom[0])) return 0; /* Linear search but only happens once (see after the loop) */ for (i = 0; i < (int)tep->func_count; i++) { unsigned long long addr; const char *name; if (tep->func_map) { addr = tep->func_map[i].addr; name = tep->func_map[i].func; } else if (item) { addr = item->addr; name = item->func; item = item->next; } else break; if (strcmp(arg->atom.atom, name) == 0) { val = addr; break; } } /* * This modifies the arg to hardcode the value * and will not loop again. */ func = realloc(arg->atom.atom, 32); if (func) { snprintf(func, 32, "%lld", val); arg->atom.atom = func; } return val; } #define TEP_OFFSET_LEN_MASK 0xffff #define TEP_LEN_SHIFT 16 static void dynamic_offset(struct tep_handle *tep, int size, void *data, int data_size, unsigned int *offset, unsigned int *len) { unsigned long long val; unsigned int o, l; /* * The total allocated length of the dynamic array is * stored in the top half of the field and the offset * is in the bottom half of the 32 bit field. */ val = tep_read_number(tep, data, size); /* Check for overflows */ o = (unsigned int)(val & TEP_OFFSET_LEN_MASK); /* If there's no length, then just make the length the size of the data */ if (size == 2) l = data_size - o; else l = (unsigned int)((val >> TEP_LEN_SHIFT) & TEP_OFFSET_LEN_MASK); if (offset) *offset = o > data_size ? 0 : o; if (len) *len = o + l > data_size ? 0 : l; } static inline void dynamic_offset_field(struct tep_handle *tep, struct tep_format_field *field, void *data, int size, unsigned int *offset, unsigned int *len) { /* Test for overflow */ if (field->offset + field->size > size) { if (offset) *offset = 0; if (len) *len = 0; return; } dynamic_offset(tep, field->size, data + field->offset, size, offset, len); if (field->flags & TEP_FIELD_IS_RELATIVE) *offset += field->offset + field->size; } static bool check_data_offset_size(struct tep_event *event, const char *field_name, int data_size, int field_offset, int field_size) { /* Check to make sure the field is within the data */ if (field_offset + field_size <= data_size) return false; tep_warning("Event '%s' field '%s' goes beyond the size of the event (%d > %d)", event->name, field_name, field_offset + field_size, data_size); return true; } static unsigned long long eval_num_arg(void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { struct tep_handle *tep = event->tep; unsigned long long val = 0; unsigned long long left, right; struct tep_print_arg *typearg = NULL; struct tep_print_arg *larg; unsigned int offset; unsigned int field_size; switch (arg->type) { case TEP_PRINT_NULL: /* ?? */ return 0; case TEP_PRINT_ATOM: val = strtoull(arg->atom.atom, NULL, 0); if (!val) val = test_for_symbol(tep, arg); return val; case TEP_PRINT_FIELD: if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) goto out_warning_field; } if (check_data_offset_size(event, arg->field.name, size, arg->field.field->offset, arg->field.field->size)) { val = 0; break; } /* must be a number */ val = tep_read_number(tep, data + arg->field.field->offset, arg->field.field->size); break; case TEP_PRINT_FLAGS: case TEP_PRINT_SYMBOL: case TEP_PRINT_INT_ARRAY: case TEP_PRINT_HEX: case TEP_PRINT_HEX_STR: break; case TEP_PRINT_TYPE: val = eval_num_arg(data, size, event, arg->typecast.item); return eval_type(val, arg, 0); case TEP_PRINT_STRING: case TEP_PRINT_BSTRING: case TEP_PRINT_BITMASK: case TEP_PRINT_CPUMASK: return 0; case TEP_PRINT_FUNC: { struct trace_seq s; trace_seq_init(&s); val = process_defined_func(&s, data, size, event, arg); trace_seq_destroy(&s); return val; } case TEP_PRINT_OP: if (strcmp(arg->op.op, "[") == 0) { /* * Arrays are special, since we don't want * to read the arg as is. */ right = eval_num_arg(data, size, event, arg->op.right); /* handle typecasts */ larg = arg->op.left; while (larg->type == TEP_PRINT_TYPE) { if (!typearg) typearg = larg; larg = larg->typecast.item; } /* Default to long size */ field_size = tep->long_size; switch (larg->type) { case TEP_PRINT_DYNAMIC_ARRAY: dynamic_offset_field(tep, larg->dynarray.field, data, size, &offset, NULL); offset += right; if (larg->dynarray.field->elementsize) field_size = larg->dynarray.field->elementsize; break; case TEP_PRINT_FIELD: if (!larg->field.field) { larg->field.field = tep_find_any_field(event, larg->field.name); if (!larg->field.field) { arg = larg; goto out_warning_field; } } field_size = larg->field.field->elementsize; offset = larg->field.field->offset + right * larg->field.field->elementsize; break; default: goto default_op; /* oops, all bets off */ } if (check_data_offset_size(event, arg->field.name, size, offset, field_size)) { val = 0; break; } val = tep_read_number(tep, data + offset, field_size); if (typearg) val = eval_type(val, typearg, 1); break; } else if (strcmp(arg->op.op, "?") == 0) { left = eval_num_arg(data, size, event, arg->op.left); arg = arg->op.right; if (left) val = eval_num_arg(data, size, event, arg->op.left); else val = eval_num_arg(data, size, event, arg->op.right); break; } default_op: left = eval_num_arg(data, size, event, arg->op.left); right = eval_num_arg(data, size, event, arg->op.right); switch (arg->op.op[0]) { case '!': switch (arg->op.op[1]) { case 0: val = !right; break; case '=': val = left != right; break; default: goto out_warning_op; } break; case '~': val = ~right; break; case '|': if (arg->op.op[1]) val = left || right; else val = left | right; break; case '&': if (arg->op.op[1]) val = left && right; else val = left & right; break; case '<': switch (arg->op.op[1]) { case 0: val = left < right; break; case '<': val = left << right; break; case '=': val = left <= right; break; default: goto out_warning_op; } break; case '>': switch (arg->op.op[1]) { case 0: val = left > right; break; case '>': val = left >> right; break; case '=': val = left >= right; break; default: goto out_warning_op; } break; case '=': if (arg->op.op[1] != '=') goto out_warning_op; val = left == right; break; case '-': val = left - right; break; case '+': val = left + right; break; case '/': val = left / right; break; case '%': val = left % right; break; case '*': val = left * right; break; default: goto out_warning_op; } break; case TEP_PRINT_DYNAMIC_ARRAY_LEN: dynamic_offset_field(tep, arg->dynarray.field, data, size, NULL, &field_size); val = field_size; break; case TEP_PRINT_DYNAMIC_ARRAY: /* Without [], we pass the address to the dynamic data */ dynamic_offset_field(tep, arg->dynarray.field, data, size, &offset, NULL); if (check_data_offset_size(event, arg->field.name, size, offset, 0)) { val = (unsigned long)data; break; } val = (unsigned long)data + offset; break; default: /* not sure what to do there */ return 0; } return val; out_warning_op: do_warning_event(event, "%s: unknown op '%s'", __func__, arg->op.op); return 0; out_warning_field: do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); return 0; } struct flag { const char *name; unsigned long long value; }; static const struct flag flags[] = { { "HI_SOFTIRQ", 0 }, { "TIMER_SOFTIRQ", 1 }, { "NET_TX_SOFTIRQ", 2 }, { "NET_RX_SOFTIRQ", 3 }, { "BLOCK_SOFTIRQ", 4 }, { "IRQ_POLL_SOFTIRQ", 5 }, { "TASKLET_SOFTIRQ", 6 }, { "SCHED_SOFTIRQ", 7 }, { "HRTIMER_SOFTIRQ", 8 }, { "RCU_SOFTIRQ", 9 }, { "HRTIMER_NORESTART", 0 }, { "HRTIMER_RESTART", 1 }, }; static long long eval_flag(const char *flag) { int i; /* * Some flags in the format files do not get converted. * If the flag is not numeric, see if it is something that * we already know about. */ if (isdigit(flag[0])) return strtoull(flag, NULL, 0); for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++) if (strcmp(flags[i].name, flag) == 0) return flags[i].value; return -1LL; } static void print_str_to_seq(struct trace_seq *s, const char *format, int len_arg, const char *str) { if (len_arg >= 0) trace_seq_printf(s, format, len_arg, str); else trace_seq_printf(s, format, str); } static void print_bitmask_to_seq(struct tep_handle *tep, struct trace_seq *s, const char *format, int len_arg, const void *data, int size) { int nr_bits = size * 8; int str_size = (nr_bits + 3) / 4; int len = 0; char buf[3]; char *str; int index; int i; /* * The kernel likes to put in commas every 32 bits, we * can do the same. */ str_size += (nr_bits - 1) / 32; str = malloc(str_size + 1); if (!str) { do_warning("%s: not enough memory!", __func__); return; } str[str_size] = 0; /* Start out with -2 for the two chars per byte */ for (i = str_size - 2; i >= 0; i -= 2) { /* * data points to a bit mask of size bytes. * In the kernel, this is an array of long words, thus * endianness is very important. */ if (tep->file_bigendian) index = size - (len + 1); else index = len; snprintf(buf, 3, "%02x", *((unsigned char *)data + index)); memcpy(str + i, buf, 2); len++; if (!(len & 3) && i > 0) { i--; str[i] = ','; } } if (len_arg >= 0) trace_seq_printf(s, format, len_arg, str); else trace_seq_printf(s, format, str); free(str); } #define log10(n) \ ( \ n < 10UL ? 0 : \ n < 100UL ? 1 : \ n < 1000UL ? 2 : \ n < 10000UL ? 3 : \ n < 100000UL ? 4 : \ n < 1000000UL ? 5 : \ n < 10000000UL ? 6 : \ n < 100000000UL ? 7 : \ n < 1000000000UL ? 8 : \ 9 \ ) /* ilog10(0) should be 1 but the 0 simplifies below math */ #define ilog10(n) \ ( \ n == 0 ? 0UL : \ n == 1 ? 10UL : \ n == 2 ? 100UL : \ n == 3 ? 1000UL : \ n == 4 ? 10000UL : \ n == 5 ? 100000UL : \ n == 6 ? 1000000UL : \ n == 7 ? 10000000UL : \ n == 8 ? 100000000UL : \ 1000000000UL \ ) static unsigned int cpumask_worst_size(unsigned int nr_bits) { /* * Printing all the CPUs separated by a comma is a decent bound for the * maximum memory required to print a cpumask (a slightly better bound * is chunks of 2 bits set, i.e. 0-1,3-4,6-7...). * * e.g. for nr_bits=132: * - 131 commas * - 10 * 1 chars for CPUS [0, 9] * - 90 * 2 chars for CPUS [10-99] * - 32 * 3 chars for CPUS [100-131] */ unsigned int last_cpu = nr_bits - 1; unsigned int nr_chars = nr_bits - 1; int last_lvl = log10(last_cpu); /* All log10 levels before the last one have all values used */ for (int lvl = 0; lvl < last_lvl; lvl++) { int nr_values = ilog10(lvl + 1) - ilog10(lvl); nr_chars += nr_values * (lvl + 1); } /* Last level is incomplete */ nr_chars += (nr_bits - ilog10(last_lvl)) * (last_lvl + 1); return nr_chars; } static void print_cpumask_to_seq(struct tep_handle *tep, struct trace_seq *s, const char *format, int len_arg, const void *data, int size) { int firstone = -1, firstzero = -1; int nr_bits = size * 8; bool first = true; int str_size = 0; char buf[12]; /* '-' + log10(2^32) + 1 digits + '\0' */ char *str; int index; int i; str = malloc(cpumask_worst_size(nr_bits) + 1); if (!str) { do_warning("%s: not enough memory!", __func__); return; } for (i = 0; i < size; i++) { unsigned char byte; int fmtsize; if (tep->file_bigendian) index = size - (i + 1); else index = i; /* Byte by byte scan, not the best... */ byte = *(((unsigned char *)data) + index); more: /* First find a bit set to one...*/ if (firstone < 0 && byte) { /* * Set all lower bits, so a later ffz on this same byte * is guaranteed to find a later bit. */ firstone = ffs(byte) - 1; byte |= (1 << firstone) - 1; firstone += i * 8; } if (firstone < 0) continue; /* ...Then find a bit set to zero */ if ((~byte) & 0xFF) { /* * Clear all lower bits, so a later ffs on this same * byte is guaranteed to find a later bit. */ firstzero = ffs(~byte) - 1; byte &= ~((1 << (firstzero)) - 1); firstzero += i * 8; } else if (i == size - 1) { /* ...Or reach the end of the mask */ firstzero = nr_bits; byte = 0; } else { continue; } /* We've found a bit set to one, and a later bit set to zero. */ if (!first) { str[str_size] = ','; str_size++; } first = false; /* It takes {log10(number) + 1} chars to format a number */ fmtsize = log10(firstone) + 1; snprintf(buf, fmtsize + 1, "%d", firstone); memcpy(str + str_size, buf, fmtsize); str_size += fmtsize; if (firstzero > firstone + 1) { fmtsize = log10(firstzero - 1) + 2; snprintf(buf, fmtsize + 1, "-%d", firstzero - 1); memcpy(str + str_size, buf, fmtsize); str_size += fmtsize; } firstzero = firstone = -1; if (byte) goto more; } str[str_size] = 0; str_size++; if (len_arg >= 0) trace_seq_printf(s, format, len_arg, str); else trace_seq_printf(s, format, str); free(str); } static void print_str_arg(struct trace_seq *s, void *data, int size, struct tep_event *event, const char *format, int len_arg, struct tep_print_arg *arg) { struct tep_handle *tep = event->tep; struct tep_print_flag_sym *flag; struct tep_format_field *field; struct printk_map *printk; unsigned int offset, len; long long val, fval; unsigned long long addr; char *str; unsigned char *hex; int print; int i; switch (arg->type) { case TEP_PRINT_NULL: /* ?? */ return; case TEP_PRINT_ATOM: print_str_to_seq(s, format, len_arg, arg->atom.atom); return; case TEP_PRINT_FIELD: field = arg->field.field; if (!field) { field = tep_find_any_field(event, arg->field.name); if (!field) { str = arg->field.name; goto out_warning_field; } arg->field.field = field; } /* Zero sized fields, mean the rest of the data */ len = field->size ? : size - field->offset; /* * Some events pass in pointers. If this is not an array * and the size is the same as long_size, assume that it * is a pointer. */ if (!(field->flags & TEP_FIELD_IS_ARRAY) && field->size == tep->long_size) { /* Handle heterogeneous recording and processing * architectures * * CASE I: * Traces recorded on 32-bit devices (32-bit * addressing) and processed on 64-bit devices: * In this case, only 32 bits should be read. * * CASE II: * Traces recorded on 64 bit devices and processed * on 32-bit devices: * In this case, 64 bits must be read. */ addr = (tep->long_size == 8) ? *(unsigned long long *)(data + field->offset) : (unsigned long long)*(unsigned int *)(data + field->offset); /* Check if it matches a print format */ printk = find_printk(tep, addr); if (printk) trace_seq_puts(s, printk->printk); else trace_seq_printf(s, "%llx", addr); break; } str = malloc(len + 1); if (!str) { do_warning_event(event, "%s: not enough memory!", __func__); return; } memcpy(str, data + field->offset, len); str[len] = 0; print_str_to_seq(s, format, len_arg, str); free(str); break; case TEP_PRINT_FLAGS: val = eval_num_arg(data, size, event, arg->flags.field); print = 0; for (flag = arg->flags.flags; flag; flag = flag->next) { fval = eval_flag(flag->value); if (!val && fval < 0) { print_str_to_seq(s, format, len_arg, flag->str); break; } if (fval > 0 && (val & fval) == fval) { if (print && arg->flags.delim) trace_seq_puts(s, arg->flags.delim); print_str_to_seq(s, format, len_arg, flag->str); print = 1; val &= ~fval; } } if (val) { if (print && arg->flags.delim) trace_seq_puts(s, arg->flags.delim); trace_seq_printf(s, "0x%llx", val); } break; case TEP_PRINT_SYMBOL: val = eval_num_arg(data, size, event, arg->symbol.field); for (flag = arg->symbol.symbols; flag; flag = flag->next) { fval = eval_flag(flag->value); if (val == fval) { print_str_to_seq(s, format, len_arg, flag->str); break; } } if (!flag) trace_seq_printf(s, "0x%llx", val); break; case TEP_PRINT_HEX: case TEP_PRINT_HEX_STR: if (arg->hex.field->type == TEP_PRINT_DYNAMIC_ARRAY) { dynamic_offset_field(tep, arg->hex.field->dynarray.field, data, size, &offset, NULL); hex = data + offset; } else { field = arg->hex.field->field.field; if (!field) { str = arg->hex.field->field.name; field = tep_find_any_field(event, str); if (!field) goto out_warning_field; arg->hex.field->field.field = field; } hex = data + field->offset; } len = eval_num_arg(data, size, event, arg->hex.size); for (i = 0; i < len; i++) { if (i && arg->type == TEP_PRINT_HEX) trace_seq_putc(s, ' '); trace_seq_printf(s, "%02x", hex[i]); } break; case TEP_PRINT_INT_ARRAY: { void *num; int el_size; if (arg->int_array.field->type == TEP_PRINT_DYNAMIC_ARRAY) { dynamic_offset_field(tep, arg->int_array.field->dynarray.field, data, size, &offset, NULL); num = data + offset; } else { field = arg->int_array.field->field.field; if (!field) { str = arg->int_array.field->field.name; field = tep_find_any_field(event, str); if (!field) goto out_warning_field; arg->int_array.field->field.field = field; } num = data + field->offset; } len = eval_num_arg(data, size, event, arg->int_array.count); el_size = eval_num_arg(data, size, event, arg->int_array.el_size); for (i = 0; i < len; i++) { if (i) trace_seq_putc(s, ' '); if (el_size == 1) { trace_seq_printf(s, "%u", *(uint8_t *)num); } else if (el_size == 2) { trace_seq_printf(s, "%u", *(uint16_t *)num); } else if (el_size == 4) { trace_seq_printf(s, "%u", *(uint32_t *)num); } else if (el_size == 8) { trace_seq_printf(s, "%"PRIu64, *(uint64_t *)num); } else { trace_seq_printf(s, "BAD SIZE:%d 0x%x", el_size, *(uint8_t *)num); el_size = 1; } num += el_size; } break; } case TEP_PRINT_TYPE: break; case TEP_PRINT_STRING: { if (!arg->string.field) { arg->string.field = tep_find_any_field(event, arg->string.string); if (!arg->string.field) break; arg->string.offset = arg->string.field->offset; } dynamic_offset_field(tep, arg->string.field, data, size, &offset, &len); /* Do not attempt to save zero length dynamic strings */ if (!len) break; print_str_to_seq(s, format, len_arg, ((char *)data) + offset); break; } case TEP_PRINT_BSTRING: print_str_to_seq(s, format, len_arg, arg->string.string); break; case TEP_PRINT_BITMASK: { if (!arg->bitmask.field) { arg->bitmask.field = tep_find_any_field(event, arg->bitmask.bitmask); if (!arg->bitmask.field) break; arg->bitmask.offset = arg->bitmask.field->offset; } dynamic_offset_field(tep, arg->bitmask.field, data, size, &offset, &len); print_bitmask_to_seq(tep, s, format, len_arg, data + offset, len); break; } case TEP_PRINT_CPUMASK: { if (!arg->bitmask.field) { arg->bitmask.field = tep_find_any_field(event, arg->bitmask.bitmask); arg->bitmask.offset = arg->bitmask.field->offset; } if (!arg->bitmask.field) break; dynamic_offset_field(tep, arg->bitmask.field, data, size, &offset, &len); print_cpumask_to_seq(tep, s, format, len_arg, data + offset, len); break; } case TEP_PRINT_OP: /* * The only op for string should be ? : */ if (arg->op.op[0] != '?') return; val = eval_num_arg(data, size, event, arg->op.left); if (val) print_str_arg(s, data, size, event, format, len_arg, arg->op.right->op.left); else print_str_arg(s, data, size, event, format, len_arg, arg->op.right->op.right); break; case TEP_PRINT_FUNC: process_defined_func(s, data, size, event, arg); break; default: /* well... */ break; } return; out_warning_field: do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); } static unsigned long long process_defined_func(struct trace_seq *s, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { struct tep_function_handler *func_handle = arg->func.func; struct func_params *param; unsigned long long *args; unsigned long long ret; struct tep_print_arg *farg; struct trace_seq str; struct save_str { struct save_str *next; char *str; } *strings = NULL, *string; int i; if (!func_handle->nr_args) { ret = (*func_handle->func)(s, NULL); goto out; } farg = arg->func.args; param = func_handle->params; ret = ULLONG_MAX; args = malloc(sizeof(*args) * func_handle->nr_args); if (!args) goto out; for (i = 0; i < func_handle->nr_args; i++) { switch (param->type) { case TEP_FUNC_ARG_INT: case TEP_FUNC_ARG_LONG: case TEP_FUNC_ARG_PTR: args[i] = eval_num_arg(data, size, event, farg); break; case TEP_FUNC_ARG_STRING: trace_seq_init(&str); print_str_arg(&str, data, size, event, "%s", -1, farg); trace_seq_terminate(&str); string = malloc(sizeof(*string)); if (!string) { do_warning_event(event, "%s(%d): malloc str", __func__, __LINE__); goto out_free; } string->next = strings; string->str = strdup(str.buffer); if (!string->str) { free(string); do_warning_event(event, "%s(%d): malloc str", __func__, __LINE__); goto out_free; } args[i] = (uintptr_t)string->str; strings = string; trace_seq_destroy(&str); break; default: /* * Something went totally wrong, this is not * an input error, something in this code broke. */ do_warning_event(event, "Unexpected end of arguments\n"); goto out_free; } farg = farg->next; param = param->next; } ret = (*func_handle->func)(s, args); out_free: free(args); while (strings) { string = strings; strings = string->next; free(string->str); free(string); } out: /* TBD : handle return type here */ return ret; } static void free_args(struct tep_print_arg *args) { struct tep_print_arg *next; while (args) { next = args->next; free_arg(args); args = next; } } static struct tep_print_arg *make_bprint_args(char *fmt, void *data, int size, struct tep_event *event) { struct tep_handle *tep = event->tep; struct tep_format_field *field, *ip_field; struct tep_print_arg *args, *arg, **next; unsigned long long ip, val; char *ptr; void *bptr; int vsize = 0; field = tep->bprint_buf_field; ip_field = tep->bprint_ip_field; if (!field) { field = tep_find_field(event, "buf"); if (!field) { do_warning_event(event, "can't find buffer field for binary printk"); return NULL; } ip_field = tep_find_field(event, "ip"); if (!ip_field) { do_warning_event(event, "can't find ip field for binary printk"); return NULL; } tep->bprint_buf_field = field; tep->bprint_ip_field = ip_field; } ip = tep_read_number(tep, data + ip_field->offset, ip_field->size); /* * The first arg is the IP pointer. */ args = alloc_arg(); if (!args) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); return NULL; } arg = args; arg->next = NULL; next = &arg->next; arg->type = TEP_PRINT_ATOM; if (asprintf(&arg->atom.atom, "%lld", ip) < 0) goto out_free; /* skip the first "%ps: " */ for (ptr = fmt + 5, bptr = data + field->offset; bptr < data + size && *ptr; ptr++) { int ls = 0; if (*ptr == '%') { process_again: ptr++; switch (*ptr) { case '%': break; case 'l': ls++; goto process_again; case 'L': ls = 2; goto process_again; case '0' ... '9': goto process_again; case '.': goto process_again; case '#': goto process_again; case 'z': case 'Z': ls = 1; goto process_again; case 'p': ls = 1; if (isalnum(ptr[1])) { ptr++; /* Check for special pointers */ switch (*ptr) { case 's': case 'S': case 'x': break; case 'f': case 'F': /* * Pre-5.5 kernels use %pf and * %pF for printing symbols * while kernels since 5.5 use * %pfw for fwnodes. So check * %p[fF] isn't followed by 'w'. */ if (ptr[1] != 'w') break; /* fall through */ default: /* * Older kernels do not process * dereferenced pointers. * Only process if the pointer * value is a printable. */ if (isprint(*(char *)bptr)) goto process_string; } } /* fall through */ case 'd': case 'u': case 'i': case 'x': case 'X': case 'o': switch (ls) { case 0: vsize = 4; break; case 1: vsize = tep->long_size; break; case 2: vsize = 8; break; default: vsize = ls; /* ? */ break; } /* fall through */ case '*': if (*ptr == '*') vsize = 4; /* the pointers are always 4 bytes aligned */ bptr = (void *)(((unsigned long)bptr + 3) & ~3); val = tep_read_number(tep, bptr, vsize); bptr += vsize; arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); goto out_free; } arg->next = NULL; arg->type = TEP_PRINT_ATOM; if (asprintf(&arg->atom.atom, "%lld", val) < 0) { free(arg); goto out_free; } *next = arg; next = &arg->next; /* * The '*' case means that an arg is used as the length. * We need to continue to figure out for what. */ if (*ptr == '*') goto process_again; break; case 's': process_string: arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); goto out_free; } arg->next = NULL; arg->type = TEP_PRINT_BSTRING; arg->string.string = strdup(bptr); if (!arg->string.string) { free(arg); goto out_free; } bptr += strlen(bptr) + 1; *next = arg; next = &arg->next; default: break; } } } return args; out_free: free_args(args); return NULL; } static char * get_bprint_format(void *data, int size __maybe_unused, struct tep_event *event) { struct tep_handle *tep = event->tep; unsigned long long addr; struct tep_format_field *field; struct printk_map *printk; char *format; field = tep->bprint_fmt_field; if (!field) { field = tep_find_field(event, "fmt"); if (!field) { do_warning_event(event, "can't find format field for binary printk"); return NULL; } tep->bprint_fmt_field = field; } addr = tep_read_number(tep, data + field->offset, field->size); printk = find_printk(tep, addr); if (!printk) { if (asprintf(&format, "%%ps: (NO FORMAT FOUND at %llx)\n", addr) < 0) return NULL; return format; } if (asprintf(&format, "%s: %s", "%ps", printk->printk) < 0) return NULL; return format; } static int print_mac_arg(struct trace_seq *s, const char *format, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x"; bool reverse = false; unsigned char *buf; int ret = 0; if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return 0; } /* evaluate if the arg has a type cast */ while (arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (arg->type != TEP_PRINT_FIELD) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return 0; } if (format[0] == 'm') { fmt = "%.2x%.2x%.2x%.2x%.2x%.2x"; } else if (format[0] == 'M' && format[1] == 'F') { fmt = "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"; ret++; } if (format[1] == 'R') { reverse = true; ret++; } if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); return ret; } } if (arg->field.field->size != 6) { trace_seq_printf(s, "INVALIDMAC"); return ret; } buf = data + arg->field.field->offset; if (reverse) trace_seq_printf(s, fmt, buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]); else trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); return ret; } static int parse_ip4_print_args(struct tep_handle *tep, const char *ptr, bool *reverse) { int ret = 0; *reverse = false; /* hnbl */ switch (*ptr) { case 'h': if (tep->file_bigendian) *reverse = false; else *reverse = true; ret++; break; case 'l': *reverse = true; ret++; break; case 'n': case 'b': ret++; /* fall through */ default: *reverse = false; break; } return ret; } static void print_ip4_addr(struct trace_seq *s, char i, bool reverse, unsigned char *buf) { const char *fmt; if (i == 'i') fmt = "%03d.%03d.%03d.%03d"; else fmt = "%d.%d.%d.%d"; if (reverse) trace_seq_printf(s, fmt, buf[3], buf[2], buf[1], buf[0]); else trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3]); } static inline bool ipv6_addr_v4mapped(const struct in6_addr *a) { return ((unsigned long)(a->s6_addr32[0] | a->s6_addr32[1]) | (unsigned long)(a->s6_addr32[2] ^ htonl(0x0000ffff))) == 0UL; } static inline bool ipv6_addr_is_isatap(const struct in6_addr *addr) { return (addr->s6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE); } static void print_ip6c_addr(struct trace_seq *s, unsigned char *addr) { int i, j, range; unsigned char zerolength[8]; int longest = 1; int colonpos = -1; uint16_t word; uint8_t hi, lo; bool needcolon = false; bool useIPv4; struct in6_addr in6; memcpy(&in6, addr, sizeof(struct in6_addr)); useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6); memset(zerolength, 0, sizeof(zerolength)); if (useIPv4) range = 6; else range = 8; /* find position of longest 0 run */ for (i = 0; i < range; i++) { for (j = i; j < range; j++) { if (in6.s6_addr16[j] != 0) break; zerolength[i]++; } } for (i = 0; i < range; i++) { if (zerolength[i] > longest) { longest = zerolength[i]; colonpos = i; } } if (longest == 1) /* don't compress a single 0 */ colonpos = -1; /* emit address */ for (i = 0; i < range; i++) { if (i == colonpos) { if (needcolon || i == 0) trace_seq_printf(s, ":"); trace_seq_printf(s, ":"); needcolon = false; i += longest - 1; continue; } if (needcolon) { trace_seq_printf(s, ":"); needcolon = false; } /* hex u16 without leading 0s */ word = ntohs(in6.s6_addr16[i]); hi = word >> 8; lo = word & 0xff; if (hi) trace_seq_printf(s, "%x%02x", hi, lo); else trace_seq_printf(s, "%x", lo); needcolon = true; } if (useIPv4) { if (needcolon) trace_seq_printf(s, ":"); print_ip4_addr(s, 'I', false, &in6.s6_addr[12]); } return; } static void print_ip6_addr(struct trace_seq *s, char i, unsigned char *buf) { int j; for (j = 0; j < 16; j += 2) { trace_seq_printf(s, "%02x%02x", buf[j], buf[j+1]); if (i == 'I' && j < 14) trace_seq_printf(s, ":"); } } /* * %pi4 print an IPv4 address with leading zeros * %pI4 print an IPv4 address without leading zeros * %pi6 print an IPv6 address without colons * %pI6 print an IPv6 address with colons * %pI6c print an IPv6 address in compressed form with colons * %pISpc print an IP address based on sockaddr; p adds port. */ static int print_ipv4_arg(struct trace_seq *s, const char *ptr, char i, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { bool reverse = false; unsigned char *buf; int ret; ret = parse_ip4_print_args(event->tep, ptr, &reverse); if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return ret; } /* evaluate if the arg has a type cast */ while (arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (arg->type != TEP_PRINT_FIELD) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return ret; } if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning("%s: field %s not found", __func__, arg->field.name); return ret; } } buf = data + arg->field.field->offset; if (arg->field.field->size != 4) { trace_seq_printf(s, "INVALIDIPv4"); return ret; } print_ip4_addr(s, i, reverse, buf); return ret; } static int print_ipv6_arg(struct trace_seq *s, const char *ptr, char i, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { char have_c = 0; unsigned char *buf; int rc = 0; /* pI6c */ if (i == 'I' && *ptr == 'c') { have_c = 1; ptr++; rc++; } if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return rc; } /* evaluate if the arg has a type cast */ while (arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (arg->type != TEP_PRINT_FIELD) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return rc; } if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning("%s: field %s not found", __func__, arg->field.name); return rc; } } buf = data + arg->field.field->offset; if (arg->field.field->size != 16) { trace_seq_printf(s, "INVALIDIPv6"); return rc; } if (have_c) print_ip6c_addr(s, buf); else print_ip6_addr(s, i, buf); return rc; } static int print_ipsa_arg(struct trace_seq *s, const char *ptr, char i, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { char have_c = 0, have_p = 0; unsigned char *buf; struct sockaddr_storage *sa; bool reverse = false; unsigned int offset; unsigned int len; int rc = 0; int ret; /* pISpc */ if (i == 'I') { if (*ptr == 'p') { have_p = 1; ptr++; rc++; } if (*ptr == 'c') { have_c = 1; ptr++; rc++; } } ret = parse_ip4_print_args(event->tep, ptr, &reverse); ptr += ret; rc += ret; if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return rc; } /* evaluate if the arg has a type cast */ while (arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (arg->type == TEP_PRINT_FIELD) { if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning("%s: field %s not found", __func__, arg->field.name); return rc; } } offset = arg->field.field->offset; len = arg->field.field->size; } else if (arg->type == TEP_PRINT_DYNAMIC_ARRAY) { dynamic_offset_field(event->tep, arg->dynarray.field, data, size, &offset, &len); } else { trace_seq_printf(s, "ARG NOT FIELD NOR DYNAMIC ARRAY BUT TYPE %d", arg->type); return rc; } sa = (struct sockaddr_storage *)(data + offset); if (sa->ss_family == AF_INET) { struct sockaddr_in *sa4 = (struct sockaddr_in *) sa; if (len < sizeof(struct sockaddr_in)) { trace_seq_printf(s, "INVALIDIPv4"); return rc; } print_ip4_addr(s, i, reverse, (unsigned char *) &sa4->sin_addr); if (have_p) trace_seq_printf(s, ":%d", ntohs(sa4->sin_port)); } else if (sa->ss_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) sa; if (len < sizeof(struct sockaddr_in6)) { trace_seq_printf(s, "INVALIDIPv6"); return rc; } if (have_p) trace_seq_printf(s, "["); buf = (unsigned char *) &sa6->sin6_addr; if (have_c) print_ip6c_addr(s, buf); else print_ip6_addr(s, i, buf); if (have_p) trace_seq_printf(s, "]:%d", ntohs(sa6->sin6_port)); } return rc; } static int print_ip_arg(struct trace_seq *s, const char *ptr, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { char i = *ptr; /* 'i' or 'I' */ int rc = 1; /* IP version */ ptr++; switch (*ptr) { case '4': rc += print_ipv4_arg(s, ptr + 1, i, data, size, event, arg); break; case '6': rc += print_ipv6_arg(s, ptr + 1, i, data, size, event, arg); break; case 'S': rc += print_ipsa_arg(s, ptr + 1, i, data, size, event, arg); break; default: return 0; } return rc; } static const int guid_index[16] = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15}; static const int uuid_index[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; static int print_uuid_arg(struct trace_seq *s, const char *ptr, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { const int *index = uuid_index; char *format = "%02x"; int ret = 0; char *buf; int i; switch (*(ptr + 1)) { case 'L': format = "%02X"; /* fall through */ case 'l': index = guid_index; ret++; break; case 'B': format = "%02X"; /* fall through */ case 'b': ret++; break; } if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return ret; } /* evaluate if the arg has a type cast */ while (arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (arg->type != TEP_PRINT_FIELD) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return ret; } if (!arg->field.field) { arg->field.field = tep_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning("%s: field %s not found", __func__, arg->field.name); return ret; } } if (arg->field.field->size != 16) { trace_seq_printf(s, "INVALIDUUID"); return ret; } buf = data + arg->field.field->offset; for (i = 0; i < 16; i++) { trace_seq_printf(s, format, buf[index[i]] & 0xff); switch (i) { case 3: case 5: case 7: case 9: trace_seq_printf(s, "-"); break; } } return ret; } static int print_raw_buff_arg(struct trace_seq *s, const char *ptr, void *data, int size, struct tep_event *event, struct tep_print_arg *arg, int print_len) { unsigned int offset, arr_len; int plen = print_len; char *delim = " "; int ret = 0; char *buf; int i; switch (*(ptr + 1)) { case 'C': delim = ":"; ret++; break; case 'D': delim = "-"; ret++; break; case 'N': delim = ""; ret++; break; } if (arg->type == TEP_PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return ret; } if (arg->type != TEP_PRINT_DYNAMIC_ARRAY) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return ret; } dynamic_offset_field(event->tep, arg->dynarray.field, data, size, &offset, &arr_len); buf = data + offset; if (arr_len < plen) plen = arr_len; if (plen < 1) return ret; trace_seq_printf(s, "%02x", buf[0] & 0xff); for (i = 1; i < plen; i++) trace_seq_printf(s, "%s%02x", delim, buf[i] & 0xff); return ret; } static int is_printable_array(char *p, unsigned int len) { unsigned int i; for (i = 0; i < len && p[i]; i++) if (!isprint(p[i]) && !isspace(p[i])) return 0; return 1; } static void print_field_raw(struct trace_seq *s, void *data, int size, struct tep_format_field *field) { struct tep_handle *tep = field->event->tep; unsigned int offset, len, i; unsigned long long val; if (field->flags & TEP_FIELD_IS_ARRAY) { if (field->flags & TEP_FIELD_IS_DYNAMIC) { dynamic_offset_field(tep, field, data, size, &offset, &len); } else { offset = field->offset; len = field->size; } if (field->flags & TEP_FIELD_IS_STRING && is_printable_array(data + offset, len)) { trace_seq_printf(s, "%s", (char *)data + offset); } else { trace_seq_puts(s, "ARRAY["); for (i = 0; i < len; i++) { if (i) trace_seq_puts(s, ", "); trace_seq_printf(s, "%02x", *((unsigned char *)data + offset + i)); } trace_seq_putc(s, ']'); field->flags &= ~TEP_FIELD_IS_STRING; } } else { val = tep_read_number(tep, data + field->offset, field->size); if (field->flags & TEP_FIELD_IS_POINTER) { trace_seq_printf(s, "0x%llx", val); } else if (field->flags & TEP_FIELD_IS_SIGNED) { switch (field->size) { case 4: /* * If field is long then print it in hex. * A long usually stores pointers. */ if (field->flags & TEP_FIELD_IS_LONG) trace_seq_printf(s, "0x%x", (int)val); else trace_seq_printf(s, "%d", (int)val); break; case 2: trace_seq_printf(s, "%2d", (short)val); break; case 1: trace_seq_printf(s, "%1d", (char)val); break; default: trace_seq_printf(s, "%lld", val); } } else { if (field->flags & TEP_FIELD_IS_LONG) trace_seq_printf(s, "0x%llx", val); else trace_seq_printf(s, "%llu", val); } } trace_seq_terminate(s); } static int print_parse_data(struct tep_print_parse *parse, struct trace_seq *s, void *data, int size, struct tep_event *event); static inline void print_field(struct trace_seq *s, void *data, int size, struct tep_format_field *field, struct tep_print_parse **parse_ptr) { struct tep_event *event = field->event; struct tep_print_parse *start_parse; struct tep_print_parse *parse; struct tep_print_arg *arg; bool has_0x = false; parse = parse_ptr ? *parse_ptr : event->print_fmt.print_cache; if (!parse || event->flags & TEP_EVENT_FL_FAILED) goto out; if (field->flags & (TEP_FIELD_IS_ARRAY | TEP_FIELD_IS_STRING)) goto out; start_parse = parse; do { if (parse->type == PRINT_FMT_STRING) { int len = strlen(parse->format); if (len > 1 && strcmp(parse->format + (len -2), "0x") == 0) has_0x = true; else has_0x = false; goto next; } arg = parse->arg; while (arg && arg->type == TEP_PRINT_TYPE) arg = arg->typecast.item; if (!arg || arg->type != TEP_PRINT_FIELD || arg->field.field != field) { has_0x = false; goto next; } if (has_0x) trace_seq_puts(s, "0x"); print_parse_data(parse, s, data, size, event); if (parse_ptr) *parse_ptr = parse->next; return; next: parse = parse->next ? parse->next : event->print_fmt.print_cache; } while (parse != start_parse); out: /* Not found. */ print_field_raw(s, data, size, field); } /** * tep_print_field_content - write out the raw content of a field * @s: The trace_seq to write the content into * @data: The payload to extract the field from. * @size: The size of the payload. * @field: The field to extract * * Use @field to find the field content from within @data and write it * in human readable format into @s. * * It will not write anything on error (s->len will not move) */ void tep_print_field_content(struct trace_seq *s, void *data, int size, struct tep_format_field *field) { print_field(s, data, size, field, NULL); } /** DEPRECATED **/ void tep_print_field(struct trace_seq *s, void *data, struct tep_format_field *field) { /* unsafe to use, should pass in size */ print_field(s, data, 4096, field, NULL); } static inline void print_selected_fields(struct trace_seq *s, void *data, int size, struct tep_event *event, unsigned long long ignore_mask) { struct tep_print_parse *parse = event->print_fmt.print_cache; struct tep_format_field *field; unsigned long long field_mask = 1; field = event->format.fields; for(; field; field = field->next, field_mask <<= 1) { if (field_mask & ignore_mask) continue; trace_seq_printf(s, " %s=", field->name); print_field(s, data, size, field, &parse); } } void tep_print_fields(struct trace_seq *s, void *data, int size, struct tep_event *event) { print_selected_fields(s, data, size, event, 0); } /** * tep_record_print_fields - print the field name followed by the * record's field value. * @s: The seq to print to * @record: The record to get the event from * @event: The event that the field is for */ void tep_record_print_fields(struct trace_seq *s, struct tep_record *record, struct tep_event *event) { print_selected_fields(s, record->data, record->size, event, 0); } /** * tep_record_print_selected_fields - print the field name followed by the * record's field value for a selected subset of record fields. * @s: The seq to print to * @record: The record to get the event from * @event: The event that the field is for * @select_mask: Bit mask defining the fields to print */ void tep_record_print_selected_fields(struct trace_seq *s, struct tep_record *record, struct tep_event *event, unsigned long long select_mask) { unsigned long long ignore_mask = ~select_mask; print_selected_fields(s, record->data, record->size, event, ignore_mask); } static int print_function(struct trace_seq *s, const char *format, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { struct func_map *func; unsigned long long val; val = eval_num_arg(data, size, event, arg); func = find_func(event->tep, val); if (func) { trace_seq_puts(s, func->func); if (*format == 'F' || *format == 'S') trace_seq_printf(s, "+0x%llx", val - func->addr); } else { if (event->tep->long_size == 4) trace_seq_printf(s, "0x%lx", (long)val); else trace_seq_printf(s, "0x%llx", (long long)val); } return 0; } static int print_arg_pointer(struct trace_seq *s, const char *format, int plen, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { unsigned long long val; int ret = 1; if (arg->type == TEP_PRINT_BSTRING) { trace_seq_puts(s, arg->string.string); return 0; } while (*format) { if (*format == 'p') { format++; break; } format++; } switch (*format) { case 'F': case 'f': case 'S': case 's': ret += print_function(s, format, data, size, event, arg); break; case 'M': case 'm': ret += print_mac_arg(s, format, data, size, event, arg); break; case 'I': case 'i': ret += print_ip_arg(s, format, data, size, event, arg); break; case 'U': ret += print_uuid_arg(s, format, data, size, event, arg); break; case 'h': ret += print_raw_buff_arg(s, format, data, size, event, arg, plen); break; default: ret = 0; val = eval_num_arg(data, size, event, arg); trace_seq_printf(s, "%p", (void *)(intptr_t)val); break; } return ret; } static int print_arg_number(struct trace_seq *s, const char *format, int plen, void *data, int size, int ls, struct tep_event *event, struct tep_print_arg *arg) { unsigned long long val; val = eval_num_arg(data, size, event, arg); switch (ls) { case -2: if (plen >= 0) trace_seq_printf(s, format, plen, (char)val); else trace_seq_printf(s, format, (char)val); break; case -1: if (plen >= 0) trace_seq_printf(s, format, plen, (short)val); else trace_seq_printf(s, format, (short)val); break; case 0: if (plen >= 0) trace_seq_printf(s, format, plen, (int)val); else trace_seq_printf(s, format, (int)val); break; case 1: if (plen >= 0) trace_seq_printf(s, format, plen, (long)val); else trace_seq_printf(s, format, (long)val); break; case 2: if (plen >= 0) trace_seq_printf(s, format, plen, (long long)val); else trace_seq_printf(s, format, (long long)val); break; default: do_warning_event(event, "bad count (%d)", ls); event->flags |= TEP_EVENT_FL_FAILED; } return 0; } static void print_arg_string(struct trace_seq *s, const char *format, int plen, void *data, int size, struct tep_event *event, struct tep_print_arg *arg) { struct trace_seq p; /* Use helper trace_seq */ trace_seq_init(&p); print_str_arg(&p, data, size, event, format, plen, arg); trace_seq_terminate(&p); trace_seq_puts(s, p.buffer); trace_seq_destroy(&p); } static int parse_arg_format_pointer(const char *format) { int ret = 0; int index; int loop; switch (*format) { case 'F': case 'S': case 'f': case 's': ret++; break; case 'M': case 'm': /* [mM]R , [mM]F */ switch (format[1]) { case 'R': case 'F': ret++; break; } ret++; break; case 'I': case 'i': index = 2; loop = 1; switch (format[1]) { case 'S': /*[S][pfs]*/ while (loop) { switch (format[index]) { case 'p': case 'f': case 's': ret++; index++; break; default: loop = 0; break; } } /* fall through */ case '4': /* [4S][hnbl] */ switch (format[index]) { case 'h': case 'n': case 'l': case 'b': ret++; index++; break; } if (format[1] == '4') { ret++; break; } /* fall through */ case '6': /* [6S]c */ if (format[index] == 'c') ret++; ret++; break; } ret++; break; case 'U': switch (format[1]) { case 'L': case 'l': case 'B': case 'b': ret++; break; } ret++; break; case 'h': switch (format[1]) { case 'C': case 'D': case 'N': ret++; break; } ret++; break; default: break; } return ret; } static void free_parse_args(struct tep_print_parse *arg) { struct tep_print_parse *del; while (arg) { del = arg; arg = del->next; free(del->format); free(del); } } static int parse_arg_add(struct tep_print_parse **parse, char *format, enum tep_print_parse_type type, struct tep_print_arg *arg, struct tep_print_arg *len_as_arg, int ls) { struct tep_print_parse *parg = NULL; parg = calloc(1, sizeof(*parg)); if (!parg) goto error; parg->format = strdup(format); if (!parg->format) goto error; parg->type = type; parg->arg = arg; parg->len_as_arg = len_as_arg; parg->ls = ls; *parse = parg; return 0; error: if (parg) { free(parg->format); free(parg); } return -1; } static int parse_arg_format(struct tep_print_parse **parse, struct tep_event *event, const char *format, struct tep_print_arg **arg) { struct tep_print_arg *len_arg = NULL; char print_format[32]; const char *start = format; int ret = 0; int ls = 0; int res; int len; format++; ret++; for (; *format; format++) { switch (*format) { case '#': /* FIXME: need to handle properly */ break; case 'h': ls--; break; case 'l': ls++; break; case 'L': ls = 2; break; case 'z': case 'Z': ls = 1; break; case '.': case '0' ... '9': case '-': break; case '*': /* The argument is the length. */ if (!*arg) { do_warning_event(event, "no argument match"); event->flags |= TEP_EVENT_FL_FAILED; goto out_failed; } if (len_arg) { do_warning_event(event, "argument already matched"); event->flags |= TEP_EVENT_FL_FAILED; goto out_failed; } len_arg = *arg; *arg = (*arg)->next; break; case 'p': if (!*arg) { do_warning_event(event, "no argument match"); event->flags |= TEP_EVENT_FL_FAILED; goto out_failed; } res = parse_arg_format_pointer(format + 1); if (res > 0) { format += res; ret += res; } len = ((unsigned long)format + 1) - (unsigned long)start; /* should never happen */ if (len > 31) { do_warning_event(event, "bad format!"); event->flags |= TEP_EVENT_FL_FAILED; len = 31; } memcpy(print_format, start, len); print_format[len] = 0; parse_arg_add(parse, print_format, PRINT_FMT_ARG_POINTER, *arg, len_arg, ls); *arg = (*arg)->next; ret++; return ret; case 'd': case 'u': case 'i': case 'x': case 'X': case 'o': if (!*arg) { do_warning_event(event, "no argument match"); event->flags |= TEP_EVENT_FL_FAILED; goto out_failed; } len = ((unsigned long)format + 1) - (unsigned long)start; /* should never happen */ if (len > 30) { do_warning_event(event, "bad format!"); event->flags |= TEP_EVENT_FL_FAILED; len = 31; } memcpy(print_format, start, len); print_format[len] = 0; if (event->tep->long_size == 8 && ls == 1 && sizeof(long) != 8) { char *p; /* make %l into %ll */ if (ls == 1 && (p = strchr(print_format, 'l'))) memmove(p+1, p, strlen(p)+1); ls = 2; } if (ls < -2 || ls > 2) { do_warning_event(event, "bad count (%d)", ls); event->flags |= TEP_EVENT_FL_FAILED; } parse_arg_add(parse, print_format, PRINT_FMT_ARG_DIGIT, *arg, len_arg, ls); *arg = (*arg)->next; ret++; return ret; case 's': if (!*arg) { do_warning_event(event, "no matching argument"); event->flags |= TEP_EVENT_FL_FAILED; goto out_failed; } len = ((unsigned long)format + 1) - (unsigned long)start; /* should never happen */ if (len > 31) { do_warning_event(event, "bad format!"); event->flags |= TEP_EVENT_FL_FAILED; len = 31; } memcpy(print_format, start, len); print_format[len] = 0; parse_arg_add(parse, print_format, PRINT_FMT_ARG_STRING, *arg, len_arg, 0); *arg = (*arg)->next; ret++; return ret; default: snprintf(print_format, 32, ">%c<", *format); parse_arg_add(parse, print_format, PRINT_FMT_STRING, NULL, NULL, 0); ret++; return ret; } ret++; } out_failed: return ret; } static int parse_arg_string(struct tep_print_parse **parse, const char *format) { struct trace_seq s; int ret = 0; trace_seq_init(&s); for (; *format; format++) { if (*format == '\\') { format++; ret++; switch (*format) { case 'n': trace_seq_putc(&s, '\n'); break; case 't': trace_seq_putc(&s, '\t'); break; case 'r': trace_seq_putc(&s, '\r'); break; case '\\': trace_seq_putc(&s, '\\'); break; default: trace_seq_putc(&s, *format); break; } } else if (*format == '%') { if (*(format + 1) == '%') { trace_seq_putc(&s, '%'); format++; ret++; } else break; } else trace_seq_putc(&s, *format); ret++; } trace_seq_terminate(&s); parse_arg_add(parse, s.buffer, PRINT_FMT_STRING, NULL, NULL, 0); trace_seq_destroy(&s); return ret; } static struct tep_print_parse * parse_args(struct tep_event *event, const char *format, struct tep_print_arg *arg) { struct tep_print_parse *parse_ret = NULL; struct tep_print_parse **parse = NULL; int ret; int len; len = strlen(format); while (*format) { if (!parse_ret) parse = &parse_ret; if (*format == '%' && *(format + 1) != '%') ret = parse_arg_format(parse, event, format, &arg); else ret = parse_arg_string(parse, format); if (*parse) parse = &((*parse)->next); len -= ret; if (len > 0) format += ret; else break; } return parse_ret; } static int print_parse_data(struct tep_print_parse *parse, struct trace_seq *s, void *data, int size, struct tep_event *event) { int len_arg; if (parse->len_as_arg) len_arg = eval_num_arg(data, size, event, parse->len_as_arg); switch (parse->type) { case PRINT_FMT_ARG_DIGIT: print_arg_number(s, parse->format, parse->len_as_arg ? len_arg : -1, data, size, parse->ls, event, parse->arg); break; case PRINT_FMT_ARG_POINTER: print_arg_pointer(s, parse->format, parse->len_as_arg ? len_arg : 1, data, size, event, parse->arg); break; case PRINT_FMT_ARG_STRING: print_arg_string(s, parse->format, parse->len_as_arg ? len_arg : -1, data, size, event, parse->arg); break; case PRINT_FMT_STRING: default: trace_seq_printf(s, "%s", parse->format); /* Return 1 on non field. */ return 1; } /* Return 0 on field being processed. */ return 0; } static void print_event_cache(struct tep_print_parse *parse, struct trace_seq *s, void *data, int size, struct tep_event *event) { while (parse) { print_parse_data(parse, s, data, size, event); parse = parse->next; } } static void pretty_print(struct trace_seq *s, void *data, int size, struct tep_event *event) { struct tep_print_parse *parse = event->print_fmt.print_cache; struct tep_print_arg *args = NULL; char *bprint_fmt = NULL; if (event->flags & TEP_EVENT_FL_FAILED) { trace_seq_printf(s, "[FAILED TO PARSE]"); tep_print_fields(s, data, size, event); return; } if (event->flags & TEP_EVENT_FL_ISBPRINT) { bprint_fmt = get_bprint_format(data, size, event); args = make_bprint_args(bprint_fmt, data, size, event); parse = parse_args(event, bprint_fmt, args); } print_event_cache(parse, s, data, size, event); if (event->flags & TEP_EVENT_FL_ISBPRINT) { free_parse_args(parse); free_args(args); free(bprint_fmt); } } /* * This parses out the Latency format (interrupts disabled, * need rescheduling, in hard/soft interrupt, preempt count * and lock depth) and places it into the trace_seq. */ static void data_latency_format(struct tep_handle *tep, struct trace_seq *s, char *format, struct tep_record *record) { static int check_lock_depth = 1; static int check_migrate_disable = 1; static int lock_depth_exists; static int migrate_disable_exists; unsigned int lat_flags; struct trace_seq sq; unsigned int pc; int lock_depth = 0; int migrate_disable = 0; int hardirq; int softirq; void *data = record->data; trace_seq_init(&sq); lat_flags = parse_common_flags(tep, data); pc = parse_common_pc(tep, data); /* lock_depth may not always exist */ if (lock_depth_exists) lock_depth = parse_common_lock_depth(tep, data); else if (check_lock_depth) { lock_depth = parse_common_lock_depth(tep, data); if (lock_depth < 0) check_lock_depth = 0; else lock_depth_exists = 1; } /* migrate_disable may not always exist */ if (migrate_disable_exists) migrate_disable = parse_common_migrate_disable(tep, data); else if (check_migrate_disable) { migrate_disable = parse_common_migrate_disable(tep, data); if (migrate_disable < 0) check_migrate_disable = 0; else migrate_disable_exists = 1; } hardirq = lat_flags & TRACE_FLAG_HARDIRQ; softirq = lat_flags & TRACE_FLAG_SOFTIRQ; trace_seq_printf(&sq, "%c%c%c", (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' : (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ? 'X' : '.', (lat_flags & TRACE_FLAG_NEED_RESCHED) ? 'N' : '.', (hardirq && softirq) ? 'H' : hardirq ? 'h' : softirq ? 's' : '.'); if (pc & 0xf) trace_seq_printf(&sq, "%x", pc & 0xf); else trace_seq_printf(&sq, "."); if (pc & 0xf0) trace_seq_printf(&sq, "%x", pc >> 4); else trace_seq_printf(&sq, "."); if (migrate_disable_exists) { if (migrate_disable < 0) trace_seq_printf(&sq, "."); else trace_seq_printf(&sq, "%d", migrate_disable); } if (lock_depth_exists) { if (lock_depth < 0) trace_seq_printf(&sq, "."); else trace_seq_printf(&sq, "%d", lock_depth); } if (sq.state == TRACE_SEQ__MEM_ALLOC_FAILED) { s->state = TRACE_SEQ__MEM_ALLOC_FAILED; return; } trace_seq_terminate(&sq); trace_seq_puts(s, sq.buffer); trace_seq_destroy(&sq); trace_seq_terminate(s); } /** * tep_data_type - parse out the given event type * @tep: a handle to the trace event parser context * @rec: the record to read from * * This returns the event id from the @rec. */ int tep_data_type(struct tep_handle *tep, struct tep_record *rec) { return trace_parse_common_type(tep, rec->data); } /** * tep_data_pid - parse the PID from record * @tep: a handle to the trace event parser context * @rec: the record to parse * * This returns the PID from a record. */ int tep_data_pid(struct tep_handle *tep, struct tep_record *rec) { return parse_common_pid(tep, rec->data); } /** * tep_data_preempt_count - parse the preempt count from the record * @tep: a handle to the trace event parser context * @rec: the record to parse * * This returns the preempt count from a record. */ int tep_data_preempt_count(struct tep_handle *tep, struct tep_record *rec) { return parse_common_pc(tep, rec->data); } /** * tep_data_flags - parse the latency flags from the record * @tep: a handle to the trace event parser context * @rec: the record to parse * * This returns the latency flags from a record. * * Use trace_flag_type enum for the flags (see event-parse.h). */ int tep_data_flags(struct tep_handle *tep, struct tep_record *rec) { return parse_common_flags(tep, rec->data); } /** * tep_data_comm_from_pid - return the command line from PID * @tep: a handle to the trace event parser context * @pid: the PID of the task to search for * * This returns a pointer to the command line that has the given * @pid. */ const char *tep_data_comm_from_pid(struct tep_handle *tep, int pid) { const char *comm; comm = find_cmdline(tep, pid); return comm; } static struct tep_cmdline * pid_from_cmdlist(struct tep_handle *tep, const char *comm, struct tep_cmdline *next) { struct cmdline_list *cmdlist = (struct cmdline_list *)next; if (cmdlist) cmdlist = cmdlist->next; else cmdlist = tep->cmdlist; while (cmdlist && strcmp(cmdlist->comm, comm) != 0) cmdlist = cmdlist->next; return (struct tep_cmdline *)cmdlist; } /** * tep_data_pid_from_comm - return the pid from a given comm * @tep: a handle to the trace event parser context * @comm: the cmdline to find the pid from * @next: the cmdline structure to find the next comm * * This returns the cmdline structure that holds a pid for a given * comm, or NULL if none found. As there may be more than one pid for * a given comm, the result of this call can be passed back into * a recurring call in the @next parameter, and then it will find the * next pid. * Also, it does a linear search, so it may be slow. */ struct tep_cmdline *tep_data_pid_from_comm(struct tep_handle *tep, const char *comm, struct tep_cmdline *next) { struct tep_cmdline *cmdline; /* * If the cmdlines have not been converted yet, then use * the list. */ if (!tep->cmdlines) return pid_from_cmdlist(tep, comm, next); if (next) { /* * The next pointer could have been still from * a previous call before cmdlines were created */ if (next < tep->cmdlines || next >= tep->cmdlines + tep->cmdline_count) next = NULL; else cmdline = next++; } if (!next) cmdline = tep->cmdlines; while (cmdline < tep->cmdlines + tep->cmdline_count) { if (strcmp(cmdline->comm, comm) == 0) return cmdline; cmdline++; } return NULL; } /** * tep_cmdline_pid - return the pid associated to a given cmdline * @tep: a handle to the trace event parser context * @cmdline: The cmdline structure to get the pid from * * Returns the pid for a give cmdline. If @cmdline is NULL, then * -1 is returned. */ int tep_cmdline_pid(struct tep_handle *tep, struct tep_cmdline *cmdline) { struct cmdline_list *cmdlist = (struct cmdline_list *)cmdline; if (!cmdline) return -1; /* * If cmdlines have not been created yet, or cmdline is * not part of the array, then treat it as a cmdlist instead. */ if (!tep->cmdlines || cmdline < tep->cmdlines || cmdline >= tep->cmdlines + tep->cmdline_count) return cmdlist->pid; return cmdline->pid; } /* * This parses the raw @data using the given @event information and * writes the print format into the trace_seq. */ static void print_event_info(struct trace_seq *s, char *format, bool raw, struct tep_event *event, struct tep_record *record) { int print_pretty = 1; if (raw || (event->flags & TEP_EVENT_FL_PRINTRAW)) tep_print_fields(s, record->data, record->size, event); else { if (event->handler && !(event->flags & TEP_EVENT_FL_NOHANDLE)) print_pretty = event->handler(s, record, event, event->context); if (print_pretty) pretty_print(s, record->data, record->size, event); } trace_seq_terminate(s); } /** * tep_find_event_by_record - return the event from a given record * @tep: a handle to the trace event parser context * @record: The record to get the event from * * Returns the associated event for a given record, or NULL if non is * is found. */ struct tep_event * tep_find_event_by_record(struct tep_handle *tep, struct tep_record *record) { int type; if (record->size < 0) { do_warning("ug! negative record size %d", record->size); return NULL; } type = trace_parse_common_type(tep, record->data); return tep_find_event(tep, type); } /* * Writes the timestamp of the record into @s. Time divisor and precision can be * specified as part of printf @format string. Example: * "%3.1000d" - divide the time by 1000 and print the first 3 digits * before the dot. Thus, the timestamp "123456000" will be printed as * "123.456" */ static void print_event_time(struct tep_handle *tep, struct trace_seq *s, char *format, struct tep_event *event, struct tep_record *record) { unsigned long long time; char *divstr; int prec = 0, pr; int div = 0; int p10 = 1; if (isdigit(*(format + 1))) prec = atoi(format + 1); divstr = strchr(format, '.'); if (divstr && isdigit(*(divstr + 1))) div = atoi(divstr + 1); time = record->ts; if (div) { time += div / 2; time /= div; } pr = prec; while (pr--) p10 *= 10; if (p10 > 1) trace_seq_printf(s, "%5llu.%0*llu", time / p10, prec, time % p10); else trace_seq_printf(s, "%12llu", time); } struct print_event_type { enum { EVENT_TYPE_INT = 1, EVENT_TYPE_STRING, EVENT_TYPE_UNKNOWN, } type; char format[32]; }; static void print_string(struct tep_handle *tep, struct trace_seq *s, struct tep_record *record, struct tep_event *event, const char *arg, struct print_event_type *type) { const char *comm; int pid; if (strncmp(arg, TEP_PRINT_LATENCY, strlen(TEP_PRINT_LATENCY)) == 0) { data_latency_format(tep, s, type->format, record); } else if (strncmp(arg, TEP_PRINT_COMM, strlen(TEP_PRINT_COMM)) == 0) { pid = parse_common_pid(tep, record->data); comm = find_cmdline(tep, pid); trace_seq_printf(s, type->format, comm); } else if (strncmp(arg, TEP_PRINT_INFO_RAW, strlen(TEP_PRINT_INFO_RAW)) == 0) { print_event_info(s, type->format, true, event, record); } else if (strncmp(arg, TEP_PRINT_INFO, strlen(TEP_PRINT_INFO)) == 0) { print_event_info(s, type->format, false, event, record); } else if (strncmp(arg, TEP_PRINT_NAME, strlen(TEP_PRINT_NAME)) == 0) { trace_seq_printf(s, type->format, event->name); } else { trace_seq_printf(s, "[UNKNOWN TEP TYPE %s]", arg); } } static void print_int(struct tep_handle *tep, struct trace_seq *s, struct tep_record *record, struct tep_event *event, int arg, struct print_event_type *type) { int param; switch (arg) { case TEP_PRINT_CPU: param = record->cpu; break; case TEP_PRINT_PID: param = parse_common_pid(tep, record->data); break; case TEP_PRINT_TIME: return print_event_time(tep, s, type->format, event, record); default: return; } trace_seq_printf(s, type->format, param); } static int tep_print_event_param_type(char *format, struct print_event_type *type) { char *str = format + 1; int i = 1; type->type = EVENT_TYPE_UNKNOWN; while (*str) { switch (*str) { case 'd': case 'u': case 'i': case 'x': case 'X': case 'o': type->type = EVENT_TYPE_INT; break; case 's': type->type = EVENT_TYPE_STRING; break; } str++; i++; if (type->type != EVENT_TYPE_UNKNOWN) break; } memset(type->format, 0, 32); memcpy(type->format, format, i < 32 ? i : 31); return i; } /** * tep_print_event - Write various event information * @tep: a handle to the trace event parser context * @s: the trace_seq to write to * @record: The record to get the event from * @format: a printf format string. Supported event fileds: * TEP_PRINT_PID, "%d" - event PID * TEP_PRINT_CPU, "%d" - event CPU * TEP_PRINT_COMM, "%s" - event command string * TEP_PRINT_NAME, "%s" - event name * TEP_PRINT_LATENCY, "%s" - event latency * TEP_PRINT_TIME, %d - event time stamp. A divisor and precision * can be specified as part of this format string: * "%precision.divisord". Example: * "%3.1000d" - divide the time by 1000 and print the first * 3 digits before the dot. Thus, the time stamp * "123456000" will be printed as "123.456" * TEP_PRINT_INFO, "%s" - event information. If any width is specified in * the format string, the event information will be printed * in raw format. * Writes the specified event information into @s. */ void tep_print_event(struct tep_handle *tep, struct trace_seq *s, struct tep_record *record, const char *fmt, ...) { struct print_event_type type; struct tep_event *event; char *current; char *format; char *str; int offset; va_list args; event = tep_find_event_by_record(tep, record); if (!event) { trace_seq_printf(s, "[UNKNOWN EVENT]"); return; } str = current = format = strdup(fmt); if (!format) return; va_start(args, fmt); while (*current) { current = strchr(str, '%'); if (!current) { trace_seq_puts(s, str); break; } memset(&type, 0, sizeof(type)); offset = tep_print_event_param_type(current, &type); *current = '\0'; trace_seq_puts(s, str); current += offset; switch (type.type) { case EVENT_TYPE_STRING: print_string(tep, s, record, event, va_arg(args, char*), &type); break; case EVENT_TYPE_INT: print_int(tep, s, record, event, va_arg(args, int), &type); break; case EVENT_TYPE_UNKNOWN: default: trace_seq_printf(s, "[UNKNOWN TYPE]"); break; } str = current; } va_end(args); free(format); } static int events_id_cmp(const void *a, const void *b) { struct tep_event * const * ea = a; struct tep_event * const * eb = b; if ((*ea)->id < (*eb)->id) return -1; if ((*ea)->id > (*eb)->id) return 1; return 0; } static int events_name_cmp(const void *a, const void *b) { struct tep_event * const * ea = a; struct tep_event * const * eb = b; int res; res = strcmp((*ea)->name, (*eb)->name); if (res) return res; res = strcmp((*ea)->system, (*eb)->system); if (res) return res; return events_id_cmp(a, b); } static int events_system_cmp(const void *a, const void *b) { struct tep_event * const * ea = a; struct tep_event * const * eb = b; int res; res = strcmp((*ea)->system, (*eb)->system); if (res) return res; res = strcmp((*ea)->name, (*eb)->name); if (res) return res; return events_id_cmp(a, b); } static struct tep_event **list_events_copy(struct tep_handle *tep) { struct tep_event **events; if (!tep) return NULL; events = malloc(sizeof(*events) * (tep->nr_events + 1)); if (!events) return NULL; memcpy(events, tep->events, sizeof(*events) * tep->nr_events); events[tep->nr_events] = NULL; return events; } static void list_events_sort(struct tep_event **events, int nr_events, enum tep_event_sort_type sort_type) { int (*sort)(const void *a, const void *b); switch (sort_type) { case TEP_EVENT_SORT_ID: sort = events_id_cmp; break; case TEP_EVENT_SORT_NAME: sort = events_name_cmp; break; case TEP_EVENT_SORT_SYSTEM: sort = events_system_cmp; break; default: sort = NULL; } if (sort) qsort(events, nr_events, sizeof(*events), sort); } /** * tep_list_events - Get events, sorted by given criteria. * @tep: a handle to the tep context * @sort_type: desired sort order of the events in the array * * Returns an array of pointers to all events, sorted by the given * @sort_type criteria. The last element of the array is NULL. The returned * memory must not be freed, it is managed by the library. * The function is not thread safe. */ struct tep_event **tep_list_events(struct tep_handle *tep, enum tep_event_sort_type sort_type) { struct tep_event **events; if (!tep) return NULL; events = tep->sort_events; if (events && tep->last_type == sort_type) return events; if (!events) { events = list_events_copy(tep); if (!events) return NULL; tep->sort_events = events; /* the internal events are sorted by id */ if (sort_type == TEP_EVENT_SORT_ID) { tep->last_type = sort_type; return events; } } list_events_sort(events, tep->nr_events, sort_type); tep->last_type = sort_type; return events; } /** * tep_list_events_copy - Thread safe version of tep_list_events() * @tep: a handle to the tep context * @sort_type: desired sort order of the events in the array * * Returns an array of pointers to all events, sorted by the given * @sort_type criteria. The last element of the array is NULL. The returned * array is newly allocated inside the function and must be freed by the caller */ struct tep_event **tep_list_events_copy(struct tep_handle *tep, enum tep_event_sort_type sort_type) { struct tep_event **events; if (!tep) return NULL; events = list_events_copy(tep); if (!events) return NULL; /* the internal events are sorted by id */ if (sort_type == TEP_EVENT_SORT_ID) return events; list_events_sort(events, tep->nr_events, sort_type); return events; } static struct tep_format_field ** get_event_fields(const char *type, const char *name, int count, struct tep_format_field *list) { struct tep_format_field **fields; struct tep_format_field *field; int i = 0; fields = malloc(sizeof(*fields) * (count + 1)); if (!fields) return NULL; for (field = list; field; field = field->next) { fields[i++] = field; if (i == count + 1) { do_warning("event %s has more %s fields than specified", name, type); i--; break; } } if (i != count) do_warning("event %s has less %s fields than specified", name, type); fields[i] = NULL; return fields; } /** * tep_event_common_fields - return a list of common fields for an event * @event: the event to return the common fields of. * * Returns an allocated array of fields. The last item in the array is NULL. * The array must be freed with free(). */ struct tep_format_field **tep_event_common_fields(struct tep_event *event) { return get_event_fields("common", event->name, event->format.nr_common, event->format.common_fields); } /** * tep_event_fields - return a list of event specific fields for an event * @event: the event to return the fields of. * * Returns an allocated array of fields. The last item in the array is NULL. * The array must be freed with free(). */ struct tep_format_field **tep_event_fields(struct tep_event *event) { return get_event_fields("event", event->name, event->format.nr_fields, event->format.fields); } static void print_fields(struct trace_seq *s, struct tep_print_flag_sym *field) { trace_seq_printf(s, "{ %s, %s }", field->value, field->str); if (field->next) { trace_seq_puts(s, ", "); print_fields(s, field->next); } } /* for debugging */ static void print_args(struct tep_print_arg *args) { int print_paren = 1; struct trace_seq s; switch (args->type) { case TEP_PRINT_NULL: printf("null"); break; case TEP_PRINT_ATOM: printf("%s", args->atom.atom); break; case TEP_PRINT_FIELD: printf("REC->%s", args->field.name); break; case TEP_PRINT_FLAGS: printf("__print_flags("); print_args(args->flags.field); printf(", %s, ", args->flags.delim); trace_seq_init(&s); print_fields(&s, args->flags.flags); trace_seq_do_printf(&s); trace_seq_destroy(&s); printf(")"); break; case TEP_PRINT_SYMBOL: printf("__print_symbolic("); print_args(args->symbol.field); printf(", "); trace_seq_init(&s); print_fields(&s, args->symbol.symbols); trace_seq_do_printf(&s); trace_seq_destroy(&s); printf(")"); break; case TEP_PRINT_HEX: printf("__print_hex("); print_args(args->hex.field); printf(", "); print_args(args->hex.size); printf(")"); break; case TEP_PRINT_HEX_STR: printf("__print_hex_str("); print_args(args->hex.field); printf(", "); print_args(args->hex.size); printf(")"); break; case TEP_PRINT_INT_ARRAY: printf("__print_array("); print_args(args->int_array.field); printf(", "); print_args(args->int_array.count); printf(", "); print_args(args->int_array.el_size); printf(")"); break; case TEP_PRINT_STRING: case TEP_PRINT_BSTRING: printf("__get_str(%s)", args->string.string); break; case TEP_PRINT_BITMASK: printf("__get_bitmask(%s)", args->bitmask.bitmask); break; case TEP_PRINT_CPUMASK: printf("__get_cpumask(%s)", args->bitmask.bitmask); break; case TEP_PRINT_TYPE: printf("(%s)", args->typecast.type); print_args(args->typecast.item); break; case TEP_PRINT_OP: if (strcmp(args->op.op, ":") == 0) print_paren = 0; if (print_paren) printf("("); print_args(args->op.left); printf(" %s ", args->op.op); print_args(args->op.right); if (print_paren) printf(")"); break; default: /* we should warn... */ return; } if (args->next) { printf("\n"); print_args(args->next); } } static void parse_header_field(struct tep_handle *tep, const char *field, int *offset, int *size, int mandatory) { unsigned long long save_input_buf_ptr; unsigned long long save_input_buf_siz; char *token; int type; save_input_buf_ptr = tep->input_buf_ptr; save_input_buf_siz = tep->input_buf_siz; if (read_expected(tep, TEP_EVENT_ITEM, "field") < 0) return; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return; /* type */ if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; free_token(token); /* * If this is not a mandatory field, then test it first. */ if (mandatory) { if (read_expected(tep, TEP_EVENT_ITEM, field) < 0) return; } else { if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; if (strcmp(token, field) != 0) goto discard; free_token(token); } if (read_expected(tep, TEP_EVENT_OP, ";") < 0) return; if (read_expected(tep, TEP_EVENT_ITEM, "offset") < 0) return; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return; if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; *offset = atoi(token); free_token(token); if (read_expected(tep, TEP_EVENT_OP, ";") < 0) return; if (read_expected(tep, TEP_EVENT_ITEM, "size") < 0) return; if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return; if (read_expect_type(tep, TEP_EVENT_ITEM, &token) < 0) goto fail; *size = atoi(token); free_token(token); if (read_expected(tep, TEP_EVENT_OP, ";") < 0) return; type = read_token(tep, &token); if (type != TEP_EVENT_NEWLINE) { /* newer versions of the kernel have a "signed" type */ if (type != TEP_EVENT_ITEM) goto fail; if (strcmp(token, "signed") != 0) goto fail; free_token(token); if (read_expected(tep, TEP_EVENT_OP, ":") < 0) return; if (read_expect_type(tep, TEP_EVENT_ITEM, &token)) goto fail; free_token(token); if (read_expected(tep, TEP_EVENT_OP, ";") < 0) return; if (read_expect_type(tep, TEP_EVENT_NEWLINE, &token)) goto fail; } fail: free_token(token); return; discard: tep->input_buf_ptr = save_input_buf_ptr; tep->input_buf_siz = save_input_buf_siz; *offset = 0; *size = 0; free_token(token); } /** * tep_parse_header_page - parse the data stored in the header page * @tep: a handle to the trace event parser context * @buf: the buffer storing the header page format string * @size: the size of @buf * @long_size: the long size to use if there is no header * * This parses the header page format for information on the * ring buffer used. The @buf should be copied from * * /sys/kernel/debug/tracing/events/header_page */ int tep_parse_header_page(struct tep_handle *tep, char *buf, unsigned long size, int long_size) { int ignore; if (!size) { /* * Old kernels did not have header page info. * Sorry but we just use what we find here in user space. */ tep->header_page_ts_size = sizeof(long long); tep->header_page_size_size = long_size; tep->header_page_data_offset = sizeof(long long) + long_size; tep->header_page_data_size = getpagesize() - tep->header_page_data_offset; tep->old_format = 1; return -1; } init_input_buf(tep, buf, size); parse_header_field(tep, "timestamp", &tep->header_page_ts_offset, &tep->header_page_ts_size, 1); parse_header_field(tep, "commit", &tep->header_page_size_offset, &tep->header_page_size_size, 1); parse_header_field(tep, "overwrite", &tep->header_page_overwrite, &ignore, 0); parse_header_field(tep, "data", &tep->header_page_data_offset, &tep->header_page_data_size, 1); return 0; } static int event_matches(struct tep_event *event, int id, const char *sys_name, const char *event_name) { if (id >= 0 && id != event->id) return 0; if (event_name && (strcmp(event_name, event->name) != 0)) return 0; if (sys_name && (strcmp(sys_name, event->system) != 0)) return 0; return 1; } static void free_handler(struct event_handler *handle) { free((void *)handle->sys_name); free((void *)handle->event_name); free(handle); } static int find_event_handle(struct tep_handle *tep, struct tep_event *event) { struct event_handler *handle, **next; for (next = &tep->handlers; *next; next = &(*next)->next) { handle = *next; if (event_matches(event, handle->id, handle->sys_name, handle->event_name)) break; } if (!(*next)) return 0; tep_info("overriding event (%d) %s:%s with new print handler", event->id, event->system, event->name); event->handler = handle->func; event->context = handle->context; *next = handle->next; free_handler(handle); return 1; } /** * parse_format - parse the event format * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ static enum tep_errno parse_format(struct tep_event **eventp, struct tep_handle *tep, const char *buf, unsigned long size, const char *sys) { struct tep_event *event; int ret; init_input_buf(tep, buf, size); *eventp = event = alloc_event(); if (!event) return TEP_ERRNO__MEM_ALLOC_FAILED; event->name = event_read_name(tep); if (!event->name) { /* Bad event? */ ret = TEP_ERRNO__MEM_ALLOC_FAILED; goto event_alloc_failed; } if (strcmp(sys, "ftrace") == 0) { event->flags |= TEP_EVENT_FL_ISFTRACE; if (strcmp(event->name, "bprint") == 0) event->flags |= TEP_EVENT_FL_ISBPRINT; } event->id = event_read_id(tep); if (event->id < 0) { ret = TEP_ERRNO__READ_ID_FAILED; /* * This isn't an allocation error actually. * But as the ID is critical, just bail out. */ goto event_alloc_failed; } event->system = strdup(sys); if (!event->system) { ret = TEP_ERRNO__MEM_ALLOC_FAILED; goto event_alloc_failed; } /* Add tep to event so that it can be referenced */ event->tep = tep; ret = event_read_format(event); if (ret < 0) { ret = TEP_ERRNO__READ_FORMAT_FAILED; goto event_parse_failed; } /* * If the event has an override, don't print warnings if the event * print format fails to parse. */ if (tep && find_event_handle(tep, event)) show_warning = 0; ret = event_read_print(event); show_warning = 1; if (ret < 0) { ret = TEP_ERRNO__READ_PRINT_FAILED; goto event_parse_failed; } if (!ret && (event->flags & TEP_EVENT_FL_ISFTRACE)) { struct tep_format_field *field; struct tep_print_arg *arg, **list; /* old ftrace had no args */ list = &event->print_fmt.args; for (field = event->format.fields; field; field = field->next) { arg = alloc_arg(); if (!arg) { event->flags |= TEP_EVENT_FL_FAILED; return TEP_ERRNO__OLD_FTRACE_ARG_FAILED; } arg->type = TEP_PRINT_FIELD; arg->field.name = strdup(field->name); if (!arg->field.name) { event->flags |= TEP_EVENT_FL_FAILED; free_arg(arg); return TEP_ERRNO__OLD_FTRACE_ARG_FAILED; } arg->field.field = field; *list = arg; list = &arg->next; } } if (!(event->flags & TEP_EVENT_FL_ISBPRINT)) event->print_fmt.print_cache = parse_args(event, event->print_fmt.format, event->print_fmt.args); return 0; event_parse_failed: event->flags |= TEP_EVENT_FL_FAILED; return ret; event_alloc_failed: free(event->system); free(event->name); free(event); *eventp = NULL; return ret; } static enum tep_errno __parse_event(struct tep_handle *tep, struct tep_event **eventp, const char *buf, unsigned long size, const char *sys) { int ret = parse_format(eventp, tep, buf, size, sys); struct tep_event *event = *eventp; if (event == NULL) return ret; if (tep && add_event(tep, event)) { ret = TEP_ERRNO__MEM_ALLOC_FAILED; goto event_add_failed; } #define PRINT_ARGS 0 if (PRINT_ARGS && event->print_fmt.args) print_args(event->print_fmt.args); return 0; event_add_failed: free_tep_event(event); return ret; } /** * tep_parse_format - parse the event format * @tep: a handle to the trace event parser context * @eventp: returned format * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ enum tep_errno tep_parse_format(struct tep_handle *tep, struct tep_event **eventp, const char *buf, unsigned long size, const char *sys) { return __parse_event(tep, eventp, buf, size, sys); } /** * tep_parse_event - parse the event format * @tep: a handle to the trace event parser context * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ enum tep_errno tep_parse_event(struct tep_handle *tep, const char *buf, unsigned long size, const char *sys) { struct tep_event *event = NULL; return __parse_event(tep, &event, buf, size, sys); } int get_field_val(struct trace_seq *s, struct tep_format_field *field, const char *name, struct tep_record *record, unsigned long long *val, int err) { if (!field) { if (err) trace_seq_printf(s, "", name); return -1; } if (tep_read_number_field(field, record->data, val)) { if (err) trace_seq_printf(s, " %s=INVALID", name); return -1; } return 0; } /** * tep_get_field_raw - return the raw pointer into the data field * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @len: place to store the field length. * @err: print default error if failed. * * Returns a pointer into record->data of the field and places * the length of the field in @len. * * On failure, it returns NULL. */ void *tep_get_field_raw(struct trace_seq *s, struct tep_event *event, const char *name, struct tep_record *record, int *len, int err) { struct tep_format_field *field; void *data = record->data; unsigned offset; int dummy; if (!event) return NULL; field = tep_find_field(event, name); if (!field) { if (err) trace_seq_printf(s, "", name); return NULL; } /* Allow @len to be NULL */ if (!len) len = &dummy; offset = field->offset; if (field->flags & TEP_FIELD_IS_DYNAMIC) { offset = tep_read_number(event->tep, data + offset, field->size); *len = offset >> 16; offset &= 0xffff; if (field->flags & TEP_FIELD_IS_RELATIVE) offset += field->offset + field->size; } else *len = field->size; return data + offset; } /** * tep_get_field_val - find a field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int tep_get_field_val(struct trace_seq *s, struct tep_event *event, const char *name, struct tep_record *record, unsigned long long *val, int err) { struct tep_format_field *field; if (!event) return -1; field = tep_find_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * tep_get_common_field_val - find a common field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int tep_get_common_field_val(struct trace_seq *s, struct tep_event *event, const char *name, struct tep_record *record, unsigned long long *val, int err) { struct tep_format_field *field; if (!event) return -1; field = tep_find_common_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * tep_get_any_field_val - find a any field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int tep_get_any_field_val(struct trace_seq *s, struct tep_event *event, const char *name, struct tep_record *record, unsigned long long *val, int err) { struct tep_format_field *field; if (!event) return -1; field = tep_find_any_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * tep_print_num_field - print a field and a format * @s: The seq to print to * @fmt: The printf format to print the field with. * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @err: print default error if failed. * * Returns positive value on success, negative in case of an error, * or 0 if buffer is full. */ int tep_print_num_field(struct trace_seq *s, const char *fmt, struct tep_event *event, const char *name, struct tep_record *record, int err) { struct tep_format_field *field = tep_find_field(event, name); unsigned long long val; if (!field) goto failed; if (tep_read_number_field(field, record->data, &val)) goto failed; return trace_seq_printf(s, fmt, val); failed: if (err) trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); return -1; } /** * tep_print_func_field - print a field and a format for function pointers * @s: The seq to print to * @fmt: The printf format to print the field with. * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @err: print default error if failed. * * Returns positive value on success, negative in case of an error, * or 0 if buffer is full. */ int tep_print_func_field(struct trace_seq *s, const char *fmt, struct tep_event *event, const char *name, struct tep_record *record, int err) { struct tep_format_field *field = tep_find_field(event, name); struct tep_handle *tep = event->tep; unsigned long long val; struct func_map *func; char tmp[128]; if (!field) goto failed; if (tep_read_number_field(field, record->data, &val)) goto failed; func = find_func(tep, val); if (func) snprintf(tmp, 128, "%s/0x%llx", func->func, func->addr - val); else sprintf(tmp, "0x%08llx", val); return trace_seq_printf(s, fmt, tmp); failed: if (err) trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); return -1; } static void free_func_handle(struct tep_function_handler *func) { struct func_params *params; free(func->name); while (func->params) { params = func->params; func->params = params->next; free(params); } free(func); } /** * tep_register_print_function - register a helper function * @tep: a handle to the trace event parser context * @func: the function to process the helper function * @ret_type: the return type of the helper function * @name: the name of the helper function * @parameters: A list of enum tep_func_arg_type * * Some events may have helper functions in the print format arguments. * This allows a plugin to dynamically create a way to process one * of these functions. * * The @parameters is a variable list of tep_func_arg_type enums that * must end with TEP_FUNC_ARG_VOID. */ int tep_register_print_function(struct tep_handle *tep, tep_func_handler func, enum tep_func_arg_type ret_type, char *name, ...) { struct tep_function_handler *func_handle; struct func_params **next_param; struct func_params *param; enum tep_func_arg_type type; va_list ap; int ret; func_handle = find_func_handler(tep, name); if (func_handle) { /* * This is most like caused by the users own * plugins updating the function. This overrides the * system defaults. */ tep_info("override of function helper '%s'", name); remove_func_handler(tep, name); } func_handle = calloc(1, sizeof(*func_handle)); if (!func_handle) { do_warning("Failed to allocate function handler"); return TEP_ERRNO__MEM_ALLOC_FAILED; } func_handle->ret_type = ret_type; func_handle->name = strdup(name); func_handle->func = func; if (!func_handle->name) { do_warning("Failed to allocate function name"); free(func_handle); return TEP_ERRNO__MEM_ALLOC_FAILED; } next_param = &(func_handle->params); va_start(ap, name); for (;;) { type = va_arg(ap, enum tep_func_arg_type); if (type == TEP_FUNC_ARG_VOID) break; if (type >= TEP_FUNC_ARG_MAX_TYPES) { do_warning("Invalid argument type %d", type); ret = TEP_ERRNO__INVALID_ARG_TYPE; goto out_free; } param = malloc(sizeof(*param)); if (!param) { do_warning("Failed to allocate function param"); ret = TEP_ERRNO__MEM_ALLOC_FAILED; goto out_free; } param->type = type; param->next = NULL; *next_param = param; next_param = &(param->next); func_handle->nr_args++; } va_end(ap); func_handle->next = tep->func_handlers; tep->func_handlers = func_handle; return 0; out_free: va_end(ap); free_func_handle(func_handle); return ret; } /** * tep_unregister_print_function - unregister a helper function * @tep: a handle to the trace event parser context * @func: the function to process the helper function * @name: the name of the helper function * * This function removes existing print handler for function @name. * * Returns 0 if the handler was removed successully, -1 otherwise. */ int tep_unregister_print_function(struct tep_handle *tep, tep_func_handler func, char *name) { struct tep_function_handler *func_handle; func_handle = find_func_handler(tep, name); if (func_handle && func_handle->func == func) { remove_func_handler(tep, name); return 0; } return -1; } static struct tep_event *search_event(struct tep_handle *tep, int id, const char *sys_name, const char *event_name) { struct tep_event *event; if (id >= 0) { /* search by id */ event = tep_find_event(tep, id); if (!event) return NULL; if (event_name && (strcmp(event_name, event->name) != 0)) return NULL; if (sys_name && (strcmp(sys_name, event->system) != 0)) return NULL; } else { event = tep_find_event_by_name(tep, sys_name, event_name); if (!event) return NULL; } return event; } /** * tep_register_event_handler - register a way to parse an event * @tep: a handle to the trace event parser context * @id: the id of the event to register * @sys_name: the system name the event belongs to * @event_name: the name of the event * @func: the function to call to parse the event information * @context: the data to be passed to @func * * This function allows a developer to override the parsing of * a given event. If for some reason the default print format * is not sufficient, this function will register a function * for an event to be used to parse the data instead. * * If @id is >= 0, then it is used to find the event. * else @sys_name and @event_name are used. * * Returns: * TEP_REGISTER_SUCCESS_OVERWRITE if an existing handler is overwritten * TEP_REGISTER_SUCCESS if a new handler is registered successfully * negative TEP_ERRNO_... in case of an error * */ int tep_register_event_handler(struct tep_handle *tep, int id, const char *sys_name, const char *event_name, tep_event_handler_func func, void *context) { struct tep_event *event; struct event_handler *handle; event = search_event(tep, id, sys_name, event_name); if (event == NULL) goto not_found; tep_info("overriding event (%d) %s:%s with new print handler", event->id, event->system, event->name); event->handler = func; event->context = context; return TEP_REGISTER_SUCCESS_OVERWRITE; not_found: /* Save for later use. */ handle = calloc(1, sizeof(*handle)); if (!handle) { do_warning("Failed to allocate event handler"); return TEP_ERRNO__MEM_ALLOC_FAILED; } handle->id = id; if (event_name) handle->event_name = strdup(event_name); if (sys_name) handle->sys_name = strdup(sys_name); if ((event_name && !handle->event_name) || (sys_name && !handle->sys_name)) { do_warning("Failed to allocate event/sys name"); free((void *)handle->event_name); free((void *)handle->sys_name); free(handle); return TEP_ERRNO__MEM_ALLOC_FAILED; } handle->func = func; handle->next = tep->handlers; tep->handlers = handle; handle->context = context; return TEP_REGISTER_SUCCESS; } static int handle_matches(struct event_handler *handler, int id, const char *sys_name, const char *event_name, tep_event_handler_func func, void *context) { if (id >= 0 && id != handler->id) return 0; if (event_name && (strcmp(event_name, handler->event_name) != 0)) return 0; if (sys_name && (strcmp(sys_name, handler->sys_name) != 0)) return 0; if (func != handler->func || context != handler->context) return 0; return 1; } /** * tep_unregister_event_handler - unregister an existing event handler * @tep: a handle to the trace event parser context * @id: the id of the event to unregister * @sys_name: the system name the handler belongs to * @event_name: the name of the event handler * @func: the function to call to parse the event information * @context: the data to be passed to @func * * This function removes existing event handler (parser). * * If @id is >= 0, then it is used to find the event. * else @sys_name and @event_name are used. * * Returns 0 if handler was removed successfully, -1 if event was not found. */ int tep_unregister_event_handler(struct tep_handle *tep, int id, const char *sys_name, const char *event_name, tep_event_handler_func func, void *context) { struct tep_event *event; struct event_handler *handle; struct event_handler **next; event = search_event(tep, id, sys_name, event_name); if (event == NULL) goto not_found; if (event->handler == func && event->context == context) { tep_info("removing override handler for event (%d) %s:%s. Going back to default handler.", event->id, event->system, event->name); event->handler = NULL; event->context = NULL; return 0; } not_found: for (next = &tep->handlers; *next; next = &(*next)->next) { handle = *next; if (handle_matches(handle, id, sys_name, event_name, func, context)) break; } if (!(*next)) return -1; *next = handle->next; free_handler(handle); return 0; } /** * tep_alloc - create a tep handle */ struct tep_handle *tep_alloc(void) { struct tep_handle *tep = calloc(1, sizeof(*tep)); if (tep) { tep->ref_count = 1; tep->host_bigendian = tep_is_bigendian(); } return tep; } void tep_ref(struct tep_handle *tep) { tep->ref_count++; } int tep_get_ref(struct tep_handle *tep) { if (tep) return tep->ref_count; return 0; } __hidden void free_tep_format_field(struct tep_format_field *field) { free(field->type); if (field->alias != field->name) free(field->alias); free(field->name); free(field); } static void free_format_fields(struct tep_format_field *field) { struct tep_format_field *next; while (field) { next = field->next; free_tep_format_field(field); field = next; } } static void free_formats(struct tep_format *format) { free_format_fields(format->common_fields); free_format_fields(format->fields); } __hidden void free_tep_event(struct tep_event *event) { free(event->name); free(event->system); free_formats(&event->format); free(event->print_fmt.format); free_args(event->print_fmt.args); free_parse_args(event->print_fmt.print_cache); free(event); } /** * tep_free - free a tep handle * @tep: the tep handle to free */ void tep_free(struct tep_handle *tep) { struct cmdline_list *cmdlist, *cmdnext; struct func_list *funclist, *funcnext; struct printk_list *printklist, *printknext; struct tep_function_handler *func_handler; struct event_handler *handle; int i; if (!tep) return; cmdlist = tep->cmdlist; funclist = tep->funclist; printklist = tep->printklist; tep->ref_count--; if (tep->ref_count) return; if (tep->cmdlines) { for (i = 0; i < tep->cmdline_count; i++) free(tep->cmdlines[i].comm); free(tep->cmdlines); } while (cmdlist) { cmdnext = cmdlist->next; free(cmdlist->comm); free(cmdlist); cmdlist = cmdnext; } if (tep->func_map) { for (i = 0; i < (int)tep->func_count; i++) { free(tep->func_map[i].func); free(tep->func_map[i].mod); } free(tep->func_map); } while (funclist) { funcnext = funclist->next; free(funclist->func); free(funclist->mod); free(funclist); funclist = funcnext; } while (tep->func_handlers) { func_handler = tep->func_handlers; tep->func_handlers = func_handler->next; free_func_handle(func_handler); } if (tep->printk_map) { for (i = 0; i < (int)tep->printk_count; i++) free(tep->printk_map[i].printk); free(tep->printk_map); } while (printklist) { printknext = printklist->next; free(printklist->printk); free(printklist); printklist = printknext; } for (i = 0; i < tep->nr_events; i++) free_tep_event(tep->events[i]); while (tep->handlers) { handle = tep->handlers; tep->handlers = handle->next; free_handler(handle); } free(tep->events); free(tep->sort_events); free(tep->func_resolver); free_tep_plugin_paths(tep); free(tep); } void tep_unref(struct tep_handle *tep) { tep_free(tep); }