mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
048ccca8c1
- Remove usage of ib_query_device and instead store attributes in ib_device struct - Move iopoll out of block and into lib, rename to irqpoll, and use in several places in the rdma stack as our new completion queue polling library mechanism. Update the other block drivers that already used iopoll to use the new mechanism too. - Replace the per-entry GID table locks with a single GID table lock - IPoIB multicast cleanup - Cleanups to the IB MR facility - Add support for 64bit extended IB counters - Fix for netlink oops while parsing RDMA nl messages - RoCEv2 support for the core IB code - mlx4 RoCEv2 support - mlx5 RoCEv2 support - Cross Channel support for mlx5 - Timestamp support for mlx5 - Atomic support for mlx5 - Raw QP support for mlx5 - MAINTAINERS update for mlx4/mlx5 - Misc ocrdma, qib, nes, usNIC, cxgb3, cxgb4, mlx4, mlx5 updates - Add support for remote invalidate to the iSER driver (pushed through the RDMA tree due to dependencies, acknowledged by nab) - Update to NFSoRDMA (pushed through the RDMA tree due to dependencies, acknowledged by Bruce) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJWoSygAAoJELgmozMOVy/dDjsP/2vbTda2MvQfkfkGEZBQdJSg 095RN0gQgCJdg78lAl8yuaK8r4VN/7uefpDtFdudH1I/Pei7X0wxN9R1UzFNG4KR AD53lz92IVPs15328SbPR2kvNWISR9aBFQo3rlElq3Grqlp0EMn2Ou1vtu87rekF aMllxr8Nl0uZhP+eWusOsYpJUUtwirLgRnrAyfqo2UxZh/TMIroT0TCx1KXjVcAg dhDARiZAdu3OgSc6OsWqmH+DELEq6dFVA5F+DDBGAb8bFZqlJc7cuMHWInwNsNXT so4bnEQ835alTbsdYtqs5DUNS8heJTAJP4Uz0ehkTh/uNCcvnKeUTw1c2P/lXI1k 7s33gMM+0FXj0swMBw0kKwAF2d9Hhus9UAN7NwjBuOyHcjGRd5q7SAnfWkvKx000 s9jVW19slb2I38gB58nhjOh8s+vXUArgxnV1+kTia1+bJSR5swvVoWRicRXdF0vh TvLX/BjbSIU73g1TnnLNYoBTV3ybFKQ6bVdQW7fzSTDs54dsI1vvdHXi3bYZCpnL HVwQTZRfEzkvb0AdKbcvf8p/TlaAHem3ODqtO1eHvO4if1QJBSn+SptTEeJVYYdK n4B3l/dMoBH4JXJUmEHB9jwAvYOpv/YLAFIvdL7NFwbqGNsC3nfXFcmkVORB1W3B KEMcM2we4bz+uyKMjEAD =5oO7 -----END PGP SIGNATURE----- Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dledford/rdma Pull rdma updates from Doug Ledford: "Initial roundup of 4.5 merge window patches - Remove usage of ib_query_device and instead store attributes in ib_device struct - Move iopoll out of block and into lib, rename to irqpoll, and use in several places in the rdma stack as our new completion queue polling library mechanism. Update the other block drivers that already used iopoll to use the new mechanism too. - Replace the per-entry GID table locks with a single GID table lock - IPoIB multicast cleanup - Cleanups to the IB MR facility - Add support for 64bit extended IB counters - Fix for netlink oops while parsing RDMA nl messages - RoCEv2 support for the core IB code - mlx4 RoCEv2 support - mlx5 RoCEv2 support - Cross Channel support for mlx5 - Timestamp support for mlx5 - Atomic support for mlx5 - Raw QP support for mlx5 - MAINTAINERS update for mlx4/mlx5 - Misc ocrdma, qib, nes, usNIC, cxgb3, cxgb4, mlx4, mlx5 updates - Add support for remote invalidate to the iSER driver (pushed through the RDMA tree due to dependencies, acknowledged by nab) - Update to NFSoRDMA (pushed through the RDMA tree due to dependencies, acknowledged by Bruce)" * tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dledford/rdma: (169 commits) IB/mlx5: Unify CQ create flags check IB/mlx5: Expose Raw Packet QP to user space consumers {IB, net}/mlx5: Move the modify QP operation table to mlx5_ib IB/mlx5: Support setting Ethernet priority for Raw Packet QPs IB/mlx5: Add Raw Packet QP query functionality IB/mlx5: Add create and destroy functionality for Raw Packet QP IB/mlx5: Refactor mlx5_ib_qp to accommodate other QP types IB/mlx5: Allocate a Transport Domain for each ucontext net/mlx5_core: Warn on unsupported events of QP/RQ/SQ net/mlx5_core: Add RQ and SQ event handling net/mlx5_core: Export transport objects IB/mlx5: Expose CQE version to user-space IB/mlx5: Add CQE version 1 support to user QPs and SRQs IB/mlx5: Fix data validation in mlx5_ib_alloc_ucontext IB/sa: Fix netlink local service GFP crash IB/srpt: Remove redundant wc array IB/qib: Improve ipoib UD performance IB/mlx4: Advertise RoCE v2 support IB/mlx4: Create and use another QP1 for RoCEv2 IB/mlx4: Enable send of RoCE QP1 packets with IP/UDP headers ...
6726 lines
144 KiB
C
6726 lines
144 KiB
C
/*
|
|
* Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
* 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, see <http://www.gnu.org/licenses>
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <limits.h>
|
|
|
|
#include <netinet/ip6.h>
|
|
#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)
|
|
|
|
#define do_warning_event(event, fmt, ...) \
|
|
do { \
|
|
if (!show_warning) \
|
|
continue; \
|
|
\
|
|
if (event) \
|
|
warning("[%s:%s] " fmt, event->system, \
|
|
event->name, ##__VA_ARGS__); \
|
|
else \
|
|
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)
|
|
{
|
|
return calloc(1, sizeof(struct print_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(sizeof(*cmdlines) * pevent->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, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp);
|
|
|
|
pevent->cmdlines = cmdlines;
|
|
pevent->cmdlist = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *find_cmdline(struct pevent *pevent, int pid)
|
|
{
|
|
const struct cmdline *comm;
|
|
struct cmdline key;
|
|
|
|
if (!pid)
|
|
return "<idle>";
|
|
|
|
if (!pevent->cmdlines && cmdline_init(pevent))
|
|
return "<not enough memory for cmdlines!>";
|
|
|
|
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))
|
|
return 0;
|
|
|
|
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].comm = strdup(comm);
|
|
if (!cmdlines[pevent->cmdline_count].comm) {
|
|
free(cmdlines);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
cmdlines[pevent->cmdline_count].pid = pid;
|
|
|
|
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(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 = pevent->cmdlist;
|
|
|
|
pevent->cmdlist = item;
|
|
pevent->cmdline_count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pevent_register_trace_clock(struct pevent *pevent, const char *trace_clock)
|
|
{
|
|
pevent->trace_clock = strdup(trace_clock);
|
|
if (!pevent->trace_clock) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
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(sizeof(*func_map) * (pevent->func_count + 1));
|
|
if (!func_map)
|
|
return -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;
|
|
}
|
|
|
|
struct func_resolver {
|
|
pevent_func_resolver_t *func;
|
|
void *priv;
|
|
struct func_map map;
|
|
};
|
|
|
|
/**
|
|
* pevent_set_function_resolver - set an alternative function resolver
|
|
* @pevent: handle for the pevent
|
|
* @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
|
|
* pevent->funclist.
|
|
*/
|
|
int pevent_set_function_resolver(struct pevent *pevent,
|
|
pevent_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(pevent->func_resolver);
|
|
pevent->func_resolver = resolver;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* pevent_reset_function_resolver - reset alternative function resolver
|
|
* @pevent: handle for the pevent
|
|
*
|
|
* Stop using whatever alternative resolver was set, use the default
|
|
* one instead.
|
|
*/
|
|
void pevent_reset_function_resolver(struct pevent *pevent)
|
|
{
|
|
free(pevent->func_resolver);
|
|
pevent->func_resolver = NULL;
|
|
}
|
|
|
|
static struct func_map *
|
|
find_func(struct pevent *pevent, unsigned long long addr)
|
|
{
|
|
struct func_map *map;
|
|
|
|
if (!pevent->func_resolver)
|
|
return __find_func(pevent, addr);
|
|
|
|
map = &pevent->func_resolver->map;
|
|
map->mod = NULL;
|
|
map->addr = addr;
|
|
map->func = pevent->func_resolver->func(pevent->func_resolver->priv,
|
|
&map->addr, &map->mod);
|
|
if (map->func == NULL)
|
|
return NULL;
|
|
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* 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 = malloc(sizeof(*item));
|
|
|
|
if (!item)
|
|
return -1;
|
|
|
|
item->next = pevent->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;
|
|
|
|
pevent->funclist = item;
|
|
pevent->func_count++;
|
|
|
|
return 0;
|
|
|
|
out_free_func:
|
|
free(item->func);
|
|
item->func = NULL;
|
|
out_free:
|
|
free(item);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* 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 int printk_map_init(struct pevent *pevent)
|
|
{
|
|
struct printk_list *printklist;
|
|
struct printk_list *item;
|
|
struct printk_map *printk_map;
|
|
int i;
|
|
|
|
printk_map = malloc(sizeof(*printk_map) * (pevent->printk_count + 1));
|
|
if (!printk_map)
|
|
return -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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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))
|
|
return NULL;
|
|
|
|
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, const char *fmt,
|
|
unsigned long long addr)
|
|
{
|
|
struct printk_list *item = malloc(sizeof(*item));
|
|
char *p;
|
|
|
|
if (!item)
|
|
return -1;
|
|
|
|
item->next = pevent->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;
|
|
|
|
pevent->printklist = item;
|
|
pevent->printk_count++;
|
|
|
|
return 0;
|
|
|
|
out_free:
|
|
free(item);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
return calloc(1, sizeof(struct event_format));
|
|
}
|
|
|
|
static int add_event(struct pevent *pevent, struct event_format *event)
|
|
{
|
|
int i;
|
|
struct event_format **events = realloc(pevent->events, sizeof(event) *
|
|
(pevent->nr_events + 1));
|
|
if (!events)
|
|
return -1;
|
|
|
|
pevent->events = 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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_INT_ARRAY:
|
|
free_arg(arg->int_array.field);
|
|
free_arg(arg->int_array.count);
|
|
free_arg(arg->int_array.el_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_BITMASK:
|
|
free(arg->bitmask.bitmask);
|
|
break;
|
|
case PRINT_DYNAMIC_ARRAY:
|
|
case PRINT_DYNAMIC_ARRAY_LEN:
|
|
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:
|
|
if (asprintf(tok, "%c", ch) < 0)
|
|
return EVENT_ERROR;
|
|
|
|
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 unsigned int type_size(const char *name)
|
|
{
|
|
/* This covers all 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 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 {
|
|
unsigned int size_dynamic = 0;
|
|
|
|
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 = calloc(1, sizeof(*field));
|
|
if (!field)
|
|
goto fail;
|
|
|
|
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) {
|
|
do_warning_event(event, "%s: no type found", __func__);
|
|
goto fail;
|
|
}
|
|
field->name = field->alias = 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) {
|
|
do_warning_event(event, "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);
|
|
size_dynamic = type_size(field->name);
|
|
free_token(field->name);
|
|
strcat(field->type, brackets);
|
|
field->name = field->alias = 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;
|
|
|
|
if (strtoul(token, NULL, 0))
|
|
field->flags |= FIELD_IS_SIGNED;
|
|
|
|
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_DYNAMIC)
|
|
field->elementsize = size_dynamic;
|
|
else if (field->flags & FIELD_IS_STRING)
|
|
field->elementsize = 1;
|
|
else if (field->flags & FIELD_IS_LONG)
|
|
field->elementsize = event->pevent ?
|
|
event->pevent->long_size :
|
|
sizeof(long);
|
|
} 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);
|
|
|
|
/*
|
|
* For __print_symbolic() and __print_flags, we need to completely
|
|
* evaluate the first argument, which defines what to print next.
|
|
*/
|
|
static enum event_type
|
|
process_field_arg(struct event_format *event, struct print_arg *arg, char **tok)
|
|
{
|
|
enum event_type type;
|
|
|
|
type = process_arg(event, arg, tok);
|
|
|
|
while (type == EVENT_OP) {
|
|
type = process_op(event, arg, tok);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
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();
|
|
|
|
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 = PRINT_OP;
|
|
arg->op.left = left;
|
|
arg->op.right = right;
|
|
|
|
*tok = NULL;
|
|
type = process_arg(event, left, &token);
|
|
|
|
again:
|
|
if (type == EVENT_ERROR)
|
|
goto out_free;
|
|
|
|
/* 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();
|
|
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 EVENT_ERROR;
|
|
}
|
|
|
|
*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(token);
|
|
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]) {
|
|
do_warning_event(event, "bad op token %s", token);
|
|
goto out_free;
|
|
}
|
|
switch (token[0]) {
|
|
case '~':
|
|
case '!':
|
|
case '+':
|
|
case '-':
|
|
break;
|
|
default:
|
|
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 = 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 = PRINT_OP;
|
|
arg->op.op = token;
|
|
arg->op.left = left;
|
|
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) {
|
|
|
|
left = alloc_arg();
|
|
if (!left)
|
|
goto out_warn_free;
|
|
|
|
/* copy the top arg to the left */
|
|
*left = *arg;
|
|
|
|
arg->type = PRINT_OP;
|
|
arg->op.op = token;
|
|
arg->op.left = left;
|
|
arg->op.right = NULL;
|
|
|
|
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) {
|
|
do_warning_event(event, "bad pointer type");
|
|
goto out_free;
|
|
}
|
|
new_atom = realloc(left->atom.atom,
|
|
strlen(left->atom.atom) + 3);
|
|
if (!new_atom)
|
|
goto out_warn_free;
|
|
|
|
left->atom.atom = new_atom;
|
|
strcat(left->atom.atom, " *");
|
|
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 == EVENT_ERROR) {
|
|
free_arg(right);
|
|
/* token was freed in process_arg_token() via *tok */
|
|
token = NULL;
|
|
goto out_free;
|
|
}
|
|
|
|
if (right->type == PRINT_OP &&
|
|
get_op_prio(arg->op.op) < get_op_prio(right->op.op)) {
|
|
struct 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 = PRINT_OP;
|
|
arg->op.op = token;
|
|
arg->op.left = left;
|
|
|
|
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 |= 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_warn_free:
|
|
do_warning_event(event, "%s: not enough memory!", __func__);
|
|
out_free:
|
|
free_token(token);
|
|
*tok = NULL;
|
|
return EVENT_ERROR;
|
|
}
|
|
|
|
static enum event_type
|
|
process_entry(struct event_format *event __maybe_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 int alloc_and_process_delim(struct event_format *event, char *next_token,
|
|
struct print_arg **print_arg)
|
|
{
|
|
struct print_arg *field;
|
|
enum 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);
|
|
|
|
if (test_type_token(type, token, EVENT_DELIM, next_token)) {
|
|
errno = EINVAL;
|
|
ret = -1;
|
|
free_arg(field);
|
|
goto out_free_token;
|
|
}
|
|
|
|
*print_arg = field;
|
|
|
|
out_free_token:
|
|
free_token(token);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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(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"))
|
|
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) {
|
|
do_warning("expected type argument");
|
|
return 0;
|
|
}
|
|
|
|
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:
|
|
case PRINT_BITMASK:
|
|
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:
|
|
case PRINT_BITMASK:
|
|
default:
|
|
do_warning("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();
|
|
if (!arg)
|
|
goto out_free;
|
|
|
|
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 = 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, 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(&token);
|
|
} while (type == 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 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 = NULL;
|
|
|
|
memset(arg, 0, sizeof(*arg));
|
|
arg->type = 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 == EVENT_OP)
|
|
type = process_op(event, field, &token);
|
|
|
|
if (test_type_token(type, token, EVENT_DELIM, ","))
|
|
goto out_free_field;
|
|
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_field:
|
|
free_arg(field);
|
|
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 = NULL;
|
|
|
|
memset(arg, 0, sizeof(*arg));
|
|
arg->type = 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, EVENT_DELIM, ","))
|
|
goto out_free_field;
|
|
|
|
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_field:
|
|
free_arg(field);
|
|
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)
|
|
{
|
|
memset(arg, 0, sizeof(*arg));
|
|
arg->type = PRINT_HEX;
|
|
|
|
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(tok);
|
|
|
|
free_field:
|
|
free_arg(arg->hex.field);
|
|
out:
|
|
*tok = NULL;
|
|
return EVENT_ERROR;
|
|
}
|
|
|
|
static enum event_type
|
|
process_int_array(struct event_format *event, struct print_arg *arg, char **tok)
|
|
{
|
|
memset(arg, 0, sizeof(*arg));
|
|
arg->type = 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(tok);
|
|
|
|
free_size:
|
|
free_arg(arg->int_array.count);
|
|
free_field:
|
|
free_arg(arg->int_array.field);
|
|
out:
|
|
*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();
|
|
if (!arg) {
|
|
do_warning_event(event, "%s: not enough memory!", __func__);
|
|
*tok = NULL;
|
|
return EVENT_ERROR;
|
|
}
|
|
|
|
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_dynamic_array_len(struct event_format *event, struct print_arg *arg,
|
|
char **tok)
|
|
{
|
|
struct format_field *field;
|
|
enum event_type type;
|
|
char *token;
|
|
|
|
if (read_expect_type(EVENT_ITEM, &token) < 0)
|
|
goto out_free;
|
|
|
|
arg->type = PRINT_DYNAMIC_ARRAY_LEN;
|
|
|
|
/* 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_err;
|
|
|
|
type = read_token(&token);
|
|
*tok = token;
|
|
|
|
return type;
|
|
|
|
out_free:
|
|
free_token(token);
|
|
out_err:
|
|
*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) {
|
|
do_warning_event(event, "previous needed to be 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 = 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 __maybe_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 enum event_type
|
|
process_bitmask(struct event_format *event __maybe_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_BITMASK;
|
|
arg->bitmask.bitmask = token;
|
|
arg->bitmask.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;
|
|
|
|
if (!pevent)
|
|
return NULL;
|
|
|
|
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;
|
|
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();
|
|
if (!farg) {
|
|
do_warning_event(event, "%s: not enough memory!",
|
|
__func__);
|
|
return EVENT_ERROR;
|
|
}
|
|
|
|
type = process_arg(event, farg, &token);
|
|
if (i < (func->nr_args - 1)) {
|
|
if (type != 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 != 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(&token);
|
|
*tok = token;
|
|
|
|
return type;
|
|
|
|
err:
|
|
free_arg(farg);
|
|
free_token(token);
|
|
return EVENT_ERROR;
|
|
}
|
|
|
|
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, "__print_array") == 0) {
|
|
free_token(token);
|
|
return process_int_array(event, arg, tok);
|
|
}
|
|
if (strcmp(token, "__get_str") == 0) {
|
|
free_token(token);
|
|
return process_str(event, arg, tok);
|
|
}
|
|
if (strcmp(token, "__get_bitmask") == 0) {
|
|
free_token(token);
|
|
return process_bitmask(event, arg, tok);
|
|
}
|
|
if (strcmp(token, "__get_dynamic_array") == 0) {
|
|
free_token(token);
|
|
return process_dynamic_array(event, arg, tok);
|
|
}
|
|
if (strcmp(token, "__get_dynamic_array_len") == 0) {
|
|
free_token(token);
|
|
return process_dynamic_array_len(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_event(event, "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:
|
|
do_warning_event(event, "unexpected type %d", type);
|
|
return EVENT_ERROR;
|
|
}
|
|
*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();
|
|
if (!arg) {
|
|
do_warning_event(event, "%s: not enough memory!",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
|
|
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, 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) {
|
|
do_warning("no event_list!");
|
|
return -1;
|
|
}
|
|
|
|
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)
|
|
goto out_warning_field;
|
|
|
|
}
|
|
/* 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_INT_ARRAY:
|
|
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:
|
|
case PRINT_BITMASK:
|
|
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) {
|
|
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 */
|
|
}
|
|
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) {
|
|
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;
|
|
default:
|
|
goto out_warning_op;
|
|
}
|
|
break;
|
|
case PRINT_DYNAMIC_ARRAY_LEN:
|
|
offset = pevent_read_number(pevent,
|
|
data + arg->dynarray.field->offset,
|
|
arg->dynarray.field->size);
|
|
/*
|
|
* 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 = (unsigned long long)(offset >> 16);
|
|
break;
|
|
case PRINT_DYNAMIC_ARRAY:
|
|
/* Without [], we pass the address to the dynamic data */
|
|
offset = pevent_read_number(pevent,
|
|
data + arg->dynarray.field->offset,
|
|
arg->dynarray.field->size);
|
|
/*
|
|
* 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.
|
|
*/
|
|
offset &= 0xffff;
|
|
val = (unsigned long long)((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 pevent *pevent,
|
|
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
|
|
* endianess is very important.
|
|
*/
|
|
if (pevent->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);
|
|
}
|
|
|
|
static void print_str_arg(struct trace_seq *s, void *data, int size,
|
|
struct event_format *event, const char *format,
|
|
int len_arg, struct print_arg *arg)
|
|
{
|
|
struct pevent *pevent = event->pevent;
|
|
struct print_flag_sym *flag;
|
|
struct format_field *field;
|
|
struct printk_map *printk;
|
|
long long val, fval;
|
|
unsigned long long addr;
|
|
char *str;
|
|
unsigned char *hex;
|
|
int print;
|
|
int i, len;
|
|
|
|
switch (arg->type) {
|
|
case PRINT_NULL:
|
|
/* ?? */
|
|
return;
|
|
case PRINT_ATOM:
|
|
print_str_to_seq(s, format, len_arg, arg->atom.atom);
|
|
return;
|
|
case PRINT_FIELD:
|
|
field = arg->field.field;
|
|
if (!field) {
|
|
field = pevent_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 & FIELD_IS_ARRAY) &&
|
|
field->size == pevent->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 = (pevent->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(pevent, 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 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;
|
|
}
|
|
}
|
|
break;
|
|
case 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;
|
|
}
|
|
}
|
|
break;
|
|
case PRINT_HEX:
|
|
if (arg->hex.field->type == PRINT_DYNAMIC_ARRAY) {
|
|
unsigned long offset;
|
|
offset = pevent_read_number(pevent,
|
|
data + arg->hex.field->dynarray.field->offset,
|
|
arg->hex.field->dynarray.field->size);
|
|
hex = data + (offset & 0xffff);
|
|
} else {
|
|
field = arg->hex.field->field.field;
|
|
if (!field) {
|
|
str = arg->hex.field->field.name;
|
|
field = pevent_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)
|
|
trace_seq_putc(s, ' ');
|
|
trace_seq_printf(s, "%02x", hex[i]);
|
|
}
|
|
break;
|
|
|
|
case PRINT_INT_ARRAY: {
|
|
void *num;
|
|
int el_size;
|
|
|
|
if (arg->int_array.field->type == PRINT_DYNAMIC_ARRAY) {
|
|
unsigned long offset;
|
|
struct format_field *field =
|
|
arg->int_array.field->dynarray.field;
|
|
offset = pevent_read_number(pevent,
|
|
data + field->offset,
|
|
field->size);
|
|
num = data + (offset & 0xffff);
|
|
} else {
|
|
field = arg->int_array.field->field.field;
|
|
if (!field) {
|
|
str = arg->int_array.field->field.name;
|
|
field = pevent_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 PRINT_TYPE:
|
|
break;
|
|
case PRINT_STRING: {
|
|
int str_offset;
|
|
|
|
if (arg->string.offset == -1) {
|
|
struct format_field *f;
|
|
|
|
f = pevent_find_any_field(event, arg->string.string);
|
|
arg->string.offset = f->offset;
|
|
}
|
|
str_offset = data2host4(pevent, data + arg->string.offset);
|
|
str_offset &= 0xffff;
|
|
print_str_to_seq(s, format, len_arg, ((char *)data) + str_offset);
|
|
break;
|
|
}
|
|
case PRINT_BSTRING:
|
|
print_str_to_seq(s, format, len_arg, arg->string.string);
|
|
break;
|
|
case PRINT_BITMASK: {
|
|
int bitmask_offset;
|
|
int bitmask_size;
|
|
|
|
if (arg->bitmask.offset == -1) {
|
|
struct format_field *f;
|
|
|
|
f = pevent_find_any_field(event, arg->bitmask.bitmask);
|
|
arg->bitmask.offset = f->offset;
|
|
}
|
|
bitmask_offset = data2host4(pevent, data + arg->bitmask.offset);
|
|
bitmask_size = bitmask_offset >> 16;
|
|
bitmask_offset &= 0xffff;
|
|
print_bitmask_to_seq(pevent, s, format, len_arg,
|
|
data + bitmask_offset, bitmask_size);
|
|
break;
|
|
}
|
|
case 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 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 event_format *event, struct print_arg *arg)
|
|
{
|
|
struct pevent_function_handler *func_handle = arg->func.func;
|
|
struct pevent_func_params *param;
|
|
unsigned long long *args;
|
|
unsigned long long ret;
|
|
struct 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 PEVENT_FUNC_ARG_INT:
|
|
case PEVENT_FUNC_ARG_LONG:
|
|
case PEVENT_FUNC_ARG_PTR:
|
|
args[i] = eval_num_arg(data, size, event, farg);
|
|
break;
|
|
case PEVENT_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 print_arg *args)
|
|
{
|
|
struct print_arg *next;
|
|
|
|
while (args) {
|
|
next = args->next;
|
|
|
|
free_arg(args);
|
|
args = next;
|
|
}
|
|
}
|
|
|
|
static struct print_arg *make_bprint_args(char *fmt, void *data, int size, struct event_format *event)
|
|
{
|
|
struct pevent *pevent = event->pevent;
|
|
struct format_field *field, *ip_field;
|
|
struct print_arg *args, *arg, **next;
|
|
unsigned long long ip, val;
|
|
char *ptr;
|
|
void *bptr;
|
|
int vsize;
|
|
|
|
field = pevent->bprint_buf_field;
|
|
ip_field = pevent->bprint_ip_field;
|
|
|
|
if (!field) {
|
|
field = pevent_find_field(event, "buf");
|
|
if (!field) {
|
|
do_warning_event(event, "can't find buffer field for binary printk");
|
|
return NULL;
|
|
}
|
|
ip_field = pevent_find_field(event, "ip");
|
|
if (!ip_field) {
|
|
do_warning_event(event, "can't find ip field for binary printk");
|
|
return NULL;
|
|
}
|
|
pevent->bprint_buf_field = field;
|
|
pevent->bprint_ip_field = ip_field;
|
|
}
|
|
|
|
ip = pevent_read_number(pevent, 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 = 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 'z':
|
|
case 'Z':
|
|
ls = 1;
|
|
goto process_again;
|
|
case 'p':
|
|
ls = 1;
|
|
/* fall through */
|
|
case 'd':
|
|
case 'u':
|
|
case 'x':
|
|
case 'i':
|
|
switch (ls) {
|
|
case 0:
|
|
vsize = 4;
|
|
break;
|
|
case 1:
|
|
vsize = pevent->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 = pevent_read_number(pevent, 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 = 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':
|
|
arg = alloc_arg();
|
|
if (!arg) {
|
|
do_warning_event(event, "%s(%d): not enough memory!",
|
|
__func__, __LINE__);
|
|
goto out_free;
|
|
}
|
|
arg->next = NULL;
|
|
arg->type = PRINT_BSTRING;
|
|
arg->string.string = strdup(bptr);
|
|
if (!arg->string.string)
|
|
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 event_format *event)
|
|
{
|
|
struct pevent *pevent = event->pevent;
|
|
unsigned long long addr;
|
|
struct format_field *field;
|
|
struct printk_map *printk;
|
|
char *format;
|
|
|
|
field = pevent->bprint_fmt_field;
|
|
|
|
if (!field) {
|
|
field = pevent_find_field(event, "fmt");
|
|
if (!field) {
|
|
do_warning_event(event, "can't find format field for binary printk");
|
|
return NULL;
|
|
}
|
|
pevent->bprint_fmt_field = field;
|
|
}
|
|
|
|
addr = pevent_read_number(pevent, data + field->offset, field->size);
|
|
|
|
printk = find_printk(pevent, addr);
|
|
if (!printk) {
|
|
if (asprintf(&format, "%%pf: (NO FORMAT FOUND at %llx)\n", addr) < 0)
|
|
return NULL;
|
|
return format;
|
|
}
|
|
|
|
if (asprintf(&format, "%s: %s", "%pf", printk->printk) < 0)
|
|
return NULL;
|
|
|
|
return format;
|
|
}
|
|
|
|
static void print_mac_arg(struct trace_seq *s, int mac, void *data, int size,
|
|
struct event_format *event, struct print_arg *arg)
|
|
{
|
|
unsigned char *buf;
|
|
const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x";
|
|
|
|
if (arg->type == PRINT_FUNC) {
|
|
process_defined_func(s, data, size, event, arg);
|
|
return;
|
|
}
|
|
|
|
if (arg->type != PRINT_FIELD) {
|
|
trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d",
|
|
arg->type);
|
|
return;
|
|
}
|
|
|
|
if (mac == 'm')
|
|
fmt = "%.2x%.2x%.2x%.2x%.2x%.2x";
|
|
if (!arg->field.field) {
|
|
arg->field.field =
|
|
pevent_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;
|
|
}
|
|
}
|
|
if (arg->field.field->size != 6) {
|
|
trace_seq_printf(s, "INVALIDMAC");
|
|
return;
|
|
}
|
|
buf = data + arg->field.field->offset;
|
|
trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
|
|
}
|
|
|
|
static void print_ip4_addr(struct trace_seq *s, char i, unsigned char *buf)
|
|
{
|
|
const char *fmt;
|
|
|
|
if (i == 'i')
|
|
fmt = "%03d.%03d.%03d.%03d";
|
|
else
|
|
fmt = "%d.%d.%d.%d";
|
|
|
|
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', &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 event_format *event,
|
|
struct print_arg *arg)
|
|
{
|
|
unsigned char *buf;
|
|
|
|
if (arg->type == PRINT_FUNC) {
|
|
process_defined_func(s, data, size, event, arg);
|
|
return 0;
|
|
}
|
|
|
|
if (arg->type != PRINT_FIELD) {
|
|
trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
|
|
return 0;
|
|
}
|
|
|
|
if (!arg->field.field) {
|
|
arg->field.field =
|
|
pevent_find_any_field(event, arg->field.name);
|
|
if (!arg->field.field) {
|
|
do_warning("%s: field %s not found",
|
|
__func__, arg->field.name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
buf = data + arg->field.field->offset;
|
|
|
|
if (arg->field.field->size != 4) {
|
|
trace_seq_printf(s, "INVALIDIPv4");
|
|
return 0;
|
|
}
|
|
print_ip4_addr(s, i, buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int print_ipv6_arg(struct trace_seq *s, const char *ptr, char i,
|
|
void *data, int size, struct event_format *event,
|
|
struct 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 == PRINT_FUNC) {
|
|
process_defined_func(s, data, size, event, arg);
|
|
return rc;
|
|
}
|
|
|
|
if (arg->type != PRINT_FIELD) {
|
|
trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
|
|
return rc;
|
|
}
|
|
|
|
if (!arg->field.field) {
|
|
arg->field.field =
|
|
pevent_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 event_format *event,
|
|
struct print_arg *arg)
|
|
{
|
|
char have_c = 0, have_p = 0;
|
|
unsigned char *buf;
|
|
struct sockaddr_storage *sa;
|
|
int rc = 0;
|
|
|
|
/* pISpc */
|
|
if (i == 'I') {
|
|
if (*ptr == 'p') {
|
|
have_p = 1;
|
|
ptr++;
|
|
rc++;
|
|
}
|
|
if (*ptr == 'c') {
|
|
have_c = 1;
|
|
ptr++;
|
|
rc++;
|
|
}
|
|
}
|
|
|
|
if (arg->type == PRINT_FUNC) {
|
|
process_defined_func(s, data, size, event, arg);
|
|
return rc;
|
|
}
|
|
|
|
if (arg->type != PRINT_FIELD) {
|
|
trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type);
|
|
return rc;
|
|
}
|
|
|
|
if (!arg->field.field) {
|
|
arg->field.field =
|
|
pevent_find_any_field(event, arg->field.name);
|
|
if (!arg->field.field) {
|
|
do_warning("%s: field %s not found",
|
|
__func__, arg->field.name);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
sa = (struct sockaddr_storage *) (data + arg->field.field->offset);
|
|
|
|
if (sa->ss_family == AF_INET) {
|
|
struct sockaddr_in *sa4 = (struct sockaddr_in *) sa;
|
|
|
|
if (arg->field.field->size < sizeof(struct sockaddr_in)) {
|
|
trace_seq_printf(s, "INVALIDIPv4");
|
|
return rc;
|
|
}
|
|
|
|
print_ip4_addr(s, i, (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 (arg->field.field->size < 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 event_format *event,
|
|
struct print_arg *arg)
|
|
{
|
|
char i = *ptr; /* 'i' or 'I' */
|
|
char ver;
|
|
int rc = 0;
|
|
|
|
ptr++;
|
|
rc++;
|
|
|
|
ver = *ptr;
|
|
ptr++;
|
|
rc++;
|
|
|
|
switch (ver) {
|
|
case '4':
|
|
rc += print_ipv4_arg(s, ptr, i, data, size, event, arg);
|
|
break;
|
|
case '6':
|
|
rc += print_ipv6_arg(s, ptr, i, data, size, event, arg);
|
|
break;
|
|
case 'S':
|
|
rc += print_ipsa_arg(s, ptr, i, data, size, event, arg);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void pevent_print_field(struct trace_seq *s, void *data,
|
|
struct format_field *field)
|
|
{
|
|
unsigned long long val;
|
|
unsigned int offset, len, i;
|
|
struct pevent *pevent = field->event->pevent;
|
|
|
|
if (field->flags & FIELD_IS_ARRAY) {
|
|
offset = field->offset;
|
|
len = field->size;
|
|
if (field->flags & FIELD_IS_DYNAMIC) {
|
|
val = pevent_read_number(pevent, data + offset, len);
|
|
offset = val;
|
|
len = offset >> 16;
|
|
offset &= 0xffff;
|
|
}
|
|
if (field->flags & 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 &= ~FIELD_IS_STRING;
|
|
}
|
|
} else {
|
|
val = pevent_read_number(pevent, data + field->offset,
|
|
field->size);
|
|
if (field->flags & FIELD_IS_POINTER) {
|
|
trace_seq_printf(s, "0x%llx", val);
|
|
} else if (field->flags & 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 & 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 & FIELD_IS_LONG)
|
|
trace_seq_printf(s, "0x%llx", val);
|
|
else
|
|
trace_seq_printf(s, "%llu", val);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pevent_print_fields(struct trace_seq *s, void *data,
|
|
int size __maybe_unused, struct event_format *event)
|
|
{
|
|
struct format_field *field;
|
|
|
|
field = event->format.fields;
|
|
while (field) {
|
|
trace_seq_printf(s, " %s=", field->name);
|
|
pevent_print_field(s, data, field);
|
|
field = field->next;
|
|
}
|
|
}
|
|
|
|
static void pretty_print(struct trace_seq *s, void *data, int size, struct event_format *event)
|
|
{
|
|
struct pevent *pevent = event->pevent;
|
|
struct print_fmt *print_fmt = &event->print_fmt;
|
|
struct print_arg *arg = print_fmt->args;
|
|
struct print_arg *args = NULL;
|
|
const char *ptr = print_fmt->format;
|
|
unsigned long long val;
|
|
struct func_map *func;
|
|
const char *saveptr;
|
|
struct trace_seq p;
|
|
char *bprint_fmt = NULL;
|
|
char format[32];
|
|
int show_func;
|
|
int len_as_arg;
|
|
int len_arg;
|
|
int len;
|
|
int ls;
|
|
|
|
if (event->flags & EVENT_FL_FAILED) {
|
|
trace_seq_printf(s, "[FAILED TO PARSE]");
|
|
pevent_print_fields(s, data, size, event);
|
|
return;
|
|
}
|
|
|
|
if (event->flags & EVENT_FL_ISBPRINT) {
|
|
bprint_fmt = get_bprint_format(data, size, event);
|
|
args = make_bprint_args(bprint_fmt, data, size, event);
|
|
arg = args;
|
|
ptr = bprint_fmt;
|
|
}
|
|
|
|
for (; *ptr; ptr++) {
|
|
ls = 0;
|
|
if (*ptr == '\\') {
|
|
ptr++;
|
|
switch (*ptr) {
|
|
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, *ptr);
|
|
break;
|
|
}
|
|
|
|
} else if (*ptr == '%') {
|
|
saveptr = ptr;
|
|
show_func = 0;
|
|
len_as_arg = 0;
|
|
cont_process:
|
|
ptr++;
|
|
switch (*ptr) {
|
|
case '%':
|
|
trace_seq_putc(s, '%');
|
|
break;
|
|
case '#':
|
|
/* FIXME: need to handle properly */
|
|
goto cont_process;
|
|
case 'h':
|
|
ls--;
|
|
goto cont_process;
|
|
case 'l':
|
|
ls++;
|
|
goto cont_process;
|
|
case 'L':
|
|
ls = 2;
|
|
goto cont_process;
|
|
case '*':
|
|
/* The argument is the length. */
|
|
if (!arg) {
|
|
do_warning_event(event, "no argument match");
|
|
event->flags |= EVENT_FL_FAILED;
|
|
goto out_failed;
|
|
}
|
|
len_arg = eval_num_arg(data, size, event, arg);
|
|
len_as_arg = 1;
|
|
arg = arg->next;
|
|
goto cont_process;
|
|
case '.':
|
|
case 'z':
|
|
case 'Z':
|
|
case '0' ... '9':
|
|
case '-':
|
|
goto cont_process;
|
|
case 'p':
|
|
if (pevent->long_size == 4)
|
|
ls = 1;
|
|
else
|
|
ls = 2;
|
|
|
|
if (*(ptr+1) == 'F' || *(ptr+1) == 'f' ||
|
|
*(ptr+1) == 'S' || *(ptr+1) == 's') {
|
|
ptr++;
|
|
show_func = *ptr;
|
|
} else if (*(ptr+1) == 'M' || *(ptr+1) == 'm') {
|
|
print_mac_arg(s, *(ptr+1), data, size, event, arg);
|
|
ptr++;
|
|
arg = arg->next;
|
|
break;
|
|
} else if (*(ptr+1) == 'I' || *(ptr+1) == 'i') {
|
|
int n;
|
|
|
|
n = print_ip_arg(s, ptr+1, data, size, event, arg);
|
|
if (n > 0) {
|
|
ptr += n;
|
|
arg = arg->next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* fall through */
|
|
case 'd':
|
|
case 'i':
|
|
case 'x':
|
|
case 'X':
|
|
case 'u':
|
|
if (!arg) {
|
|
do_warning_event(event, "no argument match");
|
|
event->flags |= EVENT_FL_FAILED;
|
|
goto out_failed;
|
|
}
|
|
|
|
len = ((unsigned long)ptr + 1) -
|
|
(unsigned long)saveptr;
|
|
|
|
/* should never happen */
|
|
if (len > 31) {
|
|
do_warning_event(event, "bad format!");
|
|
event->flags |= EVENT_FL_FAILED;
|
|
len = 31;
|
|
}
|
|
|
|
memcpy(format, saveptr, len);
|
|
format[len] = 0;
|
|
|
|
val = eval_num_arg(data, size, event, arg);
|
|
arg = arg->next;
|
|
|
|
if (show_func) {
|
|
func = find_func(pevent, val);
|
|
if (func) {
|
|
trace_seq_puts(s, func->func);
|
|
if (show_func == 'F')
|
|
trace_seq_printf(s,
|
|
"+0x%llx",
|
|
val - func->addr);
|
|
break;
|
|
}
|
|
}
|
|
if (pevent->long_size == 8 && ls &&
|
|
sizeof(long) != 8) {
|
|
char *p;
|
|
|
|
/* make %l into %ll */
|
|
if (ls == 1 && (p = strchr(format, 'l')))
|
|
memmove(p+1, p, strlen(p)+1);
|
|
else if (strcmp(format, "%p") == 0)
|
|
strcpy(format, "0x%llx");
|
|
ls = 2;
|
|
}
|
|
switch (ls) {
|
|
case -2:
|
|
if (len_as_arg)
|
|
trace_seq_printf(s, format, len_arg, (char)val);
|
|
else
|
|
trace_seq_printf(s, format, (char)val);
|
|
break;
|
|
case -1:
|
|
if (len_as_arg)
|
|
trace_seq_printf(s, format, len_arg, (short)val);
|
|
else
|
|
trace_seq_printf(s, format, (short)val);
|
|
break;
|
|
case 0:
|
|
if (len_as_arg)
|
|
trace_seq_printf(s, format, len_arg, (int)val);
|
|
else
|
|
trace_seq_printf(s, format, (int)val);
|
|
break;
|
|
case 1:
|
|
if (len_as_arg)
|
|
trace_seq_printf(s, format, len_arg, (long)val);
|
|
else
|
|
trace_seq_printf(s, format, (long)val);
|
|
break;
|
|
case 2:
|
|
if (len_as_arg)
|
|
trace_seq_printf(s, format, len_arg,
|
|
(long long)val);
|
|
else
|
|
trace_seq_printf(s, format, (long long)val);
|
|
break;
|
|
default:
|
|
do_warning_event(event, "bad count (%d)", ls);
|
|
event->flags |= EVENT_FL_FAILED;
|
|
}
|
|
break;
|
|
case 's':
|
|
if (!arg) {
|
|
do_warning_event(event, "no matching argument");
|
|
event->flags |= EVENT_FL_FAILED;
|
|
goto out_failed;
|
|
}
|
|
|
|
len = ((unsigned long)ptr + 1) -
|
|
(unsigned long)saveptr;
|
|
|
|
/* should never happen */
|
|
if (len > 31) {
|
|
do_warning_event(event, "bad format!");
|
|
event->flags |= EVENT_FL_FAILED;
|
|
len = 31;
|
|
}
|
|
|
|
memcpy(format, saveptr, len);
|
|
format[len] = 0;
|
|
if (!len_as_arg)
|
|
len_arg = -1;
|
|
/* Use helper trace_seq */
|
|
trace_seq_init(&p);
|
|
print_str_arg(&p, data, size, event,
|
|
format, len_arg, arg);
|
|
trace_seq_terminate(&p);
|
|
trace_seq_puts(s, p.buffer);
|
|
trace_seq_destroy(&p);
|
|
arg = arg->next;
|
|
break;
|
|
default:
|
|
trace_seq_printf(s, ">%c<", *ptr);
|
|
|
|
}
|
|
} else
|
|
trace_seq_putc(s, *ptr);
|
|
}
|
|
|
|
if (event->flags & EVENT_FL_FAILED) {
|
|
out_failed:
|
|
trace_seq_printf(s, "[FAILED TO PARSE]");
|
|
}
|
|
|
|
if (args) {
|
|
free_args(args);
|
|
free(bprint_fmt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* pevent_data_lat_fmt - parse the data for the latency format
|
|
* @pevent: a handle to the pevent
|
|
* @s: the trace_seq to write to
|
|
* @record: the record to read from
|
|
*
|
|
* 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.
|
|
*/
|
|
void pevent_data_lat_fmt(struct pevent *pevent,
|
|
struct trace_seq *s, struct pevent_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;
|
|
unsigned int pc;
|
|
int lock_depth;
|
|
int migrate_disable;
|
|
int hardirq;
|
|
int softirq;
|
|
void *data = record->data;
|
|
|
|
lat_flags = parse_common_flags(pevent, data);
|
|
pc = parse_common_pc(pevent, data);
|
|
/* lock_depth may not always exist */
|
|
if (lock_depth_exists)
|
|
lock_depth = parse_common_lock_depth(pevent, data);
|
|
else if (check_lock_depth) {
|
|
lock_depth = parse_common_lock_depth(pevent, 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(pevent, data);
|
|
else if (check_migrate_disable) {
|
|
migrate_disable = parse_common_migrate_disable(pevent, 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(s, "%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)
|
|
trace_seq_printf(s, "%x", pc);
|
|
else
|
|
trace_seq_putc(s, '.');
|
|
|
|
if (migrate_disable_exists) {
|
|
if (migrate_disable < 0)
|
|
trace_seq_putc(s, '.');
|
|
else
|
|
trace_seq_printf(s, "%d", migrate_disable);
|
|
}
|
|
|
|
if (lock_depth_exists) {
|
|
if (lock_depth < 0)
|
|
trace_seq_putc(s, '.');
|
|
else
|
|
trace_seq_printf(s, "%d", lock_depth);
|
|
}
|
|
|
|
trace_seq_terminate(s);
|
|
}
|
|
|
|
/**
|
|
* pevent_data_type - parse out the given event type
|
|
* @pevent: a handle to the pevent
|
|
* @rec: the record to read from
|
|
*
|
|
* This returns the event id from the @rec.
|
|
*/
|
|
int pevent_data_type(struct pevent *pevent, struct pevent_record *rec)
|
|
{
|
|
return trace_parse_common_type(pevent, rec->data);
|
|
}
|
|
|
|
/**
|
|
* pevent_data_event_from_type - find the event by a given type
|
|
* @pevent: a handle to the pevent
|
|
* @type: the type of the event.
|
|
*
|
|
* This returns the event form a given @type;
|
|
*/
|
|
struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type)
|
|
{
|
|
return pevent_find_event(pevent, type);
|
|
}
|
|
|
|
/**
|
|
* pevent_data_pid - parse the PID from raw data
|
|
* @pevent: a handle to the pevent
|
|
* @rec: the record to parse
|
|
*
|
|
* This returns the PID from a raw data.
|
|
*/
|
|
int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec)
|
|
{
|
|
return parse_common_pid(pevent, rec->data);
|
|
}
|
|
|
|
/**
|
|
* pevent_data_comm_from_pid - return the command line from PID
|
|
* @pevent: a handle to the pevent
|
|
* @pid: the PID of the task to search for
|
|
*
|
|
* This returns a pointer to the command line that has the given
|
|
* @pid.
|
|
*/
|
|
const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid)
|
|
{
|
|
const char *comm;
|
|
|
|
comm = find_cmdline(pevent, pid);
|
|
return comm;
|
|
}
|
|
|
|
static struct cmdline *
|
|
pid_from_cmdlist(struct pevent *pevent, const char *comm, struct cmdline *next)
|
|
{
|
|
struct cmdline_list *cmdlist = (struct cmdline_list *)next;
|
|
|
|
if (cmdlist)
|
|
cmdlist = cmdlist->next;
|
|
else
|
|
cmdlist = pevent->cmdlist;
|
|
|
|
while (cmdlist && strcmp(cmdlist->comm, comm) != 0)
|
|
cmdlist = cmdlist->next;
|
|
|
|
return (struct cmdline *)cmdlist;
|
|
}
|
|
|
|
/**
|
|
* pevent_data_pid_from_comm - return the pid from a given comm
|
|
* @pevent: a handle to the pevent
|
|
* @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 paramater, and then it will find the
|
|
* next pid.
|
|
* Also, it does a linear seach, so it may be slow.
|
|
*/
|
|
struct cmdline *pevent_data_pid_from_comm(struct pevent *pevent, const char *comm,
|
|
struct cmdline *next)
|
|
{
|
|
struct cmdline *cmdline;
|
|
|
|
/*
|
|
* If the cmdlines have not been converted yet, then use
|
|
* the list.
|
|
*/
|
|
if (!pevent->cmdlines)
|
|
return pid_from_cmdlist(pevent, comm, next);
|
|
|
|
if (next) {
|
|
/*
|
|
* The next pointer could have been still from
|
|
* a previous call before cmdlines were created
|
|
*/
|
|
if (next < pevent->cmdlines ||
|
|
next >= pevent->cmdlines + pevent->cmdline_count)
|
|
next = NULL;
|
|
else
|
|
cmdline = next++;
|
|
}
|
|
|
|
if (!next)
|
|
cmdline = pevent->cmdlines;
|
|
|
|
while (cmdline < pevent->cmdlines + pevent->cmdline_count) {
|
|
if (strcmp(cmdline->comm, comm) == 0)
|
|
return cmdline;
|
|
cmdline++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* pevent_cmdline_pid - return the pid associated to a given cmdline
|
|
* @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 pevent_cmdline_pid(struct pevent *pevent, struct 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 (!pevent->cmdlines ||
|
|
cmdline < pevent->cmdlines ||
|
|
cmdline >= pevent->cmdlines + pevent->cmdline_count)
|
|
return cmdlist->pid;
|
|
|
|
return cmdline->pid;
|
|
}
|
|
|
|
/**
|
|
* pevent_data_comm_from_pid - parse the data into the print format
|
|
* @s: the trace_seq to write to
|
|
* @event: the handle to the event
|
|
* @record: the record to read from
|
|
*
|
|
* This parses the raw @data using the given @event information and
|
|
* writes the print format into the trace_seq.
|
|
*/
|
|
void pevent_event_info(struct trace_seq *s, struct event_format *event,
|
|
struct pevent_record *record)
|
|
{
|
|
int print_pretty = 1;
|
|
|
|
if (event->pevent->print_raw || (event->flags & EVENT_FL_PRINTRAW))
|
|
pevent_print_fields(s, record->data, record->size, event);
|
|
else {
|
|
|
|
if (event->handler && !(event->flags & 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);
|
|
}
|
|
|
|
static bool is_timestamp_in_us(char *trace_clock, bool use_trace_clock)
|
|
{
|
|
if (!use_trace_clock)
|
|
return true;
|
|
|
|
if (!strcmp(trace_clock, "local") || !strcmp(trace_clock, "global")
|
|
|| !strcmp(trace_clock, "uptime") || !strcmp(trace_clock, "perf"))
|
|
return true;
|
|
|
|
/* trace_clock is setting in tsc or counter mode */
|
|
return false;
|
|
}
|
|
|
|
void pevent_print_event(struct pevent *pevent, struct trace_seq *s,
|
|
struct pevent_record *record, bool use_trace_clock)
|
|
{
|
|
static const char *spaces = " "; /* 20 spaces */
|
|
struct event_format *event;
|
|
unsigned long secs;
|
|
unsigned long usecs;
|
|
unsigned long nsecs;
|
|
const char *comm;
|
|
void *data = record->data;
|
|
int type;
|
|
int pid;
|
|
int len;
|
|
int p;
|
|
bool use_usec_format;
|
|
|
|
use_usec_format = is_timestamp_in_us(pevent->trace_clock,
|
|
use_trace_clock);
|
|
if (use_usec_format) {
|
|
secs = record->ts / NSECS_PER_SEC;
|
|
nsecs = record->ts - secs * NSECS_PER_SEC;
|
|
}
|
|
|
|
if (record->size < 0) {
|
|
do_warning("ug! negative record size %d", record->size);
|
|
return;
|
|
}
|
|
|
|
type = trace_parse_common_type(pevent, data);
|
|
|
|
event = pevent_find_event(pevent, type);
|
|
if (!event) {
|
|
do_warning("ug! no event found for type %d", type);
|
|
return;
|
|
}
|
|
|
|
pid = parse_common_pid(pevent, data);
|
|
comm = find_cmdline(pevent, pid);
|
|
|
|
if (pevent->latency_format) {
|
|
trace_seq_printf(s, "%8.8s-%-5d %3d",
|
|
comm, pid, record->cpu);
|
|
pevent_data_lat_fmt(pevent, s, record);
|
|
} else
|
|
trace_seq_printf(s, "%16s-%-5d [%03d]", comm, pid, record->cpu);
|
|
|
|
if (use_usec_format) {
|
|
if (pevent->flags & PEVENT_NSEC_OUTPUT) {
|
|
usecs = nsecs;
|
|
p = 9;
|
|
} else {
|
|
usecs = (nsecs + 500) / NSECS_PER_USEC;
|
|
p = 6;
|
|
}
|
|
|
|
trace_seq_printf(s, " %5lu.%0*lu: %s: ",
|
|
secs, p, usecs, event->name);
|
|
} else
|
|
trace_seq_printf(s, " %12llu: %s: ",
|
|
record->ts, event->name);
|
|
|
|
/* Space out the event names evenly. */
|
|
len = strlen(event->name);
|
|
if (len < 20)
|
|
trace_seq_printf(s, "%.*s", 20 - len, spaces);
|
|
|
|
pevent_event_info(s, event, record);
|
|
}
|
|
|
|
static int events_id_cmp(const void *a, const void *b)
|
|
{
|
|
struct event_format * const * ea = a;
|
|
struct event_format * 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 event_format * const * ea = a;
|
|
struct event_format * 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 event_format * const * ea = a;
|
|
struct event_format * 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);
|
|
}
|
|
|
|
struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type sort_type)
|
|
{
|
|
struct event_format **events;
|
|
int (*sort)(const void *a, const void *b);
|
|
|
|
events = pevent->sort_events;
|
|
|
|
if (events && pevent->last_type == sort_type)
|
|
return events;
|
|
|
|
if (!events) {
|
|
events = malloc(sizeof(*events) * (pevent->nr_events + 1));
|
|
if (!events)
|
|
return NULL;
|
|
|
|
memcpy(events, pevent->events, sizeof(*events) * pevent->nr_events);
|
|
events[pevent->nr_events] = NULL;
|
|
|
|
pevent->sort_events = events;
|
|
|
|
/* the internal events are sorted by id */
|
|
if (sort_type == EVENT_SORT_ID) {
|
|
pevent->last_type = sort_type;
|
|
return events;
|
|
}
|
|
}
|
|
|
|
switch (sort_type) {
|
|
case EVENT_SORT_ID:
|
|
sort = events_id_cmp;
|
|
break;
|
|
case EVENT_SORT_NAME:
|
|
sort = events_name_cmp;
|
|
break;
|
|
case EVENT_SORT_SYSTEM:
|
|
sort = events_system_cmp;
|
|
break;
|
|
default:
|
|
return events;
|
|
}
|
|
|
|
qsort(events, pevent->nr_events, sizeof(*events), sort);
|
|
pevent->last_type = sort_type;
|
|
|
|
return events;
|
|
}
|
|
|
|
static struct format_field **
|
|
get_event_fields(const char *type, const char *name,
|
|
int count, struct format_field *list)
|
|
{
|
|
struct format_field **fields;
|
|
struct 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;
|
|
}
|
|
|
|
/**
|
|
* pevent_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 format_field **pevent_event_common_fields(struct event_format *event)
|
|
{
|
|
return get_event_fields("common", event->name,
|
|
event->format.nr_common,
|
|
event->format.common_fields);
|
|
}
|
|
|
|
/**
|
|
* pevent_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 format_field **pevent_event_fields(struct event_format *event)
|
|
{
|
|
return get_event_fields("event", event->name,
|
|
event->format.nr_fields,
|
|
event->format.fields);
|
|
}
|
|
|
|
static void print_fields(struct trace_seq *s, struct 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 print_arg *args)
|
|
{
|
|
int print_paren = 1;
|
|
struct trace_seq s;
|
|
|
|
switch (args->type) {
|
|
case PRINT_NULL:
|
|
printf("null");
|
|
break;
|
|
case PRINT_ATOM:
|
|
printf("%s", args->atom.atom);
|
|
break;
|
|
case PRINT_FIELD:
|
|
printf("REC->%s", args->field.name);
|
|
break;
|
|
case 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 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 PRINT_HEX:
|
|
printf("__print_hex(");
|
|
print_args(args->hex.field);
|
|
printf(", ");
|
|
print_args(args->hex.size);
|
|
printf(")");
|
|
break;
|
|
case 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 PRINT_STRING:
|
|
case PRINT_BSTRING:
|
|
printf("__get_str(%s)", args->string.string);
|
|
break;
|
|
case PRINT_BITMASK:
|
|
printf("__get_bitmask(%s)", args->bitmask.bitmask);
|
|
break;
|
|
case PRINT_TYPE:
|
|
printf("(%s)", args->typecast.type);
|
|
print_args(args->typecast.item);
|
|
break;
|
|
case 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(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 = input_buf_ptr;
|
|
save_input_buf_siz = input_buf_siz;
|
|
|
|
if (read_expected(EVENT_ITEM, "field") < 0)
|
|
return;
|
|
if (read_expected(EVENT_OP, ":") < 0)
|
|
return;
|
|
|
|
/* type */
|
|
if (read_expect_type(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(EVENT_ITEM, field) < 0)
|
|
return;
|
|
} else {
|
|
if (read_expect_type(EVENT_ITEM, &token) < 0)
|
|
goto fail;
|
|
if (strcmp(token, field) != 0)
|
|
goto discard;
|
|
free_token(token);
|
|
}
|
|
|
|
if (read_expected(EVENT_OP, ";") < 0)
|
|
return;
|
|
if (read_expected(EVENT_ITEM, "offset") < 0)
|
|
return;
|
|
if (read_expected(EVENT_OP, ":") < 0)
|
|
return;
|
|
if (read_expect_type(EVENT_ITEM, &token) < 0)
|
|
goto fail;
|
|
*offset = atoi(token);
|
|
free_token(token);
|
|
if (read_expected(EVENT_OP, ";") < 0)
|
|
return;
|
|
if (read_expected(EVENT_ITEM, "size") < 0)
|
|
return;
|
|
if (read_expected(EVENT_OP, ":") < 0)
|
|
return;
|
|
if (read_expect_type(EVENT_ITEM, &token) < 0)
|
|
goto fail;
|
|
*size = atoi(token);
|
|
free_token(token);
|
|
if (read_expected(EVENT_OP, ";") < 0)
|
|
return;
|
|
type = read_token(&token);
|
|
if (type != EVENT_NEWLINE) {
|
|
/* newer versions of the kernel have a "signed" type */
|
|
if (type != EVENT_ITEM)
|
|
goto fail;
|
|
|
|
if (strcmp(token, "signed") != 0)
|
|
goto fail;
|
|
|
|
free_token(token);
|
|
|
|
if (read_expected(EVENT_OP, ":") < 0)
|
|
return;
|
|
|
|
if (read_expect_type(EVENT_ITEM, &token))
|
|
goto fail;
|
|
|
|
free_token(token);
|
|
if (read_expected(EVENT_OP, ";") < 0)
|
|
return;
|
|
|
|
if (read_expect_type(EVENT_NEWLINE, &token))
|
|
goto fail;
|
|
}
|
|
fail:
|
|
free_token(token);
|
|
return;
|
|
|
|
discard:
|
|
input_buf_ptr = save_input_buf_ptr;
|
|
input_buf_siz = save_input_buf_siz;
|
|
*offset = 0;
|
|
*size = 0;
|
|
free_token(token);
|
|
}
|
|
|
|
/**
|
|
* pevent_parse_header_page - parse the data stored in the header page
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_parse_header_page(struct pevent *pevent, 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.
|
|
*/
|
|
pevent->header_page_ts_size = sizeof(long long);
|
|
pevent->header_page_size_size = long_size;
|
|
pevent->header_page_data_offset = sizeof(long long) + long_size;
|
|
pevent->old_format = 1;
|
|
return -1;
|
|
}
|
|
init_input_buf(buf, size);
|
|
|
|
parse_header_field("timestamp", &pevent->header_page_ts_offset,
|
|
&pevent->header_page_ts_size, 1);
|
|
parse_header_field("commit", &pevent->header_page_size_offset,
|
|
&pevent->header_page_size_size, 1);
|
|
parse_header_field("overwrite", &pevent->header_page_overwrite,
|
|
&ignore, 0);
|
|
parse_header_field("data", &pevent->header_page_data_offset,
|
|
&pevent->header_page_data_size, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int event_matches(struct event_format *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 pevent *pevent, struct event_format *event)
|
|
{
|
|
struct event_handler *handle, **next;
|
|
|
|
for (next = &pevent->handlers; *next;
|
|
next = &(*next)->next) {
|
|
handle = *next;
|
|
if (event_matches(event, handle->id,
|
|
handle->sys_name,
|
|
handle->event_name))
|
|
break;
|
|
}
|
|
|
|
if (!(*next))
|
|
return 0;
|
|
|
|
pr_stat("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;
|
|
}
|
|
|
|
/**
|
|
* __pevent_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
|
|
*/
|
|
enum pevent_errno __pevent_parse_format(struct event_format **eventp,
|
|
struct pevent *pevent, const char *buf,
|
|
unsigned long size, const char *sys)
|
|
{
|
|
struct event_format *event;
|
|
int ret;
|
|
|
|
init_input_buf(buf, size);
|
|
|
|
*eventp = event = alloc_event();
|
|
if (!event)
|
|
return PEVENT_ERRNO__MEM_ALLOC_FAILED;
|
|
|
|
event->name = event_read_name();
|
|
if (!event->name) {
|
|
/* Bad event? */
|
|
ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
|
|
goto event_alloc_failed;
|
|
}
|
|
|
|
if (strcmp(sys, "ftrace") == 0) {
|
|
event->flags |= EVENT_FL_ISFTRACE;
|
|
|
|
if (strcmp(event->name, "bprint") == 0)
|
|
event->flags |= EVENT_FL_ISBPRINT;
|
|
}
|
|
|
|
event->id = event_read_id();
|
|
if (event->id < 0) {
|
|
ret = PEVENT_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 = PEVENT_ERRNO__MEM_ALLOC_FAILED;
|
|
goto event_alloc_failed;
|
|
}
|
|
|
|
/* Add pevent to event so that it can be referenced */
|
|
event->pevent = pevent;
|
|
|
|
ret = event_read_format(event);
|
|
if (ret < 0) {
|
|
ret = PEVENT_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 (pevent && find_event_handle(pevent, event))
|
|
show_warning = 0;
|
|
|
|
ret = event_read_print(event);
|
|
show_warning = 1;
|
|
|
|
if (ret < 0) {
|
|
ret = PEVENT_ERRNO__READ_PRINT_FAILED;
|
|
goto event_parse_failed;
|
|
}
|
|
|
|
if (!ret && (event->flags & EVENT_FL_ISFTRACE)) {
|
|
struct format_field *field;
|
|
struct 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 |= EVENT_FL_FAILED;
|
|
return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED;
|
|
}
|
|
arg->type = PRINT_FIELD;
|
|
arg->field.name = strdup(field->name);
|
|
if (!arg->field.name) {
|
|
event->flags |= EVENT_FL_FAILED;
|
|
free_arg(arg);
|
|
return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED;
|
|
}
|
|
arg->field.field = field;
|
|
*list = arg;
|
|
list = &arg->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
|
|
event_parse_failed:
|
|
event->flags |= EVENT_FL_FAILED;
|
|
return ret;
|
|
|
|
event_alloc_failed:
|
|
free(event->system);
|
|
free(event->name);
|
|
free(event);
|
|
*eventp = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static enum pevent_errno
|
|
__pevent_parse_event(struct pevent *pevent,
|
|
struct event_format **eventp,
|
|
const char *buf, unsigned long size,
|
|
const char *sys)
|
|
{
|
|
int ret = __pevent_parse_format(eventp, pevent, buf, size, sys);
|
|
struct event_format *event = *eventp;
|
|
|
|
if (event == NULL)
|
|
return ret;
|
|
|
|
if (pevent && add_event(pevent, event)) {
|
|
ret = PEVENT_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:
|
|
pevent_free_format(event);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* pevent_parse_format - parse the event format
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_errno pevent_parse_format(struct pevent *pevent,
|
|
struct event_format **eventp,
|
|
const char *buf,
|
|
unsigned long size, const char *sys)
|
|
{
|
|
return __pevent_parse_event(pevent, eventp, buf, size, sys);
|
|
}
|
|
|
|
/**
|
|
* pevent_parse_event - parse the event format
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf,
|
|
unsigned long size, const char *sys)
|
|
{
|
|
struct event_format *event = NULL;
|
|
return __pevent_parse_event(pevent, &event, buf, size, sys);
|
|
}
|
|
|
|
#undef _PE
|
|
#define _PE(code, str) str
|
|
static const char * const pevent_error_str[] = {
|
|
PEVENT_ERRORS
|
|
};
|
|
#undef _PE
|
|
|
|
int pevent_strerror(struct pevent *pevent __maybe_unused,
|
|
enum pevent_errno errnum, char *buf, size_t buflen)
|
|
{
|
|
int idx;
|
|
const char *msg;
|
|
|
|
if (errnum >= 0) {
|
|
msg = strerror_r(errnum, buf, buflen);
|
|
if (msg != buf) {
|
|
size_t len = strlen(msg);
|
|
memcpy(buf, msg, min(buflen - 1, len));
|
|
*(buf + min(buflen - 1, len)) = '\0';
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (errnum <= __PEVENT_ERRNO__START ||
|
|
errnum >= __PEVENT_ERRNO__END)
|
|
return -1;
|
|
|
|
idx = errnum - __PEVENT_ERRNO__START - 1;
|
|
msg = pevent_error_str[idx];
|
|
snprintf(buf, buflen, "%s", msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int get_field_val(struct trace_seq *s, struct format_field *field,
|
|
const char *name, struct pevent_record *record,
|
|
unsigned long long *val, int err)
|
|
{
|
|
if (!field) {
|
|
if (err)
|
|
trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
|
|
return -1;
|
|
}
|
|
|
|
if (pevent_read_number_field(field, record->data, val)) {
|
|
if (err)
|
|
trace_seq_printf(s, " %s=INVALID", name);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* pevent_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 *pevent_get_field_raw(struct trace_seq *s, struct event_format *event,
|
|
const char *name, struct pevent_record *record,
|
|
int *len, int err)
|
|
{
|
|
struct format_field *field;
|
|
void *data = record->data;
|
|
unsigned offset;
|
|
int dummy;
|
|
|
|
if (!event)
|
|
return NULL;
|
|
|
|
field = pevent_find_field(event, name);
|
|
|
|
if (!field) {
|
|
if (err)
|
|
trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
|
|
return NULL;
|
|
}
|
|
|
|
/* Allow @len to be NULL */
|
|
if (!len)
|
|
len = &dummy;
|
|
|
|
offset = field->offset;
|
|
if (field->flags & FIELD_IS_DYNAMIC) {
|
|
offset = pevent_read_number(event->pevent,
|
|
data + offset, field->size);
|
|
*len = offset >> 16;
|
|
offset &= 0xffff;
|
|
} else
|
|
*len = field->size;
|
|
|
|
return data + offset;
|
|
}
|
|
|
|
/**
|
|
* pevent_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 pevent_get_field_val(struct trace_seq *s, struct event_format *event,
|
|
const char *name, struct pevent_record *record,
|
|
unsigned long long *val, int err)
|
|
{
|
|
struct format_field *field;
|
|
|
|
if (!event)
|
|
return -1;
|
|
|
|
field = pevent_find_field(event, name);
|
|
|
|
return get_field_val(s, field, name, record, val, err);
|
|
}
|
|
|
|
/**
|
|
* pevent_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 pevent_get_common_field_val(struct trace_seq *s, struct event_format *event,
|
|
const char *name, struct pevent_record *record,
|
|
unsigned long long *val, int err)
|
|
{
|
|
struct format_field *field;
|
|
|
|
if (!event)
|
|
return -1;
|
|
|
|
field = pevent_find_common_field(event, name);
|
|
|
|
return get_field_val(s, field, name, record, val, err);
|
|
}
|
|
|
|
/**
|
|
* pevent_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 pevent_get_any_field_val(struct trace_seq *s, struct event_format *event,
|
|
const char *name, struct pevent_record *record,
|
|
unsigned long long *val, int err)
|
|
{
|
|
struct format_field *field;
|
|
|
|
if (!event)
|
|
return -1;
|
|
|
|
field = pevent_find_any_field(event, name);
|
|
|
|
return get_field_val(s, field, name, record, val, err);
|
|
}
|
|
|
|
/**
|
|
* pevent_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: 0 on success, -1 field not found, or 1 if buffer is full.
|
|
*/
|
|
int pevent_print_num_field(struct trace_seq *s, const char *fmt,
|
|
struct event_format *event, const char *name,
|
|
struct pevent_record *record, int err)
|
|
{
|
|
struct format_field *field = pevent_find_field(event, name);
|
|
unsigned long long val;
|
|
|
|
if (!field)
|
|
goto failed;
|
|
|
|
if (pevent_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;
|
|
}
|
|
|
|
/**
|
|
* pevent_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: 0 on success, -1 field not found, or 1 if buffer is full.
|
|
*/
|
|
int pevent_print_func_field(struct trace_seq *s, const char *fmt,
|
|
struct event_format *event, const char *name,
|
|
struct pevent_record *record, int err)
|
|
{
|
|
struct format_field *field = pevent_find_field(event, name);
|
|
struct pevent *pevent = event->pevent;
|
|
unsigned long long val;
|
|
struct func_map *func;
|
|
char tmp[128];
|
|
|
|
if (!field)
|
|
goto failed;
|
|
|
|
if (pevent_read_number_field(field, record->data, &val))
|
|
goto failed;
|
|
|
|
func = find_func(pevent, 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 pevent_function_handler *func)
|
|
{
|
|
struct pevent_func_params *params;
|
|
|
|
free(func->name);
|
|
|
|
while (func->params) {
|
|
params = func->params;
|
|
func->params = params->next;
|
|
free(params);
|
|
}
|
|
|
|
free(func);
|
|
}
|
|
|
|
/**
|
|
* pevent_register_print_function - register a helper function
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_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 pevent_func_arg_type enums that
|
|
* must end with PEVENT_FUNC_ARG_VOID.
|
|
*/
|
|
int pevent_register_print_function(struct pevent *pevent,
|
|
pevent_func_handler func,
|
|
enum pevent_func_arg_type ret_type,
|
|
char *name, ...)
|
|
{
|
|
struct pevent_function_handler *func_handle;
|
|
struct pevent_func_params **next_param;
|
|
struct pevent_func_params *param;
|
|
enum pevent_func_arg_type type;
|
|
va_list ap;
|
|
int ret;
|
|
|
|
func_handle = find_func_handler(pevent, name);
|
|
if (func_handle) {
|
|
/*
|
|
* This is most like caused by the users own
|
|
* plugins updating the function. This overrides the
|
|
* system defaults.
|
|
*/
|
|
pr_stat("override of function helper '%s'", name);
|
|
remove_func_handler(pevent, name);
|
|
}
|
|
|
|
func_handle = calloc(1, sizeof(*func_handle));
|
|
if (!func_handle) {
|
|
do_warning("Failed to allocate function handler");
|
|
return PEVENT_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 PEVENT_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
|
|
next_param = &(func_handle->params);
|
|
va_start(ap, name);
|
|
for (;;) {
|
|
type = va_arg(ap, enum pevent_func_arg_type);
|
|
if (type == PEVENT_FUNC_ARG_VOID)
|
|
break;
|
|
|
|
if (type >= PEVENT_FUNC_ARG_MAX_TYPES) {
|
|
do_warning("Invalid argument type %d", type);
|
|
ret = PEVENT_ERRNO__INVALID_ARG_TYPE;
|
|
goto out_free;
|
|
}
|
|
|
|
param = malloc(sizeof(*param));
|
|
if (!param) {
|
|
do_warning("Failed to allocate function param");
|
|
ret = PEVENT_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 = pevent->func_handlers;
|
|
pevent->func_handlers = func_handle;
|
|
|
|
return 0;
|
|
out_free:
|
|
va_end(ap);
|
|
free_func_handle(func_handle);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* pevent_unregister_print_function - unregister a helper function
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_unregister_print_function(struct pevent *pevent,
|
|
pevent_func_handler func, char *name)
|
|
{
|
|
struct pevent_function_handler *func_handle;
|
|
|
|
func_handle = find_func_handler(pevent, name);
|
|
if (func_handle && func_handle->func == func) {
|
|
remove_func_handler(pevent, name);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static struct event_format *pevent_search_event(struct pevent *pevent, int id,
|
|
const char *sys_name,
|
|
const char *event_name)
|
|
{
|
|
struct event_format *event;
|
|
|
|
if (id >= 0) {
|
|
/* search by id */
|
|
event = pevent_find_event(pevent, 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 = pevent_find_event_by_name(pevent, sys_name, event_name);
|
|
if (!event)
|
|
return NULL;
|
|
}
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* pevent_register_event_handler - register a way to parse an event
|
|
* @pevent: the handle to the pevent
|
|
* @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.
|
|
*/
|
|
int pevent_register_event_handler(struct pevent *pevent, int id,
|
|
const char *sys_name, const char *event_name,
|
|
pevent_event_handler_func func, void *context)
|
|
{
|
|
struct event_format *event;
|
|
struct event_handler *handle;
|
|
|
|
event = pevent_search_event(pevent, id, sys_name, event_name);
|
|
if (event == NULL)
|
|
goto not_found;
|
|
|
|
pr_stat("overriding event (%d) %s:%s with new print handler",
|
|
event->id, event->system, event->name);
|
|
|
|
event->handler = func;
|
|
event->context = context;
|
|
return 0;
|
|
|
|
not_found:
|
|
/* Save for later use. */
|
|
handle = calloc(1, sizeof(*handle));
|
|
if (!handle) {
|
|
do_warning("Failed to allocate event handler");
|
|
return PEVENT_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 PEVENT_ERRNO__MEM_ALLOC_FAILED;
|
|
}
|
|
|
|
handle->func = func;
|
|
handle->next = pevent->handlers;
|
|
pevent->handlers = handle;
|
|
handle->context = context;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int handle_matches(struct event_handler *handler, int id,
|
|
const char *sys_name, const char *event_name,
|
|
pevent_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;
|
|
}
|
|
|
|
/**
|
|
* pevent_unregister_event_handler - unregister an existing event handler
|
|
* @pevent: the handle to the pevent
|
|
* @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 pevent_unregister_event_handler(struct pevent *pevent, int id,
|
|
const char *sys_name, const char *event_name,
|
|
pevent_event_handler_func func, void *context)
|
|
{
|
|
struct event_format *event;
|
|
struct event_handler *handle;
|
|
struct event_handler **next;
|
|
|
|
event = pevent_search_event(pevent, id, sys_name, event_name);
|
|
if (event == NULL)
|
|
goto not_found;
|
|
|
|
if (event->handler == func && event->context == context) {
|
|
pr_stat("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 = &pevent->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;
|
|
}
|
|
|
|
/**
|
|
* pevent_alloc - create a pevent handle
|
|
*/
|
|
struct pevent *pevent_alloc(void)
|
|
{
|
|
struct pevent *pevent = calloc(1, sizeof(*pevent));
|
|
|
|
if (pevent)
|
|
pevent->ref_count = 1;
|
|
|
|
return pevent;
|
|
}
|
|
|
|
void pevent_ref(struct pevent *pevent)
|
|
{
|
|
pevent->ref_count++;
|
|
}
|
|
|
|
void pevent_free_format_field(struct 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 format_field *field)
|
|
{
|
|
struct format_field *next;
|
|
|
|
while (field) {
|
|
next = field->next;
|
|
pevent_free_format_field(field);
|
|
field = next;
|
|
}
|
|
}
|
|
|
|
static void free_formats(struct format *format)
|
|
{
|
|
free_format_fields(format->common_fields);
|
|
free_format_fields(format->fields);
|
|
}
|
|
|
|
void pevent_free_format(struct event_format *event)
|
|
{
|
|
free(event->name);
|
|
free(event->system);
|
|
|
|
free_formats(&event->format);
|
|
|
|
free(event->print_fmt.format);
|
|
free_args(event->print_fmt.args);
|
|
|
|
free(event);
|
|
}
|
|
|
|
/**
|
|
* pevent_free - free a pevent handle
|
|
* @pevent: the pevent handle to free
|
|
*/
|
|
void pevent_free(struct pevent *pevent)
|
|
{
|
|
struct cmdline_list *cmdlist, *cmdnext;
|
|
struct func_list *funclist, *funcnext;
|
|
struct printk_list *printklist, *printknext;
|
|
struct pevent_function_handler *func_handler;
|
|
struct event_handler *handle;
|
|
int i;
|
|
|
|
if (!pevent)
|
|
return;
|
|
|
|
cmdlist = pevent->cmdlist;
|
|
funclist = pevent->funclist;
|
|
printklist = pevent->printklist;
|
|
|
|
pevent->ref_count--;
|
|
if (pevent->ref_count)
|
|
return;
|
|
|
|
if (pevent->cmdlines) {
|
|
for (i = 0; i < pevent->cmdline_count; i++)
|
|
free(pevent->cmdlines[i].comm);
|
|
free(pevent->cmdlines);
|
|
}
|
|
|
|
while (cmdlist) {
|
|
cmdnext = cmdlist->next;
|
|
free(cmdlist->comm);
|
|
free(cmdlist);
|
|
cmdlist = cmdnext;
|
|
}
|
|
|
|
if (pevent->func_map) {
|
|
for (i = 0; i < (int)pevent->func_count; i++) {
|
|
free(pevent->func_map[i].func);
|
|
free(pevent->func_map[i].mod);
|
|
}
|
|
free(pevent->func_map);
|
|
}
|
|
|
|
while (funclist) {
|
|
funcnext = funclist->next;
|
|
free(funclist->func);
|
|
free(funclist->mod);
|
|
free(funclist);
|
|
funclist = funcnext;
|
|
}
|
|
|
|
while (pevent->func_handlers) {
|
|
func_handler = pevent->func_handlers;
|
|
pevent->func_handlers = func_handler->next;
|
|
free_func_handle(func_handler);
|
|
}
|
|
|
|
if (pevent->printk_map) {
|
|
for (i = 0; i < (int)pevent->printk_count; i++)
|
|
free(pevent->printk_map[i].printk);
|
|
free(pevent->printk_map);
|
|
}
|
|
|
|
while (printklist) {
|
|
printknext = printklist->next;
|
|
free(printklist->printk);
|
|
free(printklist);
|
|
printklist = printknext;
|
|
}
|
|
|
|
for (i = 0; i < pevent->nr_events; i++)
|
|
pevent_free_format(pevent->events[i]);
|
|
|
|
while (pevent->handlers) {
|
|
handle = pevent->handlers;
|
|
pevent->handlers = handle->next;
|
|
free_handler(handle);
|
|
}
|
|
|
|
free(pevent->trace_clock);
|
|
free(pevent->events);
|
|
free(pevent->sort_events);
|
|
free(pevent->func_resolver);
|
|
|
|
free(pevent);
|
|
}
|
|
|
|
void pevent_unref(struct pevent *pevent)
|
|
{
|
|
pevent_free(pevent);
|
|
}
|