/* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License (not later!) * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include "event-parse.h" #include "event-utils.h" static const char *input_buf; static unsigned long long input_buf_ptr; static unsigned long long input_buf_siz; static int is_flag_field; static int is_symbolic_field; static int show_warning = 1; #define do_warning(fmt, ...) \ do { \ if (show_warning) \ warning(fmt, ##__VA_ARGS__); \ } while (0) static void init_input_buf(const char *buf, unsigned long long size) { input_buf = buf; input_buf_siz = size; input_buf_ptr = 0; } const char *pevent_get_input_buf(void) { return input_buf; } unsigned long long pevent_get_input_buf_ptr(void) { return input_buf_ptr; } struct event_handler { struct event_handler *next; int id; const char *sys_name; const char *event_name; pevent_event_handler_func func; void *context; }; struct pevent_func_params { struct pevent_func_params *next; enum pevent_func_arg_type type; }; struct pevent_function_handler { struct pevent_function_handler *next; enum pevent_func_arg_type ret_type; char *name; pevent_func_handler func; struct pevent_func_params *params; int nr_args; }; static unsigned long long process_defined_func(struct trace_seq *s, void *data, int size, struct event_format *event, struct print_arg *arg); static void free_func_handle(struct pevent_function_handler *func); /** * pevent_buffer_init - init buffer for parsing * @buf: buffer to parse * @size: the size of the buffer * * For use with pevent_read_token(), this initializes the internal * buffer that pevent_read_token() will parse. */ void pevent_buffer_init(const char *buf, unsigned long long size) { init_input_buf(buf, size); } void breakpoint(void) { static int x; x++; } struct print_arg *alloc_arg(void) { struct print_arg *arg; arg = malloc_or_die(sizeof(*arg)); if (!arg) return NULL; memset(arg, 0, sizeof(*arg)); return arg; } struct cmdline { char *comm; int pid; }; static int cmdline_cmp(const void *a, const void *b) { const struct cmdline *ca = a; const struct cmdline *cb = b; if (ca->pid < cb->pid) return -1; if (ca->pid > cb->pid) return 1; return 0; } struct cmdline_list { struct cmdline_list *next; char *comm; int pid; }; static int cmdline_init(struct pevent *pevent) { struct cmdline_list *cmdlist = pevent->cmdlist; struct cmdline_list *item; struct cmdline *cmdlines; int i; cmdlines = malloc_or_die(sizeof(*cmdlines) * pevent->cmdline_count); i = 0; while (cmdlist) { cmdlines[i].pid = cmdlist->pid; cmdlines[i].comm = cmdlist->comm; i++; item = cmdlist; cmdlist = cmdlist->next; free(item); } qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp); pevent->cmdlines = cmdlines; pevent->cmdlist = NULL; return 0; } static char *find_cmdline(struct pevent *pevent, int pid) { const struct cmdline *comm; struct cmdline key; if (!pid) return ""; if (!pevent->cmdlines) cmdline_init(pevent); key.pid = pid; comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (comm) return comm->comm; return "<...>"; } /** * pevent_pid_is_registered - return if a pid has a cmdline registered * @pevent: handle for the pevent * @pid: The pid to check if it has a cmdline registered with. * * Returns 1 if the pid has a cmdline mapped to it * 0 otherwise. */ int pevent_pid_is_registered(struct pevent *pevent, int pid) { const struct cmdline *comm; struct cmdline key; if (!pid) return 1; if (!pevent->cmdlines) cmdline_init(pevent); key.pid = pid; comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (comm) return 1; return 0; } /* * 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 pevent *pevent, const char *comm, int pid) { struct cmdline *cmdlines = pevent->cmdlines; const struct cmdline *cmdline; struct cmdline key; if (!pid) return 0; /* avoid duplicates */ key.pid = pid; cmdline = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (cmdline) { errno = EEXIST; return -1; } cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (pevent->cmdline_count + 1)); if (!cmdlines) { errno = ENOMEM; return -1; } cmdlines[pevent->cmdline_count].pid = pid; cmdlines[pevent->cmdline_count].comm = strdup(comm); if (!cmdlines[pevent->cmdline_count].comm) die("malloc comm"); if (cmdlines[pevent->cmdline_count].comm) pevent->cmdline_count++; qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp); pevent->cmdlines = cmdlines; return 0; } /** * pevent_register_comm - register a pid / comm mapping * @pevent: handle for the pevent * @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. */ int pevent_register_comm(struct pevent *pevent, const char *comm, int pid) { struct cmdline_list *item; if (pevent->cmdlines) return add_new_comm(pevent, comm, pid); item = malloc_or_die(sizeof(*item)); item->comm = strdup(comm); if (!item->comm) die("malloc comm"); item->pid = pid; item->next = pevent->cmdlist; pevent->cmdlist = item; pevent->cmdline_count++; return 0; } 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 pevent *pevent) { struct func_list *funclist; struct func_list *item; struct func_map *func_map; int i; func_map = malloc_or_die(sizeof(*func_map) * (pevent->func_count + 1)); funclist = pevent->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, pevent->func_count, sizeof(*func_map), func_cmp); /* * Add a special record at the end. */ func_map[pevent->func_count].func = NULL; func_map[pevent->func_count].addr = 0; func_map[pevent->func_count].mod = NULL; pevent->func_map = func_map; pevent->funclist = NULL; return 0; } static struct func_map * find_func(struct pevent *pevent, unsigned long long addr) { struct func_map *func; struct func_map key; if (!pevent->func_map) func_map_init(pevent); key.addr = addr; func = bsearch(&key, pevent->func_map, pevent->func_count, sizeof(*pevent->func_map), func_bcmp); return func; } /** * pevent_find_function - find a function by a given address * @pevent: handle for the pevent * @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 *pevent_find_function(struct pevent *pevent, unsigned long long addr) { struct func_map *map; map = find_func(pevent, addr); if (!map) return NULL; return map->func; } /** * pevent_find_function_address - find a function address by a given address * @pevent: handle for the pevent * @addr: the address to find the function with * * Returns the address the function starts at. This can be used in * conjunction with pevent_find_function to print both the function * name and the function offset. */ unsigned long long pevent_find_function_address(struct pevent *pevent, unsigned long long addr) { struct func_map *map; map = find_func(pevent, addr); if (!map) return 0; return map->addr; } /** * pevent_register_function - register a function with a given address * @pevent: handle for the pevent * @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 pevent_register_function(struct pevent *pevent, char *func, unsigned long long addr, char *mod) { struct func_list *item; item = malloc_or_die(sizeof(*item)); item->next = pevent->funclist; item->func = strdup(func); if (mod) item->mod = strdup(mod); else item->mod = NULL; item->addr = addr; if (!item->func || (mod && !item->mod)) die("malloc func"); pevent->funclist = item; pevent->func_count++; return 0; } /** * pevent_print_funcs - print out the stored functions * @pevent: handle for the pevent * * This prints out the stored functions. */ void pevent_print_funcs(struct pevent *pevent) { int i; if (!pevent->func_map) func_map_init(pevent); for (i = 0; i < (int)pevent->func_count; i++) { printf("%016llx %s", pevent->func_map[i].addr, pevent->func_map[i].func); if (pevent->func_map[i].mod) printf(" [%s]\n", pevent->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 void printk_map_init(struct pevent *pevent) { struct printk_list *printklist; struct printk_list *item; struct printk_map *printk_map; int i; printk_map = malloc_or_die(sizeof(*printk_map) * (pevent->printk_count + 1)); printklist = pevent->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, pevent->printk_count, sizeof(*printk_map), printk_cmp); pevent->printk_map = printk_map; pevent->printklist = NULL; } static struct printk_map * find_printk(struct pevent *pevent, unsigned long long addr) { struct printk_map *printk; struct printk_map key; if (!pevent->printk_map) printk_map_init(pevent); key.addr = addr; printk = bsearch(&key, pevent->printk_map, pevent->printk_count, sizeof(*pevent->printk_map), printk_cmp); return printk; } /** * pevent_register_print_string - register a string by its address * @pevent: handle for the pevent * @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 pevent_register_print_string(struct pevent *pevent, char *fmt, unsigned long long addr) { struct printk_list *item; item = malloc_or_die(sizeof(*item)); item->next = pevent->printklist; item->printk = strdup(fmt); item->addr = addr; if (!item->printk) die("malloc fmt"); pevent->printklist = item; pevent->printk_count++; return 0; } /** * pevent_print_printk - print out the stored strings * @pevent: handle for the pevent * * This prints the string formats that were stored. */ void pevent_print_printk(struct pevent *pevent) { int i; if (!pevent->printk_map) printk_map_init(pevent); for (i = 0; i < (int)pevent->printk_count; i++) { printf("%016llx %s\n", pevent->printk_map[i].addr, pevent->printk_map[i].printk); } } static struct event_format *alloc_event(void) { struct event_format *event; event = malloc(sizeof(*event)); if (!event) return NULL; memset(event, 0, sizeof(*event)); return event; } static void add_event(struct pevent *pevent, struct event_format *event) { int i; pevent->events = realloc(pevent->events, sizeof(event) * (pevent->nr_events + 1)); if (!pevent->events) die("Can not allocate events"); for (i = 0; i < pevent->nr_events; i++) { if (pevent->events[i]->id > event->id) break; } if (i < pevent->nr_events) memmove(&pevent->events[i + 1], &pevent->events[i], sizeof(event) * (pevent->nr_events - i)); pevent->events[i] = event; pevent->nr_events++; event->pevent = pevent; } static int event_item_type(enum event_type type) { switch (type) { case EVENT_ITEM ... EVENT_SQUOTE: return 1; case EVENT_ERROR ... EVENT_DELIM: default: return 0; } } static void free_flag_sym(struct print_flag_sym *fsym) { struct print_flag_sym *next; while (fsym) { next = fsym->next; free(fsym->value); free(fsym->str); free(fsym); fsym = next; } } static void free_arg(struct print_arg *arg) { struct print_arg *farg; if (!arg) return; switch (arg->type) { case PRINT_ATOM: free(arg->atom.atom); break; case PRINT_FIELD: free(arg->field.name); break; case PRINT_FLAGS: free_arg(arg->flags.field); free(arg->flags.delim); free_flag_sym(arg->flags.flags); break; case PRINT_SYMBOL: free_arg(arg->symbol.field); free_flag_sym(arg->symbol.symbols); break; case PRINT_HEX: free_arg(arg->hex.field); free_arg(arg->hex.size); break; case PRINT_TYPE: free(arg->typecast.type); free_arg(arg->typecast.item); break; case PRINT_STRING: case PRINT_BSTRING: free(arg->string.string); break; case PRINT_DYNAMIC_ARRAY: free(arg->dynarray.index); break; case PRINT_OP: free(arg->op.op); free_arg(arg->op.left); free_arg(arg->op.right); break; case PRINT_FUNC: while (arg->func.args) { farg = arg->func.args; arg->func.args = farg->next; free_arg(farg); } break; case PRINT_NULL: default: break; } free(arg); } static enum event_type get_type(int ch) { if (ch == '\n') return EVENT_NEWLINE; if (isspace(ch)) return EVENT_SPACE; if (isalnum(ch) || ch == '_') return EVENT_ITEM; if (ch == '\'') return EVENT_SQUOTE; if (ch == '"') return EVENT_DQUOTE; if (!isprint(ch)) return EVENT_NONE; if (ch == '(' || ch == ')' || ch == ',') return EVENT_DELIM; return EVENT_OP; } static int __read_char(void) { if (input_buf_ptr >= input_buf_siz) return -1; return input_buf[input_buf_ptr++]; } static int __peek_char(void) { if (input_buf_ptr >= input_buf_siz) return -1; return input_buf[input_buf_ptr]; } /** * pevent_peek_char - peek at the next character that will be read * * Returns the next character read, or -1 if end of buffer. */ int pevent_peek_char(void) { return __peek_char(); } 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 event_type force_token(const char *str, char **tok); static enum event_type __read_token(char **tok) { char buf[BUFSIZ]; int ch, last_ch, quote_ch, next_ch; int i = 0; int tok_size = 0; enum event_type type; *tok = NULL; ch = __read_char(); if (ch < 0) return EVENT_NONE; type = get_type(ch); if (type == EVENT_NONE) return type; buf[i++] = ch; switch (type) { case EVENT_NEWLINE: case EVENT_DELIM: *tok = malloc_or_die(2); (*tok)[0] = ch; (*tok)[1] = 0; return type; case EVENT_OP: switch (ch) { case '-': next_ch = __peek_char(); if (next_ch == '>') { buf[i++] = __read_char(); break; } /* fall through */ case '+': case '|': case '&': case '>': case '<': last_ch = ch; ch = __peek_char(); if (ch != last_ch) goto test_equal; buf[i++] = __read_char(); 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(); if (ch == '=') buf[i++] = __read_char(); goto out; case EVENT_DQUOTE: case 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 EVENT_NONE; i = 0; } last_ch = ch; ch = __read_char(); buf[i++] = ch; /* the '\' '\' will cancel itself */ if (ch == '\\' && last_ch == '\\') last_ch = 0; } while (ch != quote_ch || last_ch == '\\'); /* remove the last quote */ i--; /* * For strings (double quotes) check the next token. * If it is another string, concatinate the two. */ if (type == EVENT_DQUOTE) { unsigned long long save_input_buf_ptr = input_buf_ptr; do { ch = __read_char(); } while (isspace(ch)); if (ch == '"') goto concat; input_buf_ptr = save_input_buf_ptr; } goto out; case EVENT_ERROR ... EVENT_SPACE: case EVENT_ITEM: default: break; } while (get_type(__peek_char()) == type) { if (i == (BUFSIZ - 1)) { buf[i] = 0; tok_size += BUFSIZ; if (extend_token(tok, buf, tok_size) < 0) return EVENT_NONE; i = 0; } ch = __read_char(); buf[i++] = ch; } out: buf[i] = 0; if (extend_token(tok, buf, tok_size + i + 1) < 0) return EVENT_NONE; if (type == 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("\"\%s\" ", tok); } else if (strcmp(*tok, "STA_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token("\" sta:%pM\" ", tok); } else if (strcmp(*tok, "VIF_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token("\" vif:%p(%d)\" ", tok); } } return type; } static enum event_type force_token(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 event_type type; /* save off the current input pointers */ save_input_buf = input_buf; save_input_buf_ptr = input_buf_ptr; save_input_buf_siz = input_buf_siz; init_input_buf(str, strlen(str)); type = __read_token(tok); /* reset back to original token */ input_buf = save_input_buf; input_buf_ptr = save_input_buf_ptr; input_buf_siz = save_input_buf_siz; return type; } static void free_token(char *tok) { if (tok) free(tok); } static enum event_type read_token(char **tok) { enum event_type type; for (;;) { type = __read_token(tok); if (type != EVENT_SPACE) return type; free_token(*tok); } /* not reached */ *tok = NULL; return EVENT_NONE; } /** * pevent_read_token - access to utilites to use the pevent parser * @tok: The token to return * * This will parse tokens from the string given by * pevent_init_data(). * * Returns the token type. */ enum event_type pevent_read_token(char **tok) { return read_token(tok); } /** * pevent_free_token - free a token returned by pevent_read_token * @token: the token to free */ void pevent_free_token(char *token) { free_token(token); } /* no newline */ static enum event_type read_token_item(char **tok) { enum event_type type; for (;;) { type = __read_token(tok); if (type != EVENT_SPACE && type != EVENT_NEWLINE) return type; free_token(*tok); *tok = NULL; } /* not reached */ *tok = NULL; return EVENT_NONE; } static int test_type(enum event_type type, enum event_type expect) { if (type != expect) { do_warning("Error: expected type %d but read %d", expect, type); return -1; } return 0; } static int test_type_token(enum event_type type, const char *token, enum event_type expect, const char *expect_tok) { if (type != expect) { do_warning("Error: expected type %d but read %d", expect, 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(enum event_type expect, char **tok, int newline_ok) { enum event_type type; if (newline_ok) type = read_token(tok); else type = read_token_item(tok); return test_type(type, expect); } static int read_expect_type(enum event_type expect, char **tok) { return __read_expect_type(expect, tok, 1); } static int __read_expected(enum event_type expect, const char *str, int newline_ok) { enum event_type type; char *token; int ret; if (newline_ok) type = read_token(&token); else type = read_token_item(&token); ret = test_type_token(type, token, expect, str); free_token(token); return ret; } static int read_expected(enum event_type expect, const char *str) { return __read_expected(expect, str, 1); } static int read_expected_item(enum event_type expect, const char *str) { return __read_expected(expect, str, 0); } static char *event_read_name(void) { char *token; if (read_expected(EVENT_ITEM, "name") < 0) return NULL; if (read_expected(EVENT_OP, ":") < 0) return NULL; if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; return token; fail: free_token(token); return NULL; } static int event_read_id(void) { char *token; int id; if (read_expected_item(EVENT_ITEM, "ID") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(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 format_field *field) { if ((field->flags & 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 format_field *field) { if (strncmp(field->type, "__data_loc", 10) == 0) return 1; return 0; } static int field_is_long(struct format_field *field) { /* includes long long */ if (strstr(field->type, "long")) return 1; return 0; } static int event_read_fields(struct event_format *event, struct format_field **fields) { struct format_field *field = NULL; enum event_type type; char *token; char *last_token; int count = 0; do { type = read_token(&token); if (type == EVENT_NEWLINE) { free_token(token); return count; } count++; if (test_type_token(type, token, EVENT_ITEM, "field")) goto fail; free_token(token); type = read_token(&token); /* * The ftrace fields may still use the "special" name. * Just ignore it. */ if (event->flags & EVENT_FL_ISFTRACE && type == EVENT_ITEM && strcmp(token, "special") == 0) { free_token(token); type = read_token(&token); } if (test_type_token(type, token, EVENT_OP, ":") < 0) goto fail; free_token(token); if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; last_token = token; field = malloc_or_die(sizeof(*field)); memset(field, 0, sizeof(*field)); field->event = event; /* read the rest of the type */ for (;;) { type = read_token(&token); if (type == EVENT_ITEM || (type == EVENT_OP && strcmp(token, "*") == 0) || /* * Some of the ftrace fields are broken and have * an illegal "." in them. */ (event->flags & EVENT_FL_ISFTRACE && type == EVENT_OP && strcmp(token, ".") == 0)) { if (strcmp(token, "*") == 0) field->flags |= FIELD_IS_POINTER; if (field->type) { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(last_token) + 2); if (!new_type) { free(last_token); goto fail; } field->type = new_type; strcat(field->type, " "); strcat(field->type, last_token); free(last_token); } else field->type = last_token; last_token = token; continue; } break; } if (!field->type) { die("no type found"); goto fail; } field->name = last_token; if (test_type(type, EVENT_OP)) goto fail; if (strcmp(token, "[") == 0) { enum event_type last_type = type; char *brackets = token; char *new_brackets; int len; field->flags |= FIELD_IS_ARRAY; type = read_token(&token); if (type == EVENT_ITEM) field->arraylen = strtoul(token, NULL, 0); else field->arraylen = 0; while (strcmp(token, "]") != 0) { if (last_type == EVENT_ITEM && type == EVENT_ITEM) len = 2; else len = 1; last_type = type; new_brackets = realloc(brackets, strlen(brackets) + strlen(token) + len); if (!new_brackets) { free(brackets); goto fail; } brackets = new_brackets; if (len == 2) strcat(brackets, " "); strcat(brackets, token); /* We only care about the last token */ field->arraylen = strtoul(token, NULL, 0); free_token(token); type = read_token(&token); if (type == EVENT_NONE) { die("failed to find token"); goto fail; } } free_token(token); new_brackets = realloc(brackets, strlen(brackets) + 2); if (!new_brackets) { free(brackets); goto fail; } brackets = new_brackets; strcat(brackets, "]"); /* add brackets to type */ type = read_token(&token); /* * If the next token is not an OP, then it is of * the format: type [] item; */ if (type == EVENT_ITEM) { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(field->name) + strlen(brackets) + 2); if (!new_type) { free(brackets); goto fail; } field->type = new_type; strcat(field->type, " "); strcat(field->type, field->name); free_token(field->name); strcat(field->type, brackets); field->name = token; type = read_token(&token); } else { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(brackets) + 1); if (!new_type) { free(brackets); goto fail; } field->type = new_type; strcat(field->type, brackets); } free(brackets); } if (field_is_string(field)) field->flags |= FIELD_IS_STRING; if (field_is_dynamic(field)) field->flags |= FIELD_IS_DYNAMIC; if (field_is_long(field)) field->flags |= FIELD_IS_LONG; if (test_type_token(type, token, EVENT_OP, ";")) goto fail; free_token(token); if (read_expected(EVENT_ITEM, "offset") < 0) goto fail_expect; if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; field->offset = strtoul(token, NULL, 0); free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; if (read_expected(EVENT_ITEM, "size") < 0) goto fail_expect; if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; field->size = strtoul(token, NULL, 0); free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; type = read_token(&token); if (type != EVENT_NEWLINE) { /* newer versions of the kernel have a "signed" type */ if (test_type_token(type, token, EVENT_ITEM, "signed")) goto fail; free_token(token); if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; /* add signed type */ free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; if (read_expect_type(EVENT_NEWLINE, &token)) goto fail; } free_token(token); if (field->flags & FIELD_IS_ARRAY) { if (field->arraylen) field->elementsize = field->size / field->arraylen; else if (field->flags & FIELD_IS_STRING) field->elementsize = 1; else field->elementsize = event->pevent->long_size; } else field->elementsize = field->size; *fields = field; fields = &field->next; } 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 event_format *event) { char *token; int ret; if (read_expected_item(EVENT_ITEM, "format") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(EVENT_NEWLINE, &token)) goto fail; free_token(token); ret = event_read_fields(event, &event->format.common_fields); if (ret < 0) return ret; event->format.nr_common = ret; ret = event_read_fields(event, &event->format.fields); if (ret < 0) return ret; event->format.nr_fields = ret; return 0; fail: free_token(token); return -1; } static enum event_type process_arg_token(struct event_format *event, struct print_arg *arg, char **tok, enum event_type type); static enum event_type process_arg(struct event_format *event, struct print_arg *arg, char **tok) { enum event_type type; char *token; type = read_token(&token); *tok = token; return process_arg_token(event, arg, tok, type); } static enum event_type process_op(struct event_format *event, struct print_arg *arg, char **tok); static enum event_type process_cond(struct event_format *event, struct print_arg *top, char **tok) { struct print_arg *arg, *left, *right; enum event_type type; char *token = NULL; arg = alloc_arg(); left = alloc_arg(); right = alloc_arg(); arg->type = PRINT_OP; arg->op.left = left; arg->op.right = right; *tok = NULL; type = process_arg(event, left, &token); again: /* Handle other operations in the arguments */ if (type == EVENT_OP && strcmp(token, ":") != 0) { type = process_op(event, left, &token); goto again; } if (test_type_token(type, token, 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 EVENT_ERROR; } static enum event_type process_array(struct event_format *event, struct print_arg *top, char **tok) { struct print_arg *arg; enum event_type type; char *token = NULL; arg = alloc_arg(); *tok = NULL; type = process_arg(event, arg, &token); if (test_type_token(type, token, EVENT_OP, "]")) goto out_free; top->op.right = arg; free_token(token); type = read_token_item(&token); *tok = token; return type; out_free: free_token(*tok); *tok = NULL; free_arg(arg); return EVENT_ERROR; } static int get_op_prio(char *op) { if (!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 print_arg *arg) { /* single ops are the greatest */ if (!arg->op.left || arg->op.left->type == PRINT_NULL) arg->op.prio = 0; else arg->op.prio = get_op_prio(arg->op.op); return arg->op.prio; } /* Note, *tok does not get freed, but will most likely be saved */ static enum event_type process_op(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *left, *right = NULL; enum event_type type; char *token; /* the op is passed in via tok */ token = *tok; if (arg->type == PRINT_OP && !arg->op.left) { /* handle single op */ if (token[1]) { die("bad op token %s", token); goto out_free; } switch (token[0]) { case '~': case '!': case '+': case '-': break; default: do_warning("bad op token %s", token); goto out_free; } /* make an empty left */ left = alloc_arg(); left->type = PRINT_NULL; arg->op.left = left; right = alloc_arg(); 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(); /* copy the top arg to the left */ *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.prio = 0; 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) { left = alloc_arg(); /* copy the top arg to the left */ *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; if (set_op_prio(arg) == -1) { event->flags |= EVENT_FL_FAILED; /* arg->op.op (= token) will be freed at out_free */ arg->op.op = NULL; goto out_free; } type = read_token_item(&token); *tok = token; /* could just be a type pointer */ if ((strcmp(arg->op.op, "*") == 0) && type == EVENT_DELIM && (strcmp(token, ")") == 0)) { char *new_atom; if (left->type != PRINT_ATOM) die("bad pointer type"); new_atom = realloc(left->atom.atom, strlen(left->atom.atom) + 3); if (!new_atom) goto out_free; left->atom.atom = new_atom; strcat(left->atom.atom, " *"); free(arg->op.op); *arg = *left; free(left); return type; } right = alloc_arg(); type = process_arg_token(event, right, tok, type); arg->op.right = right; } else if (strcmp(token, "[") == 0) { left = alloc_arg(); *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.prio = 0; type = process_array(event, arg, tok); } else { do_warning("unknown op '%s'", token); event->flags |= EVENT_FL_FAILED; /* the arg is now the left side */ goto out_free; } if (type == 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_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_entry(struct event_format *event __unused, struct print_arg *arg, char **tok) { enum event_type type; char *field; char *token; if (read_expected(EVENT_OP, "->") < 0) goto out_err; if (read_expect_type(EVENT_ITEM, &token) < 0) goto out_free; field = token; arg->type = PRINT_FIELD; arg->field.name = field; if (is_flag_field) { arg->field.field = pevent_find_any_field(event, arg->field.name); arg->field.field->flags |= FIELD_IS_FLAG; is_flag_field = 0; } else if (is_symbolic_field) { arg->field.field = pevent_find_any_field(event, arg->field.name); arg->field.field->flags |= FIELD_IS_SYMBOLIC; is_symbolic_field = 0; } type = read_token(&token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return EVENT_ERROR; } static char *arg_eval (struct 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 (pointer) { if (type[len-1] != '*') { do_warning("pointer expected with non pointer type"); return val; } ref = malloc_or_die(len); 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")) 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 print_arg *arg, int pointer) { if (arg->type != PRINT_TYPE) die("expected type argument"); return eval_type_str(val, arg->typecast.type, pointer); } static int arg_num_eval(struct print_arg *arg, long long *val) { long long left, right; int ret = 1; switch (arg->type) { case PRINT_ATOM: *val = strtoll(arg->atom.atom, NULL, 0); break; case PRINT_TYPE: ret = arg_num_eval(arg->typecast.item, val); if (!ret) break; *val = eval_type(*val, arg, 0); break; case 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 == 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 == 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; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case PRINT_NULL: case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: default: do_warning("invalid eval type %d", arg->type); ret = 0; } return ret; } static char *arg_eval (struct print_arg *arg) { long long val; static char buf[20]; switch (arg->type) { case PRINT_ATOM: return arg->atom.atom; case PRINT_TYPE: return arg_eval(arg->typecast.item); case PRINT_OP: if (!arg_num_eval(arg, &val)) break; sprintf(buf, "%lld", val); return buf; case PRINT_NULL: case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: default: die("invalid eval type %d", arg->type); break; } return NULL; } static enum event_type process_fields(struct event_format *event, struct print_flag_sym **list, char **tok) { enum event_type type; struct print_arg *arg = NULL; struct print_flag_sym *field; char *token = *tok; char *value; do { free_token(token); type = read_token_item(&token); if (test_type_token(type, token, EVENT_OP, "{")) break; arg = alloc_arg(); free_token(token); type = process_arg(event, arg, &token); if (type == EVENT_OP) type = process_op(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; field = malloc_or_die(sizeof(*field)); memset(field, 0, sizeof(*field)); value = arg_eval(arg); if (value == NULL) goto out_free; field->value = strdup(value); if (field->value == NULL) goto out_free; free_arg(arg); arg = alloc_arg(); free_token(token); type = process_arg(event, arg, &token); if (test_type_token(type, token, EVENT_OP, "}")) goto out_free; value = arg_eval(arg); if (value == NULL) goto out_free; field->str = strdup(value); if (field->str == NULL) goto out_free; free_arg(arg); arg = NULL; *list = field; list = &field->next; free_token(token); type = read_token_item(&token); } while (type == EVENT_DELIM && strcmp(token, ",") == 0); *tok = token; return type; out_free: free_arg(arg); free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_flags(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_FLAGS; field = alloc_arg(); type = process_arg(event, field, &token); /* Handle operations in the first argument */ while (type == EVENT_OP) type = process_op(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; free_token(token); arg->flags.field = field; type = read_token_item(&token); if (event_item_type(type)) { arg->flags.delim = token; type = read_token_item(&token); } if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; type = process_fields(event, &arg->flags.flags, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(tok); return type; out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_symbols(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_SYMBOL; field = alloc_arg(); type = process_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; arg->symbol.field = field; type = process_fields(event, &arg->symbol.symbols, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(tok); return type; out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_hex(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_HEX; field = alloc_arg(); type = process_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; arg->hex.field = field; free_token(token); field = alloc_arg(); type = process_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; arg->hex.size = field; free_token(token); type = read_token_item(tok); return type; out_free: free_arg(field); free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_dynamic_array(struct event_format *event, struct print_arg *arg, char **tok) { struct format_field *field; enum event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_DYNAMIC_ARRAY; /* * The item within the parenthesis is another field that holds * the index into where the array starts. */ type = read_token(&token); *tok = token; if (type != EVENT_ITEM) goto out_free; /* Find the field */ field = pevent_find_field(event, token); if (!field) goto out_free; arg->dynarray.field = field; arg->dynarray.index = 0; if (read_expected(EVENT_DELIM, ")") < 0) goto out_free; free_token(token); type = read_token_item(&token); *tok = token; if (type != EVENT_OP || strcmp(token, "[") != 0) return type; free_token(token); arg = alloc_arg(); type = process_arg(event, arg, &token); if (type == EVENT_ERROR) goto out_free_arg; if (!test_type_token(type, token, EVENT_OP, "]")) goto out_free_arg; free_token(token); type = read_token_item(tok); return type; out_free_arg: free_arg(arg); out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_paren(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *item_arg; enum event_type type; char *token; type = process_arg(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (type == EVENT_OP) type = process_op(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(&token); /* * If the next token is an item or another open paren, then * this was a typecast. */ if (event_item_type(type) || (type == EVENT_DELIM && strcmp(token, "(") == 0)) { /* make this a typecast and contine */ /* prevous must be an atom */ if (arg->type != PRINT_ATOM) die("previous needed to be PRINT_ATOM"); item_arg = alloc_arg(); arg->type = 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 EVENT_ERROR; } static enum event_type process_str(struct event_format *event __unused, struct print_arg *arg, char **tok) { enum event_type type; char *token; if (read_expect_type(EVENT_ITEM, &token) < 0) goto out_free; arg->type = PRINT_STRING; arg->string.string = token; arg->string.offset = -1; if (read_expected(EVENT_DELIM, ")") < 0) goto out_err; type = read_token(&token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return EVENT_ERROR; } static struct pevent_function_handler * find_func_handler(struct pevent *pevent, char *func_name) { struct pevent_function_handler *func; for (func = pevent->func_handlers; func; func = func->next) { if (strcmp(func->name, func_name) == 0) break; } return func; } static void remove_func_handler(struct pevent *pevent, char *func_name) { struct pevent_function_handler *func; struct pevent_function_handler **next; next = &pevent->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 event_type process_func_handler(struct event_format *event, struct pevent_function_handler *func, struct print_arg *arg, char **tok) { struct print_arg **next_arg; struct print_arg *farg; enum event_type type; char *token; char *test; int i; arg->type = PRINT_FUNC; arg->func.func = func; *tok = NULL; next_arg = &(arg->func.args); for (i = 0; i < func->nr_args; i++) { farg = alloc_arg(); type = process_arg(event, farg, &token); if (i < (func->nr_args - 1)) test = ","; else test = ")"; if (test_type_token(type, token, EVENT_DELIM, test)) { free_arg(farg); free_token(token); return EVENT_ERROR; } *next_arg = farg; next_arg = &(farg->next); free_token(token); } type = read_token(&token); *tok = token; return type; } static enum event_type process_function(struct event_format *event, struct print_arg *arg, char *token, char **tok) { struct pevent_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, "__get_str") == 0) { free_token(token); return process_str(event, arg, tok); } if (strcmp(token, "__get_dynamic_array") == 0) { free_token(token); return process_dynamic_array(event, arg, tok); } func = find_func_handler(event->pevent, token); if (func) { free_token(token); return process_func_handler(event, func, arg, tok); } do_warning("function %s not defined", token); free_token(token); return EVENT_ERROR; } static enum event_type process_arg_token(struct event_format *event, struct print_arg *arg, char **tok, enum event_type type) { char *token; char *atom; token = *tok; switch (type) { case 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(&token); /* * If the next token is a parenthesis, then this * is a function. */ if (type == 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 == EVENT_ITEM) { char *new_atom; new_atom = realloc(atom, strlen(atom) + strlen(token) + 2); if (!new_atom) { free(atom); *tok = NULL; free_token(token); return EVENT_ERROR; } atom = new_atom; strcat(atom, " "); strcat(atom, token); free_token(token); type = read_token_item(&token); } arg->type = PRINT_ATOM; arg->atom.atom = atom; break; case EVENT_DQUOTE: case EVENT_SQUOTE: arg->type = PRINT_ATOM; arg->atom.atom = token; type = read_token_item(&token); break; case EVENT_DELIM: if (strcmp(token, "(") == 0) { free_token(token); type = process_paren(event, arg, &token); break; } case EVENT_OP: /* handle single ops */ arg->type = PRINT_OP; arg->op.op = token; arg->op.left = NULL; type = process_op(event, arg, &token); /* On error, the op is freed */ if (type == EVENT_ERROR) arg->op.op = NULL; /* return error type if errored */ break; case EVENT_ERROR ... EVENT_NEWLINE: default: die("unexpected type %d", type); } *tok = token; return type; } static int event_read_print_args(struct event_format *event, struct print_arg **list) { enum event_type type = EVENT_ERROR; struct print_arg *arg; char *token; int args = 0; do { if (type == EVENT_NEWLINE) { type = read_token_item(&token); continue; } arg = alloc_arg(); type = process_arg(event, arg, &token); if (type == EVENT_ERROR) { free_token(token); free_arg(arg); return -1; } *list = arg; args++; if (type == EVENT_OP) { type = process_op(event, arg, &token); free_token(token); if (type == EVENT_ERROR) { *list = NULL; free_arg(arg); return -1; } list = &arg->next; continue; } if (type == EVENT_DELIM && strcmp(token, ",") == 0) { free_token(token); *list = arg; list = &arg->next; continue; } break; } while (type != EVENT_NONE); if (type != EVENT_NONE && type != EVENT_ERROR) free_token(token); return args; } static int event_read_print(struct event_format *event) { enum event_type type; char *token; int ret; if (read_expected_item(EVENT_ITEM, "print") < 0) return -1; if (read_expected(EVENT_ITEM, "fmt") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(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(&token); if (type == EVENT_NONE) return 0; /* Handle concatenation of print lines */ if (type == EVENT_DQUOTE) { char *cat; cat = malloc_or_die(strlen(event->print_fmt.format) + strlen(token) + 1); strcpy(cat, event->print_fmt.format); strcat(cat, token); free_token(token); free_token(event->print_fmt.format); event->print_fmt.format = NULL; token = cat; goto concat; } if (test_type_token(type, token, 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; } /** * pevent_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 searchs the common fields and not all field. */ struct format_field * pevent_find_common_field(struct event_format *event, const char *name) { struct format_field *format; for (format = event->format.common_fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * pevent_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 format_field * pevent_find_field(struct event_format *event, const char *name) { struct format_field *format; for (format = event->format.fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * pevent_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 searchs the common field names first, then * the non-common ones if a common one was not found. */ struct format_field * pevent_find_any_field(struct event_format *event, const char *name) { struct format_field *format; format = pevent_find_common_field(event, name); if (format) return format; return pevent_find_field(event, name); } /** * pevent_read_number - read a number from data * @pevent: handle for the pevent * @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 pevent_read_number(struct pevent *pevent, const void *ptr, int size) { switch (size) { case 1: return *(unsigned char *)ptr; case 2: return data2host2(pevent, ptr); case 4: return data2host4(pevent, ptr); case 8: return data2host8(pevent, ptr); default: /* BUG! */ return 0; } } /** * pevent_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 pevent_read_number_field(struct 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 = pevent_read_number(field->event->pevent, data + field->offset, field->size); return 0; default: return -1; } } static int get_common_info(struct pevent *pevent, const char *type, int *offset, int *size) { struct event_format *event; struct format_field *field; /* * All events should have the same common elements. * Pick any event to find where the type is; */ if (!pevent->events) die("no event_list!"); event = pevent->events[0]; field = pevent_find_common_field(event, type); if (!field) return -1; *offset = field->offset; *size = field->size; return 0; } static int __parse_common(struct pevent *pevent, void *data, int *size, int *offset, const char *name) { int ret; if (!*size) { ret = get_common_info(pevent, name, offset, size); if (ret < 0) return ret; } return pevent_read_number(pevent, data + *offset, *size); } static int trace_parse_common_type(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->type_size, &pevent->type_offset, "common_type"); } static int parse_common_pid(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->pid_size, &pevent->pid_offset, "common_pid"); } static int parse_common_pc(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->pc_size, &pevent->pc_offset, "common_preempt_count"); } static int parse_common_flags(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->flags_size, &pevent->flags_offset, "common_flags"); } static int parse_common_lock_depth(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->ld_size, &pevent->ld_offset, "common_lock_depth"); } static int parse_common_migrate_disable(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->ld_size, &pevent->ld_offset, "common_migrate_disable"); } static int events_id_cmp(const void *a, const void *b); /** * pevent_find_event - find an event by given id * @pevent: a handle to the pevent * @id: the id of the event * * Returns an event that has a given @id. */ struct event_format *pevent_find_event(struct pevent *pevent, int id) { struct event_format **eventptr; struct event_format key; struct event_format *pkey = &key; /* Check cache first */ if (pevent->last_event && pevent->last_event->id == id) return pevent->last_event; key.id = id; eventptr = bsearch(&pkey, pevent->events, pevent->nr_events, sizeof(*pevent->events), events_id_cmp); if (eventptr) { pevent->last_event = *eventptr; return *eventptr; } return NULL; } /** * pevent_find_event_by_name - find an event by given name * @pevent: a handle to the pevent * @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 event_format * pevent_find_event_by_name(struct pevent *pevent, const char *sys, const char *name) { struct event_format *event; int i; if (pevent->last_event && strcmp(pevent->last_event->name, name) == 0 && (!sys || strcmp(pevent->last_event->system, sys) == 0)) return pevent->last_event; for (i = 0; i < pevent->nr_events; i++) { event = pevent->events[i]; if (strcmp(event->name, name) == 0) { if (!sys) break; if (strcmp(event->system, sys) == 0) break; } } if (i == pevent->nr_events) event = NULL; pevent->last_event = event; return event; } static unsigned long long eval_num_arg(void *data, int size, struct event_format *event, struct print_arg *arg) { struct pevent *pevent = event->pevent; unsigned long long val = 0; unsigned long long left, right; struct print_arg *typearg = NULL; struct print_arg *larg; unsigned long offset; unsigned int field_size; switch (arg->type) { case PRINT_NULL: /* ?? */ return 0; case PRINT_ATOM: return strtoull(arg->atom.atom, NULL, 0); case PRINT_FIELD: if (!arg->field.field) { arg->field.field = pevent_find_any_field(event, arg->field.name); if (!arg->field.field) die("field %s not found", arg->field.name); } /* must be a number */ val = pevent_read_number(pevent, data + arg->field.field->offset, arg->field.field->size); break; case PRINT_FLAGS: case PRINT_SYMBOL: case PRINT_HEX: break; case PRINT_TYPE: val = eval_num_arg(data, size, event, arg->typecast.item); return eval_type(val, arg, 0); case PRINT_STRING: case PRINT_BSTRING: return 0; case 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 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 == PRINT_TYPE) { if (!typearg) typearg = larg; larg = larg->typecast.item; } /* Default to long size */ field_size = pevent->long_size; switch (larg->type) { case PRINT_DYNAMIC_ARRAY: offset = pevent_read_number(pevent, data + larg->dynarray.field->offset, larg->dynarray.field->size); if (larg->dynarray.field->elementsize) field_size = larg->dynarray.field->elementsize; /* * The actual 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. */ offset &= 0xffff; offset += right; break; case PRINT_FIELD: if (!larg->field.field) { larg->field.field = pevent_find_any_field(event, larg->field.name); if (!larg->field.field) die("field %s not found", larg->field.name); } 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 */ } val = pevent_read_number(pevent, data + offset, field_size); if (typearg) val = eval_type(val, typearg, 1); break; } else if (strcmp(arg->op.op, "?") == 0) {