diff options
Diffstat (limited to 'tools/lib')
83 files changed, 15067 insertions, 0 deletions
diff --git a/tools/lib/api/Makefile b/tools/lib/api/Makefile new file mode 100644 index 00000000000..ce00f7ee645 --- /dev/null +++ b/tools/lib/api/Makefile @@ -0,0 +1,44 @@ +include ../../scripts/Makefile.include +include ../../perf/config/utilities.mak		# QUIET_CLEAN + +CC = $(CROSS_COMPILE)gcc +AR = $(CROSS_COMPILE)ar + +# guard against environment variables +LIB_H= +LIB_OBJS= + +LIB_H += fs/debugfs.h +LIB_H += fs/fs.h + +LIB_OBJS += $(OUTPUT)fs/debugfs.o +LIB_OBJS += $(OUTPUT)fs/fs.o + +LIBFILE = libapikfs.a + +CFLAGS = -ggdb3 -Wall -Wextra -std=gnu99 -Werror -O6 -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) -fPIC +EXTLIBS = -lelf -lpthread -lrt -lm +ALL_CFLAGS = $(CFLAGS) $(BASIC_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +ALL_LDFLAGS = $(LDFLAGS) + +RM = rm -f + +$(LIBFILE): $(LIB_OBJS) +	$(QUIET_AR)$(RM) $@ && $(AR) rcs $(OUTPUT)$@ $(LIB_OBJS) + +$(LIB_OBJS): $(LIB_H) + +libapi_dirs: +	$(QUIET_MKDIR)mkdir -p $(OUTPUT)fs/ + +$(OUTPUT)%.o: %.c libapi_dirs +	$(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $< +$(OUTPUT)%.s: %.c libapi_dirs +	$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< +$(OUTPUT)%.o: %.S libapi_dirs +	$(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $< + +clean: +	$(call QUIET_CLEAN, libapi) $(RM) $(LIB_OBJS) $(LIBFILE) + +.PHONY: clean diff --git a/tools/lib/api/fs/debugfs.c b/tools/lib/api/fs/debugfs.c new file mode 100644 index 00000000000..a74fba6d774 --- /dev/null +++ b/tools/lib/api/fs/debugfs.c @@ -0,0 +1,100 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/vfs.h> +#include <sys/mount.h> +#include <linux/kernel.h> + +#include "debugfs.h" + +char debugfs_mountpoint[PATH_MAX + 1] = "/sys/kernel/debug"; + +static const char * const debugfs_known_mountpoints[] = { +	"/sys/kernel/debug", +	"/debug", +	0, +}; + +static bool debugfs_found; + +/* find the path to the mounted debugfs */ +const char *debugfs_find_mountpoint(void) +{ +	const char * const *ptr; +	char type[100]; +	FILE *fp; + +	if (debugfs_found) +		return (const char *)debugfs_mountpoint; + +	ptr = debugfs_known_mountpoints; +	while (*ptr) { +		if (debugfs_valid_mountpoint(*ptr) == 0) { +			debugfs_found = true; +			strcpy(debugfs_mountpoint, *ptr); +			return debugfs_mountpoint; +		} +		ptr++; +	} + +	/* give up and parse /proc/mounts */ +	fp = fopen("/proc/mounts", "r"); +	if (fp == NULL) +		return NULL; + +	while (fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n", +		      debugfs_mountpoint, type) == 2) { +		if (strcmp(type, "debugfs") == 0) +			break; +	} +	fclose(fp); + +	if (strcmp(type, "debugfs") != 0) +		return NULL; + +	debugfs_found = true; + +	return debugfs_mountpoint; +} + +/* verify that a mountpoint is actually a debugfs instance */ + +int debugfs_valid_mountpoint(const char *debugfs) +{ +	struct statfs st_fs; + +	if (statfs(debugfs, &st_fs) < 0) +		return -ENOENT; +	else if (st_fs.f_type != (long) DEBUGFS_MAGIC) +		return -ENOENT; + +	return 0; +} + +/* mount the debugfs somewhere if it's not mounted */ +char *debugfs_mount(const char *mountpoint) +{ +	/* see if it's already mounted */ +	if (debugfs_find_mountpoint()) +		goto out; + +	/* if not mounted and no argument */ +	if (mountpoint == NULL) { +		/* see if environment variable set */ +		mountpoint = getenv(PERF_DEBUGFS_ENVIRONMENT); +		/* if no environment variable, use default */ +		if (mountpoint == NULL) +			mountpoint = "/sys/kernel/debug"; +	} + +	if (mount(NULL, mountpoint, "debugfs", 0, NULL) < 0) +		return NULL; + +	/* save the mountpoint */ +	debugfs_found = true; +	strncpy(debugfs_mountpoint, mountpoint, sizeof(debugfs_mountpoint)); +out: +	return debugfs_mountpoint; +} diff --git a/tools/lib/api/fs/debugfs.h b/tools/lib/api/fs/debugfs.h new file mode 100644 index 00000000000..f19d3df9609 --- /dev/null +++ b/tools/lib/api/fs/debugfs.h @@ -0,0 +1,29 @@ +#ifndef __API_DEBUGFS_H__ +#define __API_DEBUGFS_H__ + +#define _STR(x) #x +#define STR(x) _STR(x) + +/* + * On most systems <limits.h> would have given us this, but  not on some systems + * (e.g. GNU/Hurd). + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifndef DEBUGFS_MAGIC +#define DEBUGFS_MAGIC          0x64626720 +#endif + +#ifndef PERF_DEBUGFS_ENVIRONMENT +#define PERF_DEBUGFS_ENVIRONMENT "PERF_DEBUGFS_DIR" +#endif + +const char *debugfs_find_mountpoint(void); +int debugfs_valid_mountpoint(const char *debugfs); +char *debugfs_mount(const char *mountpoint); + +extern char debugfs_mountpoint[]; + +#endif /* __API_DEBUGFS_H__ */ diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c new file mode 100644 index 00000000000..c1b49c36a95 --- /dev/null +++ b/tools/lib/api/fs/fs.c @@ -0,0 +1,165 @@ +/* TODO merge/factor in debugfs.c here */ + +#include <ctype.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/vfs.h> + +#include "debugfs.h" +#include "fs.h" + +static const char * const sysfs__fs_known_mountpoints[] = { +	"/sys", +	0, +}; + +static const char * const procfs__known_mountpoints[] = { +	"/proc", +	0, +}; + +struct fs { +	const char		*name; +	const char * const	*mounts; +	char			 path[PATH_MAX + 1]; +	bool			 found; +	long			 magic; +}; + +enum { +	FS__SYSFS  = 0, +	FS__PROCFS = 1, +}; + +static struct fs fs__entries[] = { +	[FS__SYSFS] = { +		.name	= "sysfs", +		.mounts	= sysfs__fs_known_mountpoints, +		.magic	= SYSFS_MAGIC, +	}, +	[FS__PROCFS] = { +		.name	= "proc", +		.mounts	= procfs__known_mountpoints, +		.magic	= PROC_SUPER_MAGIC, +	}, +}; + +static bool fs__read_mounts(struct fs *fs) +{ +	bool found = false; +	char type[100]; +	FILE *fp; + +	fp = fopen("/proc/mounts", "r"); +	if (fp == NULL) +		return NULL; + +	while (!found && +	       fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n", +		      fs->path, type) == 2) { + +		if (strcmp(type, fs->name) == 0) +			found = true; +	} + +	fclose(fp); +	return fs->found = found; +} + +static int fs__valid_mount(const char *fs, long magic) +{ +	struct statfs st_fs; + +	if (statfs(fs, &st_fs) < 0) +		return -ENOENT; +	else if (st_fs.f_type != magic) +		return -ENOENT; + +	return 0; +} + +static bool fs__check_mounts(struct fs *fs) +{ +	const char * const *ptr; + +	ptr = fs->mounts; +	while (*ptr) { +		if (fs__valid_mount(*ptr, fs->magic) == 0) { +			fs->found = true; +			strcpy(fs->path, *ptr); +			return true; +		} +		ptr++; +	} + +	return false; +} + +static void mem_toupper(char *f, size_t len) +{ +	while (len) { +		*f = toupper(*f); +		f++; +		len--; +	} +} + +/* + * Check for "NAME_PATH" environment variable to override fs location (for + * testing). This matches the recommendation in Documentation/sysfs-rules.txt + * for SYSFS_PATH. + */ +static bool fs__env_override(struct fs *fs) +{ +	char *override_path; +	size_t name_len = strlen(fs->name); +	/* name + "_PATH" + '\0' */ +	char upper_name[name_len + 5 + 1]; +	memcpy(upper_name, fs->name, name_len); +	mem_toupper(upper_name, name_len); +	strcpy(&upper_name[name_len], "_PATH"); + +	override_path = getenv(upper_name); +	if (!override_path) +		return false; + +	fs->found = true; +	strncpy(fs->path, override_path, sizeof(fs->path)); +	return true; +} + +static const char *fs__get_mountpoint(struct fs *fs) +{ +	if (fs__env_override(fs)) +		return fs->path; + +	if (fs__check_mounts(fs)) +		return fs->path; + +	if (fs__read_mounts(fs)) +		return fs->path; + +	return NULL; +} + +static const char *fs__mountpoint(int idx) +{ +	struct fs *fs = &fs__entries[idx]; + +	if (fs->found) +		return (const char *)fs->path; + +	return fs__get_mountpoint(fs); +} + +#define FS__MOUNTPOINT(name, idx)	\ +const char *name##__mountpoint(void)	\ +{					\ +	return fs__mountpoint(idx);	\ +} + +FS__MOUNTPOINT(sysfs,  FS__SYSFS); +FS__MOUNTPOINT(procfs, FS__PROCFS); diff --git a/tools/lib/api/fs/fs.h b/tools/lib/api/fs/fs.h new file mode 100644 index 00000000000..cb7049551f3 --- /dev/null +++ b/tools/lib/api/fs/fs.h @@ -0,0 +1,14 @@ +#ifndef __API_FS__ +#define __API_FS__ + +#ifndef SYSFS_MAGIC +#define SYSFS_MAGIC            0x62656572 +#endif + +#ifndef PROC_SUPER_MAGIC +#define PROC_SUPER_MAGIC       0x9fa0 +#endif + +const char *sysfs__mountpoint(void); +const char *procfs__mountpoint(void); +#endif /* __API_FS__ */ diff --git a/tools/lib/lockdep/Makefile b/tools/lib/lockdep/Makefile new file mode 100644 index 00000000000..52f9279c6c1 --- /dev/null +++ b/tools/lib/lockdep/Makefile @@ -0,0 +1,243 @@ +# file format version +FILE_VERSION = 1 + +LIBLOCKDEP_VERSION=$(shell make --no-print-directory -sC ../../.. kernelversion) + +# Makefiles suck: This macro sets a default value of $(2) for the +# variable named by $(1), unless the variable has been set by +# environment or command line. This is necessary for CC and AR +# because make sets default values, so the simpler ?= approach +# won't work as expected. +define allow-override +  $(if $(or $(findstring environment,$(origin $(1))),\ +            $(findstring command line,$(origin $(1)))),,\ +    $(eval $(1) = $(2))) +endef + +# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. +$(call allow-override,CC,$(CROSS_COMPILE)gcc) +$(call allow-override,AR,$(CROSS_COMPILE)ar) + +INSTALL = install + +# Use DESTDIR for installing into a different root directory. +# This is useful for building a package. The program will be +# installed in this directory as if it was the root directory. +# Then the build tool can move it later. +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +prefix ?= /usr/local +libdir_relative = lib +libdir = $(prefix)/$(libdir_relative) +bindir_relative = bin +bindir = $(prefix)/$(bindir_relative) + +export DESTDIR DESTDIR_SQ INSTALL + +# copy a bit from Linux kbuild + +ifeq ("$(origin V)", "command line") +  VERBOSE = $(V) +endif +ifndef VERBOSE +  VERBOSE = 0 +endif + +ifeq ("$(origin O)", "command line") +  BUILD_OUTPUT := $(O) +endif + +ifeq ($(BUILD_SRC),) +ifneq ($(BUILD_OUTPUT),) + +define build_output +	$(if $(VERBOSE:1=),@)$(MAKE) -C $(BUILD_OUTPUT)	\ +	BUILD_SRC=$(CURDIR) -f $(CURDIR)/Makefile $1 +endef + +saved-output := $(BUILD_OUTPUT) +BUILD_OUTPUT := $(shell cd $(BUILD_OUTPUT) && /bin/pwd) +$(if $(BUILD_OUTPUT),, \ +     $(error output directory "$(saved-output)" does not exist)) + +all: sub-make + +gui: force +	$(call build_output, all_cmd) + +$(filter-out gui,$(MAKECMDGOALS)): sub-make + +sub-make: force +	$(call build_output, $(MAKECMDGOALS)) + + +# Leave processing to above invocation of make +skip-makefile := 1 + +endif # BUILD_OUTPUT +endif # BUILD_SRC + +# We process the rest of the Makefile if this is the final invocation of make +ifeq ($(skip-makefile),) + +srctree		:= $(realpath $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR))) +objtree		:= $(realpath $(CURDIR)) +src		:= $(srctree) +obj		:= $(objtree) + +export prefix libdir bindir src obj + +# Shell quotes +libdir_SQ = $(subst ','\'',$(libdir)) +bindir_SQ = $(subst ','\'',$(bindir)) + +LIB_FILE = liblockdep.a liblockdep.so.$(LIBLOCKDEP_VERSION) +BIN_FILE = lockdep + +CONFIG_INCLUDES = +CONFIG_LIBS	= +CONFIG_FLAGS	= + +OBJ		= $@ +N		= + +export Q VERBOSE + +INCLUDES = -I. -I/usr/local/include -I./uinclude -I./include -I../../include $(CONFIG_INCLUDES) + +# Set compile option CFLAGS if not set elsewhere +CFLAGS ?= -g -DCONFIG_LOCKDEP -DCONFIG_STACKTRACE -DCONFIG_PROVE_LOCKING -DBITS_PER_LONG=__WORDSIZE -DLIBLOCKDEP_VERSION='"$(LIBLOCKDEP_VERSION)"' -rdynamic -O0 -g + +override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ) + +ifeq ($(VERBOSE),1) +  Q = +  print_compile = +  print_app_build = +  print_fpic_compile = +  print_shared_lib_compile = +  print_install = +else +  Q = @ +  print_compile =		echo '  CC                 '$(OBJ); +  print_app_build =		echo '  BUILD              '$(OBJ); +  print_fpic_compile =		echo '  CC FPIC            '$(OBJ); +  print_shared_lib_compile =	echo '  BUILD SHARED LIB   '$(OBJ); +  print_static_lib_build =	echo '  BUILD STATIC LIB   '$(OBJ); +  print_install =		echo '  INSTALL     '$1'	to	$(DESTDIR_SQ)$2'; +endif + +do_fpic_compile =					\ +	($(print_fpic_compile)				\ +	$(CC) -c $(CFLAGS) $(EXT) -fPIC $< -o $@) + +do_app_build =						\ +	($(print_app_build)				\ +	$(CC) $^ -rdynamic -o $@ $(CONFIG_LIBS) $(LIBS)) + +do_compile_shared_library =			\ +	($(print_shared_lib_compile)		\ +	$(CC) --shared $^ -o $@ -lpthread -ldl -Wl,-soname='"$@"';$(shell ln -s $@ liblockdep.so)) + +do_build_static_lib =				\ +	($(print_static_lib_build)		\ +	$(RM) $@;  $(AR) rcs $@ $^) + + +define do_compile +	$(print_compile)						\ +	$(CC) -c $(CFLAGS) $(EXT) $< -o $(obj)/$@; +endef + +$(obj)/%.o: $(src)/%.c +	$(Q)$(call do_compile) + +%.o: $(src)/%.c +	$(Q)$(call do_compile) + +PEVENT_LIB_OBJS = common.o lockdep.o preload.o rbtree.o + +ALL_OBJS = $(PEVENT_LIB_OBJS) + +CMD_TARGETS = $(LIB_FILE) + +TARGETS = $(CMD_TARGETS) + + +all: all_cmd + +all_cmd: $(CMD_TARGETS) + +liblockdep.so.$(LIBLOCKDEP_VERSION): $(PEVENT_LIB_OBJS) +	$(Q)$(do_compile_shared_library) + +liblockdep.a: $(PEVENT_LIB_OBJS) +	$(Q)$(do_build_static_lib) + +$(PEVENT_LIB_OBJS): %.o: $(src)/%.c +	$(Q)$(do_fpic_compile) + +## make deps + +all_objs := $(sort $(ALL_OBJS)) +all_deps := $(all_objs:%.o=.%.d) + +# let .d file also depends on the source and header files +define check_deps +		@set -e; $(RM) $@; \ +		$(CC) -MM $(CFLAGS) $< > $@.$$$$; \ +		sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ +		$(RM) $@.$$$$ +endef + +$(all_deps): .%.d: $(src)/%.c +	$(Q)$(call check_deps) + +$(all_objs) : %.o : .%.d + +dep_includes := $(wildcard $(all_deps)) + +ifneq ($(dep_includes),) + include $(dep_includes) +endif + +### Detect environment changes +TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):$(ARCH):$(CROSS_COMPILE) + +tags:	force +	$(RM) tags +	find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \ +	--regex-c++='/_PE\(([^,)]*).*/PEVENT_ERRNO__\1/' + +TAGS:	force +	$(RM) TAGS +	find . -name '*.[ch]' | xargs etags \ +	--regex='/_PE(\([^,)]*\).*/PEVENT_ERRNO__\1/' + +define do_install +	$(print_install)				\ +	if [ ! -d '$(DESTDIR_SQ)$2' ]; then		\ +		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2';	\ +	fi;						\ +	$(INSTALL) $1 '$(DESTDIR_SQ)$2' +endef + +install_lib: all_cmd +	$(Q)$(call do_install,$(LIB_FILE),$(libdir_SQ)) +	$(Q)$(call do_install,$(BIN_FILE),$(bindir_SQ)) + +install: install_lib + +clean: +	$(RM) *.o *~ $(TARGETS) *.a *liblockdep*.so* $(VERSION_FILES) .*.d +	$(RM) tags TAGS + +endif # skip-makefile + +PHONY += force +force: + +# Declare the contents of the .PHONY variable as phony.  We keep that +# information in a variable so we can use it in if_changed and friends. +.PHONY: $(PHONY) diff --git a/tools/lib/lockdep/common.c b/tools/lib/lockdep/common.c new file mode 100644 index 00000000000..8ef602f18a3 --- /dev/null +++ b/tools/lib/lockdep/common.c @@ -0,0 +1,33 @@ +#include <stddef.h> +#include <stdbool.h> +#include <linux/compiler.h> +#include <linux/lockdep.h> +#include <unistd.h> +#include <sys/syscall.h> + +static __thread struct task_struct current_obj; + +/* lockdep wants these */ +bool debug_locks = true; +bool debug_locks_silent; + +__attribute__((constructor)) static void liblockdep_init(void) +{ +	lockdep_init(); +} + +__attribute__((destructor)) static void liblockdep_exit(void) +{ +	debug_check_no_locks_held(¤t_obj); +} + +struct task_struct *__curr(void) +{ +	if (current_obj.pid == 0) { +		/* Makes lockdep output pretty */ +		prctl(PR_GET_NAME, current_obj.comm); +		current_obj.pid = syscall(__NR_gettid); +	} + +	return ¤t_obj; +} diff --git a/tools/lib/lockdep/include/liblockdep/common.h b/tools/lib/lockdep/include/liblockdep/common.h new file mode 100644 index 00000000000..0bda630027c --- /dev/null +++ b/tools/lib/lockdep/include/liblockdep/common.h @@ -0,0 +1,50 @@ +#ifndef _LIBLOCKDEP_COMMON_H +#define _LIBLOCKDEP_COMMON_H + +#include <pthread.h> + +#define NR_LOCKDEP_CACHING_CLASSES 2 +#define MAX_LOCKDEP_SUBCLASSES 8UL + +#ifndef CALLER_ADDR0 +#define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0)) +#endif + +#ifndef _RET_IP_ +#define _RET_IP_ CALLER_ADDR0 +#endif + +#ifndef _THIS_IP_ +#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; }) +#endif + +struct lockdep_subclass_key { +	char __one_byte; +}; + +struct lock_class_key { +	struct lockdep_subclass_key subkeys[MAX_LOCKDEP_SUBCLASSES]; +}; + +struct lockdep_map { +	struct lock_class_key	*key; +	struct lock_class	*class_cache[NR_LOCKDEP_CACHING_CLASSES]; +	const char		*name; +#ifdef CONFIG_LOCK_STAT +	int			cpu; +	unsigned long		ip; +#endif +}; + +void lockdep_init_map(struct lockdep_map *lock, const char *name, +			struct lock_class_key *key, int subclass); +void lock_acquire(struct lockdep_map *lock, unsigned int subclass, +			int trylock, int read, int check, +			struct lockdep_map *nest_lock, unsigned long ip); +void lock_release(struct lockdep_map *lock, int nested, +			unsigned long ip); + +#define STATIC_LOCKDEP_MAP_INIT(_name, _key) \ +	{ .name = (_name), .key = (void *)(_key), } + +#endif diff --git a/tools/lib/lockdep/include/liblockdep/mutex.h b/tools/lib/lockdep/include/liblockdep/mutex.h new file mode 100644 index 00000000000..ee53a42818c --- /dev/null +++ b/tools/lib/lockdep/include/liblockdep/mutex.h @@ -0,0 +1,70 @@ +#ifndef _LIBLOCKDEP_MUTEX_H +#define _LIBLOCKDEP_MUTEX_H + +#include <pthread.h> +#include "common.h" + +struct liblockdep_pthread_mutex { +	pthread_mutex_t mutex; +	struct lockdep_map dep_map; +}; + +typedef struct liblockdep_pthread_mutex liblockdep_pthread_mutex_t; + +#define LIBLOCKDEP_PTHREAD_MUTEX_INITIALIZER(mtx)			\ +		(const struct liblockdep_pthread_mutex) {		\ +	.mutex = PTHREAD_MUTEX_INITIALIZER,				\ +	.dep_map = STATIC_LOCKDEP_MAP_INIT(#mtx, &((&(mtx))->dep_map)),	\ +} + +static inline int __mutex_init(liblockdep_pthread_mutex_t *lock, +				const char *name, +				struct lock_class_key *key, +				const pthread_mutexattr_t *__mutexattr) +{ +	lockdep_init_map(&lock->dep_map, name, key, 0); +	return pthread_mutex_init(&lock->mutex, __mutexattr); +} + +#define liblockdep_pthread_mutex_init(mutex, mutexattr)		\ +({								\ +	static struct lock_class_key __key;			\ +								\ +	__mutex_init((mutex), #mutex, &__key, (mutexattr));	\ +}) + +static inline int liblockdep_pthread_mutex_lock(liblockdep_pthread_mutex_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_mutex_lock(&lock->mutex); +} + +static inline int liblockdep_pthread_mutex_unlock(liblockdep_pthread_mutex_t *lock) +{ +	lock_release(&lock->dep_map, 0, (unsigned long)_RET_IP_); +	return pthread_mutex_unlock(&lock->mutex); +} + +static inline int liblockdep_pthread_mutex_trylock(liblockdep_pthread_mutex_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_mutex_trylock(&lock->mutex) == 0 ? 1 : 0; +} + +static inline int liblockdep_pthread_mutex_destroy(liblockdep_pthread_mutex_t *lock) +{ +	return pthread_mutex_destroy(&lock->mutex); +} + +#ifdef __USE_LIBLOCKDEP + +#define pthread_mutex_t         liblockdep_pthread_mutex_t +#define pthread_mutex_init      liblockdep_pthread_mutex_init +#define pthread_mutex_lock      liblockdep_pthread_mutex_lock +#define pthread_mutex_unlock    liblockdep_pthread_mutex_unlock +#define pthread_mutex_trylock   liblockdep_pthread_mutex_trylock +#define pthread_mutex_destroy   liblockdep_pthread_mutex_destroy + +#endif + +#endif diff --git a/tools/lib/lockdep/include/liblockdep/rwlock.h b/tools/lib/lockdep/include/liblockdep/rwlock.h new file mode 100644 index 00000000000..4ec03f86155 --- /dev/null +++ b/tools/lib/lockdep/include/liblockdep/rwlock.h @@ -0,0 +1,86 @@ +#ifndef _LIBLOCKDEP_RWLOCK_H +#define _LIBLOCKDEP_RWLOCK_H + +#include <pthread.h> +#include "common.h" + +struct liblockdep_pthread_rwlock { +	pthread_rwlock_t rwlock; +	struct lockdep_map dep_map; +}; + +typedef struct liblockdep_pthread_rwlock liblockdep_pthread_rwlock_t; + +#define LIBLOCKDEP_PTHREAD_RWLOCK_INITIALIZER(rwl)			\ +		(struct liblockdep_pthread_rwlock) {			\ +	.rwlock = PTHREAD_RWLOCK_INITIALIZER,				\ +	.dep_map = STATIC_LOCKDEP_MAP_INIT(#rwl, &((&(rwl))->dep_map)),	\ +} + +static inline int __rwlock_init(liblockdep_pthread_rwlock_t *lock, +				const char *name, +				struct lock_class_key *key, +				const pthread_rwlockattr_t *attr) +{ +	lockdep_init_map(&lock->dep_map, name, key, 0); + +	return pthread_rwlock_init(&lock->rwlock, attr); +} + +#define liblockdep_pthread_rwlock_init(lock, attr)		\ +({							\ +	static struct lock_class_key __key;		\ +							\ +	__rwlock_init((lock), #lock, &__key, (attr));	\ +}) + +static inline int liblockdep_pthread_rwlock_rdlock(liblockdep_pthread_rwlock_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 0, 2, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_rwlock_rdlock(&lock->rwlock); + +} + +static inline int liblockdep_pthread_rwlock_unlock(liblockdep_pthread_rwlock_t *lock) +{ +	lock_release(&lock->dep_map, 0, (unsigned long)_RET_IP_); +	return pthread_rwlock_unlock(&lock->rwlock); +} + +static inline int liblockdep_pthread_rwlock_wrlock(liblockdep_pthread_rwlock_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_rwlock_wrlock(&lock->rwlock); +} + +static inline int liblockdep_pthread_rwlock_tryrdlock(liblockdep_pthread_rwlock_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 1, 2, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_rwlock_tryrdlock(&lock->rwlock) == 0 ? 1 : 0; +} + +static inline int liblockdep_pthread_rwlock_trywlock(liblockdep_pthread_rwlock_t *lock) +{ +	lock_acquire(&lock->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_); +	return pthread_rwlock_trywlock(&lock->rwlock) == 0 ? 1 : 0; +} + +static inline int liblockdep_rwlock_destroy(liblockdep_pthread_rwlock_t *lock) +{ +	return pthread_rwlock_destroy(&lock->rwlock); +} + +#ifdef __USE_LIBLOCKDEP + +#define pthread_rwlock_t		liblockdep_pthread_rwlock_t +#define pthread_rwlock_init		liblockdep_pthread_rwlock_init +#define pthread_rwlock_rdlock		liblockdep_pthread_rwlock_rdlock +#define pthread_rwlock_unlock		liblockdep_pthread_rwlock_unlock +#define pthread_rwlock_wrlock		liblockdep_pthread_rwlock_wrlock +#define pthread_rwlock_tryrdlock	liblockdep_pthread_rwlock_tryrdlock +#define pthread_rwlock_trywlock		liblockdep_pthread_rwlock_trywlock +#define pthread_rwlock_destroy		liblockdep_rwlock_destroy + +#endif + +#endif diff --git a/tools/lib/lockdep/lockdep b/tools/lib/lockdep/lockdep new file mode 100755 index 00000000000..49af9fe19f5 --- /dev/null +++ b/tools/lib/lockdep/lockdep @@ -0,0 +1,3 @@ +#!/bin/bash + +LD_PRELOAD="./liblockdep.so $LD_PRELOAD" "$@" diff --git a/tools/lib/lockdep/lockdep.c b/tools/lib/lockdep/lockdep.c new file mode 100644 index 00000000000..f42b7e9aa48 --- /dev/null +++ b/tools/lib/lockdep/lockdep.c @@ -0,0 +1,2 @@ +#include <linux/lockdep.h> +#include "../../../kernel/locking/lockdep.c" diff --git a/tools/lib/lockdep/lockdep_internals.h b/tools/lib/lockdep/lockdep_internals.h new file mode 100644 index 00000000000..29d0c954cc2 --- /dev/null +++ b/tools/lib/lockdep/lockdep_internals.h @@ -0,0 +1 @@ +#include "../../../kernel/locking/lockdep_internals.h" diff --git a/tools/lib/lockdep/lockdep_states.h b/tools/lib/lockdep/lockdep_states.h new file mode 100644 index 00000000000..248d235efda --- /dev/null +++ b/tools/lib/lockdep/lockdep_states.h @@ -0,0 +1 @@ +#include "../../../kernel/locking/lockdep_states.h" diff --git a/tools/lib/lockdep/preload.c b/tools/lib/lockdep/preload.c new file mode 100644 index 00000000000..6f803609e49 --- /dev/null +++ b/tools/lib/lockdep/preload.c @@ -0,0 +1,445 @@ +#define _GNU_SOURCE +#include <pthread.h> +#include <stdio.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <sysexits.h> +#include "include/liblockdep/mutex.h" +#include "../../../include/linux/rbtree.h" + +/** + * struct lock_lookup - liblockdep's view of a single unique lock + * @orig: pointer to the original pthread lock, used for lookups + * @dep_map: lockdep's dep_map structure + * @key: lockdep's key structure + * @node: rb-tree node used to store the lock in a global tree + * @name: a unique name for the lock + */ +struct lock_lookup { +	void *orig; /* Original pthread lock, used for lookups */ +	struct lockdep_map dep_map; /* Since all locks are dynamic, we need +				     * a dep_map and a key for each lock */ +	/* +	 * Wait, there's no support for key classes? Yup :( +	 * Most big projects wrap the pthread api with their own calls to +	 * be compatible with different locking methods. This means that +	 * "classes" will be brokes since the function that creates all +	 * locks will point to a generic locking function instead of the +	 * actual code that wants to do the locking. +	 */ +	struct lock_class_key key; +	struct rb_node node; +#define LIBLOCKDEP_MAX_LOCK_NAME 22 +	char name[LIBLOCKDEP_MAX_LOCK_NAME]; +}; + +/* This is where we store our locks */ +static struct rb_root locks = RB_ROOT; +static pthread_rwlock_t locks_rwlock = PTHREAD_RWLOCK_INITIALIZER; + +/* pthread mutex API */ + +#ifdef __GLIBC__ +extern int __pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); +extern int __pthread_mutex_lock(pthread_mutex_t *mutex); +extern int __pthread_mutex_trylock(pthread_mutex_t *mutex); +extern int __pthread_mutex_unlock(pthread_mutex_t *mutex); +extern int __pthread_mutex_destroy(pthread_mutex_t *mutex); +#else +#define __pthread_mutex_init	NULL +#define __pthread_mutex_lock	NULL +#define __pthread_mutex_trylock	NULL +#define __pthread_mutex_unlock	NULL +#define __pthread_mutex_destroy	NULL +#endif +static int (*ll_pthread_mutex_init)(pthread_mutex_t *mutex, +			const pthread_mutexattr_t *attr)	= __pthread_mutex_init; +static int (*ll_pthread_mutex_lock)(pthread_mutex_t *mutex)	= __pthread_mutex_lock; +static int (*ll_pthread_mutex_trylock)(pthread_mutex_t *mutex)	= __pthread_mutex_trylock; +static int (*ll_pthread_mutex_unlock)(pthread_mutex_t *mutex)	= __pthread_mutex_unlock; +static int (*ll_pthread_mutex_destroy)(pthread_mutex_t *mutex)	= __pthread_mutex_destroy; + +/* pthread rwlock API */ + +#ifdef __GLIBC__ +extern int __pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); +extern int __pthread_rwlock_destroy(pthread_rwlock_t *rwlock); +extern int __pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); +extern int __pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); +extern int __pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); +extern int __pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); +extern int __pthread_rwlock_unlock(pthread_rwlock_t *rwlock); +#else +#define __pthread_rwlock_init		NULL +#define __pthread_rwlock_destroy	NULL +#define __pthread_rwlock_wrlock		NULL +#define __pthread_rwlock_trywrlock	NULL +#define __pthread_rwlock_rdlock		NULL +#define __pthread_rwlock_tryrdlock	NULL +#define __pthread_rwlock_unlock		NULL +#endif + +static int (*ll_pthread_rwlock_init)(pthread_rwlock_t *rwlock, +			const pthread_rwlockattr_t *attr)		= __pthread_rwlock_init; +static int (*ll_pthread_rwlock_destroy)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_destroy; +static int (*ll_pthread_rwlock_rdlock)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_rdlock; +static int (*ll_pthread_rwlock_tryrdlock)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_tryrdlock; +static int (*ll_pthread_rwlock_trywrlock)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_trywrlock; +static int (*ll_pthread_rwlock_wrlock)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_wrlock; +static int (*ll_pthread_rwlock_unlock)(pthread_rwlock_t *rwlock)	= __pthread_rwlock_unlock; + +enum { none, prepare, done, } __init_state; +static void init_preload(void); +static void try_init_preload(void) +{ +	if (__init_state != done) +		init_preload(); +} + +static struct rb_node **__get_lock_node(void *lock, struct rb_node **parent) +{ +	struct rb_node **node = &locks.rb_node; +	struct lock_lookup *l; + +	*parent = NULL; + +	while (*node) { +		l = rb_entry(*node, struct lock_lookup, node); + +		*parent = *node; +		if (lock < l->orig) +			node = &l->node.rb_left; +		else if (lock > l->orig) +			node = &l->node.rb_right; +		else +			return node; +	} + +	return node; +} + +#ifndef LIBLOCKDEP_STATIC_ENTRIES +#define LIBLOCKDEP_STATIC_ENTRIES	1024 +#endif + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static struct lock_lookup __locks[LIBLOCKDEP_STATIC_ENTRIES]; +static int __locks_nr; + +static inline bool is_static_lock(struct lock_lookup *lock) +{ +	return lock >= __locks && lock < __locks + ARRAY_SIZE(__locks); +} + +static struct lock_lookup *alloc_lock(void) +{ +	if (__init_state != done) { +		/* +		 * Some programs attempt to initialize and use locks in their +		 * allocation path. This means that a call to malloc() would +		 * result in locks being initialized and locked. +		 * +		 * Why is it an issue for us? dlsym() below will try allocating +		 * to give us the original function. Since this allocation will +		 * result in a locking operations, we have to let pthread deal +		 * with it, but we can't! we don't have the pointer to the +		 * original API since we're inside dlsym() trying to get it +		 */ + +		int idx = __locks_nr++; +		if (idx >= ARRAY_SIZE(__locks)) { +			fprintf(stderr, +		"LOCKDEP error: insufficient LIBLOCKDEP_STATIC_ENTRIES\n"); +			exit(EX_UNAVAILABLE); +		} +		return __locks + idx; +	} + +	return malloc(sizeof(struct lock_lookup)); +} + +static inline void free_lock(struct lock_lookup *lock) +{ +	if (likely(!is_static_lock(lock))) +		free(lock); +} + +/** + * __get_lock - find or create a lock instance + * @lock: pointer to a pthread lock function + * + * Try to find an existing lock in the rbtree using the provided pointer. If + * one wasn't found - create it. + */ +static struct lock_lookup *__get_lock(void *lock) +{ +	struct rb_node **node, *parent; +	struct lock_lookup *l; + +	ll_pthread_rwlock_rdlock(&locks_rwlock); +	node = __get_lock_node(lock, &parent); +	ll_pthread_rwlock_unlock(&locks_rwlock); +	if (*node) { +		return rb_entry(*node, struct lock_lookup, node); +	} + +	/* We didn't find the lock, let's create it */ +	l = alloc_lock(); +	if (l == NULL) +		return NULL; + +	l->orig = lock; +	/* +	 * Currently the name of the lock is the ptr value of the pthread lock, +	 * while not optimal, it makes debugging a bit easier. +	 * +	 * TODO: Get the real name of the lock using libdwarf +	 */ +	sprintf(l->name, "%p", lock); +	lockdep_init_map(&l->dep_map, l->name, &l->key, 0); + +	ll_pthread_rwlock_wrlock(&locks_rwlock); +	/* This might have changed since the last time we fetched it */ +	node = __get_lock_node(lock, &parent); +	rb_link_node(&l->node, parent, node); +	rb_insert_color(&l->node, &locks); +	ll_pthread_rwlock_unlock(&locks_rwlock); + +	return l; +} + +static void __del_lock(struct lock_lookup *lock) +{ +	ll_pthread_rwlock_wrlock(&locks_rwlock); +	rb_erase(&lock->node, &locks); +	ll_pthread_rwlock_unlock(&locks_rwlock); +	free_lock(lock); +} + +int pthread_mutex_init(pthread_mutex_t *mutex, +			const pthread_mutexattr_t *attr) +{ +	int r; + +	/* +	 * We keep trying to init our preload module because there might be +	 * code in init sections that tries to touch locks before we are +	 * initialized, in that case we'll need to manually call preload +	 * to get us going. +	 * +	 * Funny enough, kernel's lockdep had the same issue, and used +	 * (almost) the same solution. See look_up_lock_class() in +	 * kernel/locking/lockdep.c for details. +	 */ +	try_init_preload(); + +	r = ll_pthread_mutex_init(mutex, attr); +	if (r == 0) +		/* +		 * We do a dummy initialization here so that lockdep could +		 * warn us if something fishy is going on - such as +		 * initializing a held lock. +		 */ +		__get_lock(mutex); + +	return r; +} + +int pthread_mutex_lock(pthread_mutex_t *mutex) +{ +	int r; + +	try_init_preload(); + +	lock_acquire(&__get_lock(mutex)->dep_map, 0, 0, 0, 1, NULL, +			(unsigned long)_RET_IP_); +	/* +	 * Here's the thing with pthread mutexes: unlike the kernel variant, +	 * they can fail. +	 * +	 * This means that the behaviour here is a bit different from what's +	 * going on in the kernel: there we just tell lockdep that we took the +	 * lock before actually taking it, but here we must deal with the case +	 * that locking failed. +	 * +	 * To do that we'll "release" the lock if locking failed - this way +	 * we'll get lockdep doing the correct checks when we try to take +	 * the lock, and if that fails - we'll be back to the correct +	 * state by releasing it. +	 */ +	r = ll_pthread_mutex_lock(mutex); +	if (r) +		lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_mutex_trylock(pthread_mutex_t *mutex) +{ +	int r; + +	try_init_preload(); + +	lock_acquire(&__get_lock(mutex)->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_); +	r = ll_pthread_mutex_trylock(mutex); +	if (r) +		lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_mutex_unlock(pthread_mutex_t *mutex) +{ +	int r; + +	try_init_preload(); + +	lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_); +	/* +	 * Just like taking a lock, only in reverse! +	 * +	 * If we fail releasing the lock, tell lockdep we're holding it again. +	 */ +	r = ll_pthread_mutex_unlock(mutex); +	if (r) +		lock_acquire(&__get_lock(mutex)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_mutex_destroy(pthread_mutex_t *mutex) +{ +	try_init_preload(); + +	/* +	 * Let's see if we're releasing a lock that's held. +	 * +	 * TODO: Hook into free() and add that check there as well. +	 */ +	debug_check_no_locks_freed(mutex, mutex + sizeof(*mutex)); +	__del_lock(__get_lock(mutex)); +	return ll_pthread_mutex_destroy(mutex); +} + +/* This is the rwlock part, very similar to what happened with mutex above */ +int pthread_rwlock_init(pthread_rwlock_t *rwlock, +			const pthread_rwlockattr_t *attr) +{ +	int r; + +	try_init_preload(); + +	r = ll_pthread_rwlock_init(rwlock, attr); +	if (r == 0) +		__get_lock(rwlock); + +	return r; +} + +int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) +{ +	try_init_preload(); + +	debug_check_no_locks_freed(rwlock, rwlock + sizeof(*rwlock)); +	__del_lock(__get_lock(rwlock)); +	return ll_pthread_rwlock_destroy(rwlock); +} + +int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) +{ +	int r; + +        init_preload(); + +	lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 2, 1, NULL, (unsigned long)_RET_IP_); +	r = ll_pthread_rwlock_rdlock(rwlock); +	if (r) +		lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) +{ +	int r; + +        init_preload(); + +	lock_acquire(&__get_lock(rwlock)->dep_map, 0, 1, 2, 1, NULL, (unsigned long)_RET_IP_); +	r = ll_pthread_rwlock_tryrdlock(rwlock); +	if (r) +		lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) +{ +	int r; + +        init_preload(); + +	lock_acquire(&__get_lock(rwlock)->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_); +	r = ll_pthread_rwlock_trywrlock(rwlock); +	if (r) +                lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) +{ +	int r; + +        init_preload(); + +	lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_); +	r = ll_pthread_rwlock_wrlock(rwlock); +	if (r) +		lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_); + +	return r; +} + +int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) +{ +	int r; + +        init_preload(); + +	lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_); +	r = ll_pthread_rwlock_unlock(rwlock); +	if (r) +		lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_); + +	return r; +} + +__attribute__((constructor)) static void init_preload(void) +{ +	if (__init_state == done) +		return; + +#ifndef __GLIBC__ +	__init_state = prepare; + +	ll_pthread_mutex_init = dlsym(RTLD_NEXT, "pthread_mutex_init"); +	ll_pthread_mutex_lock = dlsym(RTLD_NEXT, "pthread_mutex_lock"); +	ll_pthread_mutex_trylock = dlsym(RTLD_NEXT, "pthread_mutex_trylock"); +	ll_pthread_mutex_unlock = dlsym(RTLD_NEXT, "pthread_mutex_unlock"); +	ll_pthread_mutex_destroy = dlsym(RTLD_NEXT, "pthread_mutex_destroy"); + +	ll_pthread_rwlock_init = dlsym(RTLD_NEXT, "pthread_rwlock_init"); +	ll_pthread_rwlock_destroy = dlsym(RTLD_NEXT, "pthread_rwlock_destroy"); +	ll_pthread_rwlock_rdlock = dlsym(RTLD_NEXT, "pthread_rwlock_rdlock"); +	ll_pthread_rwlock_tryrdlock = dlsym(RTLD_NEXT, "pthread_rwlock_tryrdlock"); +	ll_pthread_rwlock_wrlock = dlsym(RTLD_NEXT, "pthread_rwlock_wrlock"); +	ll_pthread_rwlock_trywrlock = dlsym(RTLD_NEXT, "pthread_rwlock_trywrlock"); +	ll_pthread_rwlock_unlock = dlsym(RTLD_NEXT, "pthread_rwlock_unlock"); +#endif + +	lockdep_init(); + +	__init_state = done; +} diff --git a/tools/lib/lockdep/rbtree.c b/tools/lib/lockdep/rbtree.c new file mode 100644 index 00000000000..f7f43033c8b --- /dev/null +++ b/tools/lib/lockdep/rbtree.c @@ -0,0 +1 @@ +#include "../../../lib/rbtree.c" diff --git a/tools/lib/lockdep/run_tests.sh b/tools/lib/lockdep/run_tests.sh new file mode 100755 index 00000000000..5334ad9d39b --- /dev/null +++ b/tools/lib/lockdep/run_tests.sh @@ -0,0 +1,27 @@ +#! /bin/bash + +make &> /dev/null + +for i in `ls tests/*.c`; do +	testname=$(basename -s .c "$i") +	gcc -o tests/$testname -pthread -lpthread $i liblockdep.a -Iinclude -D__USE_LIBLOCKDEP &> /dev/null +	echo -ne "$testname... " +	if [ $(timeout 1 ./tests/$testname | wc -l) -gt 0 ]; then +		echo "PASSED!" +	else +		echo "FAILED!" +	fi +	rm tests/$testname +done + +for i in `ls tests/*.c`; do +	testname=$(basename -s .c "$i") +	gcc -o tests/$testname -pthread -lpthread -Iinclude $i &> /dev/null +	echo -ne "(PRELOAD) $testname... " +	if [ $(timeout 1 ./lockdep ./tests/$testname | wc -l) -gt 0 ]; then +		echo "PASSED!" +	else +		echo "FAILED!" +	fi +	rm tests/$testname +done diff --git a/tools/lib/lockdep/tests/AA.c b/tools/lib/lockdep/tests/AA.c new file mode 100644 index 00000000000..0f782ff404a --- /dev/null +++ b/tools/lib/lockdep/tests/AA.c @@ -0,0 +1,13 @@ +#include <liblockdep/mutex.h> + +void main(void) +{ +	pthread_mutex_t a, b; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); + +	pthread_mutex_lock(&a); +	pthread_mutex_lock(&b); +	pthread_mutex_lock(&a); +} diff --git a/tools/lib/lockdep/tests/ABBA.c b/tools/lib/lockdep/tests/ABBA.c new file mode 100644 index 00000000000..07f0e29d548 --- /dev/null +++ b/tools/lib/lockdep/tests/ABBA.c @@ -0,0 +1,13 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(b, a); +} diff --git a/tools/lib/lockdep/tests/ABBCCA.c b/tools/lib/lockdep/tests/ABBCCA.c new file mode 100644 index 00000000000..843db09ac66 --- /dev/null +++ b/tools/lib/lockdep/tests/ABBCCA.c @@ -0,0 +1,15 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b, c; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); +	pthread_mutex_init(&c, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(b, c); +	LOCK_UNLOCK_2(c, a); +} diff --git a/tools/lib/lockdep/tests/ABBCCDDA.c b/tools/lib/lockdep/tests/ABBCCDDA.c new file mode 100644 index 00000000000..33620e268f8 --- /dev/null +++ b/tools/lib/lockdep/tests/ABBCCDDA.c @@ -0,0 +1,17 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b, c, d; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); +	pthread_mutex_init(&c, NULL); +	pthread_mutex_init(&d, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(b, c); +	LOCK_UNLOCK_2(c, d); +	LOCK_UNLOCK_2(d, a); +} diff --git a/tools/lib/lockdep/tests/ABCABC.c b/tools/lib/lockdep/tests/ABCABC.c new file mode 100644 index 00000000000..3fee51e3a68 --- /dev/null +++ b/tools/lib/lockdep/tests/ABCABC.c @@ -0,0 +1,15 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b, c; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); +	pthread_mutex_init(&c, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(c, a); +	LOCK_UNLOCK_2(b, c); +} diff --git a/tools/lib/lockdep/tests/ABCDBCDA.c b/tools/lib/lockdep/tests/ABCDBCDA.c new file mode 100644 index 00000000000..427ba562c75 --- /dev/null +++ b/tools/lib/lockdep/tests/ABCDBCDA.c @@ -0,0 +1,17 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b, c, d; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); +	pthread_mutex_init(&c, NULL); +	pthread_mutex_init(&d, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(c, d); +	LOCK_UNLOCK_2(b, c); +	LOCK_UNLOCK_2(d, a); +} diff --git a/tools/lib/lockdep/tests/ABCDBDDA.c b/tools/lib/lockdep/tests/ABCDBDDA.c new file mode 100644 index 00000000000..680c6cf3e91 --- /dev/null +++ b/tools/lib/lockdep/tests/ABCDBDDA.c @@ -0,0 +1,17 @@ +#include <liblockdep/mutex.h> +#include "common.h" + +void main(void) +{ +	pthread_mutex_t a, b, c, d; + +	pthread_mutex_init(&a, NULL); +	pthread_mutex_init(&b, NULL); +	pthread_mutex_init(&c, NULL); +	pthread_mutex_init(&d, NULL); + +	LOCK_UNLOCK_2(a, b); +	LOCK_UNLOCK_2(c, d); +	LOCK_UNLOCK_2(b, d); +	LOCK_UNLOCK_2(d, a); +} diff --git a/tools/lib/lockdep/tests/WW.c b/tools/lib/lockdep/tests/WW.c new file mode 100644 index 00000000000..d44f77d7102 --- /dev/null +++ b/tools/lib/lockdep/tests/WW.c @@ -0,0 +1,13 @@ +#include <liblockdep/rwlock.h> + +void main(void) +{ +	pthread_rwlock_t a, b; + +	pthread_rwlock_init(&a, NULL); +	pthread_rwlock_init(&b, NULL); + +	pthread_rwlock_wrlock(&a); +	pthread_rwlock_rdlock(&b); +	pthread_rwlock_wrlock(&a); +} diff --git a/tools/lib/lockdep/tests/common.h b/tools/lib/lockdep/tests/common.h new file mode 100644 index 00000000000..d89e94d47d8 --- /dev/null +++ b/tools/lib/lockdep/tests/common.h @@ -0,0 +1,12 @@ +#ifndef _LIBLOCKDEP_TEST_COMMON_H +#define _LIBLOCKDEP_TEST_COMMON_H + +#define LOCK_UNLOCK_2(a, b)			\ +	do {					\ +		pthread_mutex_lock(&(a));	\ +		pthread_mutex_lock(&(b));	\ +		pthread_mutex_unlock(&(b));	\ +		pthread_mutex_unlock(&(a));	\ +	} while(0) + +#endif diff --git a/tools/lib/lockdep/tests/unlock_balance.c b/tools/lib/lockdep/tests/unlock_balance.c new file mode 100644 index 00000000000..0bc62de686f --- /dev/null +++ b/tools/lib/lockdep/tests/unlock_balance.c @@ -0,0 +1,12 @@ +#include <liblockdep/mutex.h> + +void main(void) +{ +	pthread_mutex_t a; + +	pthread_mutex_init(&a, NULL); + +	pthread_mutex_lock(&a); +	pthread_mutex_unlock(&a); +	pthread_mutex_unlock(&a); +} diff --git a/tools/lib/lockdep/uinclude/asm/hash.h b/tools/lib/lockdep/uinclude/asm/hash.h new file mode 100644 index 00000000000..d82b170bb21 --- /dev/null +++ b/tools/lib/lockdep/uinclude/asm/hash.h @@ -0,0 +1,6 @@ +#ifndef __ASM_GENERIC_HASH_H +#define __ASM_GENERIC_HASH_H + +/* Stub */ + +#endif /* __ASM_GENERIC_HASH_H */ diff --git a/tools/lib/lockdep/uinclude/asm/hweight.h b/tools/lib/lockdep/uinclude/asm/hweight.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/asm/hweight.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/asm/sections.h b/tools/lib/lockdep/uinclude/asm/sections.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/asm/sections.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/bitops.h b/tools/lib/lockdep/uinclude/linux/bitops.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/bitops.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/compiler.h b/tools/lib/lockdep/uinclude/linux/compiler.h new file mode 100644 index 00000000000..7ac838a1f19 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/compiler.h @@ -0,0 +1,7 @@ +#ifndef _LIBLOCKDEP_LINUX_COMPILER_H_ +#define _LIBLOCKDEP_LINUX_COMPILER_H_ + +#define __used		__attribute__((__unused__)) +#define unlikely + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/debug_locks.h b/tools/lib/lockdep/uinclude/linux/debug_locks.h new file mode 100644 index 00000000000..f38eb64df79 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/debug_locks.h @@ -0,0 +1,12 @@ +#ifndef _LIBLOCKDEP_DEBUG_LOCKS_H_ +#define _LIBLOCKDEP_DEBUG_LOCKS_H_ + +#include <stddef.h> +#include <linux/compiler.h> + +#define DEBUG_LOCKS_WARN_ON(x) (x) + +extern bool debug_locks; +extern bool debug_locks_silent; + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/delay.h b/tools/lib/lockdep/uinclude/linux/delay.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/delay.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/ftrace.h b/tools/lib/lockdep/uinclude/linux/ftrace.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/ftrace.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/gfp.h b/tools/lib/lockdep/uinclude/linux/gfp.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/gfp.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/hardirq.h b/tools/lib/lockdep/uinclude/linux/hardirq.h new file mode 100644 index 00000000000..c8f3f8f5872 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/hardirq.h @@ -0,0 +1,11 @@ +#ifndef _LIBLOCKDEP_LINUX_HARDIRQ_H_ +#define _LIBLOCKDEP_LINUX_HARDIRQ_H_ + +#define SOFTIRQ_BITS	0UL +#define HARDIRQ_BITS	0UL +#define SOFTIRQ_SHIFT	0UL +#define HARDIRQ_SHIFT	0UL +#define hardirq_count()	0UL +#define softirq_count()	0UL + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/hash.h b/tools/lib/lockdep/uinclude/linux/hash.h new file mode 100644 index 00000000000..0f8479858dc --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/hash.h @@ -0,0 +1 @@ +#include "../../../include/linux/hash.h" diff --git a/tools/lib/lockdep/uinclude/linux/interrupt.h b/tools/lib/lockdep/uinclude/linux/interrupt.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/interrupt.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/irqflags.h b/tools/lib/lockdep/uinclude/linux/irqflags.h new file mode 100644 index 00000000000..6cc296f0fad --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/irqflags.h @@ -0,0 +1,38 @@ +#ifndef _LIBLOCKDEP_LINUX_TRACE_IRQFLAGS_H_ +#define _LIBLOCKDEP_LINUX_TRACE_IRQFLAGS_H_ + +# define trace_hardirq_context(p)	0 +# define trace_softirq_context(p)	0 +# define trace_hardirqs_enabled(p)	0 +# define trace_softirqs_enabled(p)	0 +# define trace_hardirq_enter()		do { } while (0) +# define trace_hardirq_exit()		do { } while (0) +# define lockdep_softirq_enter()	do { } while (0) +# define lockdep_softirq_exit()		do { } while (0) +# define INIT_TRACE_IRQFLAGS + +# define stop_critical_timings() do { } while (0) +# define start_critical_timings() do { } while (0) + +#define raw_local_irq_disable() do { } while (0) +#define raw_local_irq_enable() do { } while (0) +#define raw_local_irq_save(flags) ((flags) = 0) +#define raw_local_irq_restore(flags) do { } while (0) +#define raw_local_save_flags(flags) ((flags) = 0) +#define raw_irqs_disabled_flags(flags) do { } while (0) +#define raw_irqs_disabled() 0 +#define raw_safe_halt() + +#define local_irq_enable() do { } while (0) +#define local_irq_disable() do { } while (0) +#define local_irq_save(flags) ((flags) = 0) +#define local_irq_restore(flags) do { } while (0) +#define local_save_flags(flags)	((flags) = 0) +#define irqs_disabled() (1) +#define irqs_disabled_flags(flags) (0) +#define safe_halt() do { } while (0) + +#define trace_lock_release(x, y) +#define trace_lock_acquire(a, b, c, d, e, f, g) + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/kallsyms.h b/tools/lib/lockdep/uinclude/linux/kallsyms.h new file mode 100644 index 00000000000..b0f2dbdf1a1 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/kallsyms.h @@ -0,0 +1,32 @@ +#ifndef _LIBLOCKDEP_LINUX_KALLSYMS_H_ +#define _LIBLOCKDEP_LINUX_KALLSYMS_H_ + +#include <linux/kernel.h> +#include <stdio.h> + +#define KSYM_NAME_LEN 128 + +struct module; + +static inline const char *kallsyms_lookup(unsigned long addr, +					  unsigned long *symbolsize, +					  unsigned long *offset, +					  char **modname, char *namebuf) +{ +	return NULL; +} + +#include <execinfo.h> +#include <stdlib.h> +static inline void print_ip_sym(unsigned long ip) +{ +	char **name; + +	name = backtrace_symbols((void **)&ip, 1); + +	printf("%s\n", *name); + +	free(name); +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/kern_levels.h b/tools/lib/lockdep/uinclude/linux/kern_levels.h new file mode 100644 index 00000000000..3b9bade2869 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/kern_levels.h @@ -0,0 +1,25 @@ +#ifndef __KERN_LEVELS_H__ +#define __KERN_LEVELS_H__ + +#define KERN_SOH	""		/* ASCII Start Of Header */ +#define KERN_SOH_ASCII	'' + +#define KERN_EMERG	KERN_SOH ""	/* system is unusable */ +#define KERN_ALERT	KERN_SOH ""	/* action must be taken immediately */ +#define KERN_CRIT	KERN_SOH ""	/* critical conditions */ +#define KERN_ERR	KERN_SOH ""	/* error conditions */ +#define KERN_WARNING	KERN_SOH ""	/* warning conditions */ +#define KERN_NOTICE	KERN_SOH ""	/* normal but significant condition */ +#define KERN_INFO	KERN_SOH ""	/* informational */ +#define KERN_DEBUG	KERN_SOH ""	/* debug-level messages */ + +#define KERN_DEFAULT	KERN_SOH ""	/* the default kernel loglevel */ + +/* + * Annotation for a "continued" line of log printout (only done after a + * line that had no enclosing \n). Only to be used by core/arch code + * during early bootup (a continued line is not SMP-safe otherwise). + */ +#define KERN_CONT	"" + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/kernel.h b/tools/lib/lockdep/uinclude/linux/kernel.h new file mode 100644 index 00000000000..a11e3c357be --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/kernel.h @@ -0,0 +1,44 @@ +#ifndef _LIBLOCKDEP_LINUX_KERNEL_H_ +#define _LIBLOCKDEP_LINUX_KERNEL_H_ + +#include <linux/export.h> +#include <linux/types.h> +#include <linux/rcu.h> +#include <linux/hardirq.h> +#include <linux/kern_levels.h> + +#ifndef container_of +#define container_of(ptr, type, member) ({			\ +	const typeof(((type *)0)->member) * __mptr = (ptr);	\ +	(type *)((char *)__mptr - offsetof(type, member)); }) +#endif + +#define max(x, y) ({				\ +	typeof(x) _max1 = (x);			\ +	typeof(y) _max2 = (y);			\ +	(void) (&_max1 == &_max2);		\ +	_max1 > _max2 ? _max1 : _max2; }) + +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) +#define WARN_ON(x) (x) +#define WARN_ON_ONCE(x) (x) +#define likely(x) (x) +#define WARN(x, y, z) (x) +#define uninitialized_var(x) x +#define __init +#define noinline +#define list_add_tail_rcu list_add_tail + +#ifndef CALLER_ADDR0 +#define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0)) +#endif + +#ifndef _RET_IP_ +#define _RET_IP_ CALLER_ADDR0 +#endif + +#ifndef _THIS_IP_ +#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; }) +#endif + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/kmemcheck.h b/tools/lib/lockdep/uinclude/linux/kmemcheck.h new file mode 100644 index 00000000000..94d598bc6ab --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/kmemcheck.h @@ -0,0 +1,8 @@ +#ifndef _LIBLOCKDEP_LINUX_KMEMCHECK_H_ +#define _LIBLOCKDEP_LINUX_KMEMCHECK_H_ + +static inline void kmemcheck_mark_initialized(void *address, unsigned int n) +{ +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/linkage.h b/tools/lib/lockdep/uinclude/linux/linkage.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/linkage.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/list.h b/tools/lib/lockdep/uinclude/linux/list.h new file mode 100644 index 00000000000..6e9ef31ed82 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/list.h @@ -0,0 +1 @@ +#include "../../../include/linux/list.h" diff --git a/tools/lib/lockdep/uinclude/linux/lockdep.h b/tools/lib/lockdep/uinclude/linux/lockdep.h new file mode 100644 index 00000000000..c1552c28507 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/lockdep.h @@ -0,0 +1,58 @@ +#ifndef _LIBLOCKDEP_LOCKDEP_H_ +#define _LIBLOCKDEP_LOCKDEP_H_ + +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <string.h> +#include <limits.h> +#include <linux/utsname.h> + + +#define MAX_LOCK_DEPTH 2000UL + +#define asmlinkage +#define __visible + +#include "../../../include/linux/lockdep.h" + +struct task_struct { +	u64 curr_chain_key; +	int lockdep_depth; +	unsigned int lockdep_recursion; +	struct held_lock held_locks[MAX_LOCK_DEPTH]; +	gfp_t lockdep_reclaim_gfp; +	int pid; +	char comm[17]; +}; + +extern struct task_struct *__curr(void); + +#define current (__curr()) + +#define debug_locks_off() 1 +#define task_pid_nr(tsk) ((tsk)->pid) + +#define KSYM_NAME_LEN 128 +#define printk printf + +#define list_del_rcu list_del + +#define atomic_t unsigned long +#define atomic_inc(x) ((*(x))++) + +static struct new_utsname *init_utsname(void) +{ +	static struct new_utsname n = (struct new_utsname) { +		.release = "liblockdep", +		.version = LIBLOCKDEP_VERSION, +	}; + +	return &n; +} + +#define print_tainted() "" +#define static_obj(x) 1 + +#define debug_show_all_locks() + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/module.h b/tools/lib/lockdep/uinclude/linux/module.h new file mode 100644 index 00000000000..09c7a7be8cc --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/module.h @@ -0,0 +1,6 @@ +#ifndef _LIBLOCKDEP_LINUX_MODULE_H_ +#define _LIBLOCKDEP_LINUX_MODULE_H_ + +#define module_param(name, type, perm) + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/mutex.h b/tools/lib/lockdep/uinclude/linux/mutex.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/mutex.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/poison.h b/tools/lib/lockdep/uinclude/linux/poison.h new file mode 100644 index 00000000000..0c27bdf1423 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/poison.h @@ -0,0 +1 @@ +#include "../../../include/linux/poison.h" diff --git a/tools/lib/lockdep/uinclude/linux/prefetch.h b/tools/lib/lockdep/uinclude/linux/prefetch.h new file mode 100644 index 00000000000..d73fe6f850a --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/prefetch.h @@ -0,0 +1,6 @@ +#ifndef _LIBLOCKDEP_LINUX_PREFETCH_H_ +#define _LIBLOCKDEP_LINUX_PREFETCH_H + +static inline void prefetch(void *a __attribute__((unused))) { } + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/proc_fs.h b/tools/lib/lockdep/uinclude/linux/proc_fs.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/proc_fs.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/rbtree.h b/tools/lib/lockdep/uinclude/linux/rbtree.h new file mode 100644 index 00000000000..965901db486 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/rbtree.h @@ -0,0 +1 @@ +#include "../../../include/linux/rbtree.h" diff --git a/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h b/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h new file mode 100644 index 00000000000..c3759477379 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h @@ -0,0 +1,2 @@ +#define __always_inline +#include "../../../include/linux/rbtree_augmented.h" diff --git a/tools/lib/lockdep/uinclude/linux/rcu.h b/tools/lib/lockdep/uinclude/linux/rcu.h new file mode 100644 index 00000000000..042ee8e463c --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/rcu.h @@ -0,0 +1,21 @@ +#ifndef _LIBLOCKDEP_RCU_H_ +#define _LIBLOCKDEP_RCU_H_ + +int rcu_scheduler_active; + +static inline int rcu_lockdep_current_cpu_online(void) +{ +	return 1; +} + +static inline int rcu_is_cpu_idle(void) +{ +	return 1; +} + +static inline bool rcu_is_watching(void) +{ +	return false; +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/seq_file.h b/tools/lib/lockdep/uinclude/linux/seq_file.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/seq_file.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/lockdep/uinclude/linux/spinlock.h b/tools/lib/lockdep/uinclude/linux/spinlock.h new file mode 100644 index 00000000000..68c1aa2bcba --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/spinlock.h @@ -0,0 +1,25 @@ +#ifndef _LIBLOCKDEP_SPINLOCK_H_ +#define _LIBLOCKDEP_SPINLOCK_H_ + +#include <pthread.h> +#include <stdbool.h> + +#define arch_spinlock_t pthread_mutex_t +#define __ARCH_SPIN_LOCK_UNLOCKED PTHREAD_MUTEX_INITIALIZER + +static inline void arch_spin_lock(arch_spinlock_t *mutex) +{ +	pthread_mutex_lock(mutex); +} + +static inline void arch_spin_unlock(arch_spinlock_t *mutex) +{ +	pthread_mutex_unlock(mutex); +} + +static inline bool arch_spin_is_locked(arch_spinlock_t *mutex) +{ +	return true; +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/stacktrace.h b/tools/lib/lockdep/uinclude/linux/stacktrace.h new file mode 100644 index 00000000000..39aecc6b19d --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/stacktrace.h @@ -0,0 +1,32 @@ +#ifndef _LIBLOCKDEP_LINUX_STACKTRACE_H_ +#define _LIBLOCKDEP_LINUX_STACKTRACE_H_ + +#include <execinfo.h> + +struct stack_trace { +	unsigned int nr_entries, max_entries; +	unsigned long *entries; +	int skip; +}; + +static inline void print_stack_trace(struct stack_trace *trace, int spaces) +{ +	backtrace_symbols_fd((void **)trace->entries, trace->nr_entries, 1); +} + +#define save_stack_trace(trace)	\ +	((trace)->nr_entries =	\ +		backtrace((void **)(trace)->entries, (trace)->max_entries)) + +static inline int dump_stack(void) +{ +	void *array[64]; +	size_t size; + +	size = backtrace(array, 64); +	backtrace_symbols_fd(array, size, 1); + +	return 0; +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/stringify.h b/tools/lib/lockdep/uinclude/linux/stringify.h new file mode 100644 index 00000000000..05dfcd1ac11 --- /dev/null +++ b/tools/lib/lockdep/uinclude/linux/stringify.h @@ -0,0 +1,7 @@ +#ifndef _LIBLOCKDEP_LINUX_STRINGIFY_H_ +#define _LIBLOCKDEP_LINUX_STRINGIFY_H_ + +#define __stringify_1(x...)	#x +#define __stringify(x...)	__stringify_1(x) + +#endif diff --git a/tools/lib/lockdep/uinclude/trace/events/lock.h b/tools/lib/lockdep/uinclude/trace/events/lock.h new file mode 100644 index 00000000000..fab00ff936d --- /dev/null +++ b/tools/lib/lockdep/uinclude/trace/events/lock.h @@ -0,0 +1,3 @@ + +/* empty file */ + diff --git a/tools/lib/symbol/kallsyms.c b/tools/lib/symbol/kallsyms.c new file mode 100644 index 00000000000..18bc271a4bb --- /dev/null +++ b/tools/lib/symbol/kallsyms.c @@ -0,0 +1,58 @@ +#include "symbol/kallsyms.h" +#include <stdio.h> +#include <stdlib.h> + +int kallsyms__parse(const char *filename, void *arg, +		    int (*process_symbol)(void *arg, const char *name, +					  char type, u64 start)) +{ +	char *line = NULL; +	size_t n; +	int err = -1; +	FILE *file = fopen(filename, "r"); + +	if (file == NULL) +		goto out_failure; + +	err = 0; + +	while (!feof(file)) { +		u64 start; +		int line_len, len; +		char symbol_type; +		char *symbol_name; + +		line_len = getline(&line, &n, file); +		if (line_len < 0 || !line) +			break; + +		line[--line_len] = '\0'; /* \n */ + +		len = hex2u64(line, &start); + +		len++; +		if (len + 2 >= line_len) +			continue; + +		symbol_type = line[len]; +		len += 2; +		symbol_name = line + len; +		len = line_len - len; + +		if (len >= KSYM_NAME_LEN) { +			err = -1; +			break; +		} + +		err = process_symbol(arg, symbol_name, symbol_type, start); +		if (err) +			break; +	} + +	free(line); +	fclose(file); +	return err; + +out_failure: +	return -1; +} diff --git a/tools/lib/symbol/kallsyms.h b/tools/lib/symbol/kallsyms.h new file mode 100644 index 00000000000..6084f5e18b3 --- /dev/null +++ b/tools/lib/symbol/kallsyms.h @@ -0,0 +1,24 @@ +#ifndef __TOOLS_KALLSYMS_H_ +#define __TOOLS_KALLSYMS_H_ 1 + +#include <elf.h> +#include <linux/ctype.h> +#include <linux/types.h> + +#ifndef KSYM_NAME_LEN +#define KSYM_NAME_LEN 256 +#endif + +static inline u8 kallsyms2elf_type(char type) +{ +	if (type == 'W') +		return STB_WEAK; + +	return isupper(type) ? STB_GLOBAL : STB_LOCAL; +} + +int kallsyms__parse(const char *filename, void *arg, +		    int (*process_symbol)(void *arg, const char *name, +					  char type, u64 start)); + +#endif /* __TOOLS_KALLSYMS_H_ */ diff --git a/tools/lib/traceevent/.gitignore b/tools/lib/traceevent/.gitignore new file mode 100644 index 00000000000..35f56be5a4c --- /dev/null +++ b/tools/lib/traceevent/.gitignore @@ -0,0 +1 @@ +TRACEEVENT-CFLAGS diff --git a/tools/lib/traceevent/Makefile b/tools/lib/traceevent/Makefile new file mode 100644 index 00000000000..005c9cc0693 --- /dev/null +++ b/tools/lib/traceevent/Makefile @@ -0,0 +1,340 @@ +# trace-cmd version +EP_VERSION = 1 +EP_PATCHLEVEL = 1 +EP_EXTRAVERSION = 0 + +# file format version +FILE_VERSION = 6 + +MAKEFLAGS += --no-print-directory + + +# Makefiles suck: This macro sets a default value of $(2) for the +# variable named by $(1), unless the variable has been set by +# environment or command line. This is necessary for CC and AR +# because make sets default values, so the simpler ?= approach +# won't work as expected. +define allow-override +  $(if $(or $(findstring environment,$(origin $(1))),\ +            $(findstring command line,$(origin $(1)))),,\ +    $(eval $(1) = $(2))) +endef + +# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. +$(call allow-override,CC,$(CROSS_COMPILE)gcc) +$(call allow-override,AR,$(CROSS_COMPILE)ar) + +EXT = -std=gnu99 +INSTALL = install + +# Use DESTDIR for installing into a different root directory. +# This is useful for building a package. The program will be +# installed in this directory as if it was the root directory. +# Then the build tool can move it later. +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +prefix ?= /usr/local +bindir_relative = bin +bindir = $(prefix)/$(bindir_relative) +man_dir = $(prefix)/share/man +man_dir_SQ = '$(subst ','\'',$(man_dir))' + +export man_dir man_dir_SQ INSTALL +export DESTDIR DESTDIR_SQ + +set_plugin_dir := 1 + +# Set plugin_dir to preffered global plugin location +# If we install under $HOME directory we go under +# $(HOME)/.traceevent/plugins +# +# We dont set PLUGIN_DIR in case we install under $HOME +# directory, because by default the code looks under: +# $(HOME)/.traceevent/plugins by default. +# +ifeq ($(plugin_dir),) +ifeq ($(prefix),$(HOME)) +override plugin_dir = $(HOME)/.traceevent/plugins +set_plugin_dir := 0 +else +override plugin_dir = $(prefix)/lib/traceevent/plugins +endif +endif + +ifeq ($(set_plugin_dir),1) +PLUGIN_DIR = -DPLUGIN_DIR="$(plugin_dir)" +PLUGIN_DIR_SQ = '$(subst ','\'',$(PLUGIN_DIR))' +endif + +include $(if $(BUILD_SRC),$(BUILD_SRC)/)../../scripts/Makefile.include + +# copy a bit from Linux kbuild + +ifeq ("$(origin V)", "command line") +  VERBOSE = $(V) +endif +ifndef VERBOSE +  VERBOSE = 0 +endif + +ifeq ("$(origin O)", "command line") +  BUILD_OUTPUT := $(O) +endif + +ifeq ($(BUILD_SRC),) +ifneq ($(OUTPUT),) + +define build_output +  $(if $(VERBOSE:1=),@)+$(MAKE) -C $(OUTPUT) \ +  BUILD_SRC=$(CURDIR)/ -f $(CURDIR)/Makefile $1 +endef + +all: sub-make + +$(MAKECMDGOALS): sub-make + +sub-make: force +	$(call build_output, $(MAKECMDGOALS)) + + +# Leave processing to above invocation of make +skip-makefile := 1 + +endif # OUTPUT +endif # BUILD_SRC + +# We process the rest of the Makefile if this is the final invocation of make +ifeq ($(skip-makefile),) + +srctree		:= $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR)) +objtree		:= $(CURDIR) +src		:= $(srctree) +obj		:= $(objtree) + +export prefix bindir src obj + +# Shell quotes +bindir_SQ = $(subst ','\'',$(bindir)) +bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) +plugin_dir_SQ = $(subst ','\'',$(plugin_dir)) + +LIB_FILE = libtraceevent.a libtraceevent.so + +CONFIG_INCLUDES =  +CONFIG_LIBS	= +CONFIG_FLAGS	= + +VERSION		= $(EP_VERSION) +PATCHLEVEL	= $(EP_PATCHLEVEL) +EXTRAVERSION	= $(EP_EXTRAVERSION) + +OBJ		= $@ +N		= + +export Q VERBOSE + +EVENT_PARSE_VERSION = $(EP_VERSION).$(EP_PATCHLEVEL).$(EP_EXTRAVERSION) + +INCLUDES = -I. -I $(srctree)/../../include $(CONFIG_INCLUDES) + +# Set compile option CFLAGS if not set elsewhere +CFLAGS ?= -g -Wall + +# Append required CFLAGS +override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ) +override CFLAGS += $(udis86-flags) -D_GNU_SOURCE + +ifeq ($(VERBOSE),1) +  Q = +else +  Q = @ +endif + +do_compile_shared_library =			\ +	($(print_shared_lib_compile)		\ +	$(CC) --shared $^ -o $@) + +do_plugin_build =				\ +	($(print_plugin_build)			\ +	$(CC) $(CFLAGS) -shared -nostartfiles -o $@ $<) + +do_build_static_lib =				\ +	($(print_static_lib_build)		\ +	$(RM) $@;  $(AR) rcs $@ $^) + + +do_compile = $(QUIET_CC)$(CC) -c $(CFLAGS) $(EXT) $< -o $(obj)/$@; + +$(obj)/%.o: $(src)/%.c +	$(call do_compile) + +%.o: $(src)/%.c +	$(call do_compile) + +PEVENT_LIB_OBJS  = event-parse.o +PEVENT_LIB_OBJS += event-plugin.o +PEVENT_LIB_OBJS += trace-seq.o +PEVENT_LIB_OBJS += parse-filter.o +PEVENT_LIB_OBJS += parse-utils.o +PEVENT_LIB_OBJS += kbuffer-parse.o + +PLUGIN_OBJS  = plugin_jbd2.o +PLUGIN_OBJS += plugin_hrtimer.o +PLUGIN_OBJS += plugin_kmem.o +PLUGIN_OBJS += plugin_kvm.o +PLUGIN_OBJS += plugin_mac80211.o +PLUGIN_OBJS += plugin_sched_switch.o +PLUGIN_OBJS += plugin_function.o +PLUGIN_OBJS += plugin_xen.o +PLUGIN_OBJS += plugin_scsi.o +PLUGIN_OBJS += plugin_cfg80211.o + +PLUGINS := $(PLUGIN_OBJS:.o=.so) + +ALL_OBJS = $(PEVENT_LIB_OBJS) $(PLUGIN_OBJS) + +CMD_TARGETS = $(LIB_FILE) $(PLUGINS) + +TARGETS = $(CMD_TARGETS) + + +all: all_cmd + +all_cmd: $(CMD_TARGETS) + +libtraceevent.so: $(PEVENT_LIB_OBJS) +	$(QUIET_LINK)$(CC) --shared $^ -o $@ + +libtraceevent.a: $(PEVENT_LIB_OBJS) +	$(QUIET_LINK)$(RM) $@; $(AR) rcs $@ $^ + +plugins: $(PLUGINS) + +$(PEVENT_LIB_OBJS): %.o: $(src)/%.c TRACEEVENT-CFLAGS +	$(QUIET_CC_FPIC)$(CC) -c $(CFLAGS) $(EXT) -fPIC $< -o $@ + +$(PLUGIN_OBJS): %.o : $(src)/%.c +	$(QUIET_CC_FPIC)$(CC) -c $(CFLAGS) -fPIC -o $@ $< + +$(PLUGINS): %.so: %.o +	$(QUIET_LINK)$(CC) $(CFLAGS) -shared -nostartfiles -o $@ $< + +define make_version.h +  (echo '/* This file is automatically generated. Do not modify. */';		\ +   echo \#define VERSION_CODE $(shell						\ +   expr $(VERSION) \* 256 + $(PATCHLEVEL));					\ +   echo '#define EXTRAVERSION ' $(EXTRAVERSION);				\ +   echo '#define VERSION_STRING "'$(VERSION).$(PATCHLEVEL).$(EXTRAVERSION)'"';	\ +   echo '#define FILE_VERSION '$(FILE_VERSION);					\ +  ) > $1 +endef + +define update_version.h +  ($(call make_version.h, $@.tmp);		\ +    if [ -r $@ ] && cmp -s $@ $@.tmp; then	\ +      rm -f $@.tmp;				\ +    else					\ +      echo '  UPDATE                 $@';	\ +      mv -f $@.tmp $@;				\ +    fi); +endef + +ep_version.h: force +	$(Q)$(N)$(call update_version.h) + +VERSION_FILES = ep_version.h + +define update_dir +  (echo $1 > $@.tmp;				\ +   if [ -r $@ ] && cmp -s $@ $@.tmp; then	\ +     rm -f $@.tmp;				\ +   else						\ +     echo '  UPDATE                 $@';	\ +     mv -f $@.tmp $@;				\ +   fi); +endef + +## make deps + +all_objs := $(sort $(ALL_OBJS)) +all_deps := $(all_objs:%.o=.%.d) + +# let .d file also depends on the source and header files +define check_deps +  @set -e; $(RM) $@; \ +  $(CC) -MM $(CFLAGS) $< > $@.$$$$; \ +  sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ +  $(RM) $@.$$$$ +endef + +$(all_deps): .%.d: $(src)/%.c +	$(Q)$(call check_deps) + +$(all_objs) : %.o : .%.d + +dep_includes := $(wildcard $(all_deps)) + +ifneq ($(dep_includes),) + include $(dep_includes) +endif + +### Detect environment changes +TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):$(ARCH):$(CROSS_COMPILE) + +TRACEEVENT-CFLAGS: force +	@FLAGS='$(TRACK_CFLAGS)'; \ +	    if test x"$$FLAGS" != x"`cat TRACEEVENT-CFLAGS 2>/dev/null`" ; then \ +		echo 1>&2 "  FLAGS:   * new build flags or cross compiler"; \ +		echo "$$FLAGS" >TRACEEVENT-CFLAGS; \ +            fi + +tags:	force +	$(RM) tags +	find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \ +	--regex-c++='/_PE\(([^,)]*).*/PEVENT_ERRNO__\1/' + +TAGS:	force +	$(RM) TAGS +	find . -name '*.[ch]' | xargs etags \ +	--regex='/_PE(\([^,)]*\).*/PEVENT_ERRNO__\1/' + +define do_install +	if [ ! -d '$(DESTDIR_SQ)$2' ]; then		\ +		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2';	\ +	fi;						\ +	$(INSTALL) $1 '$(DESTDIR_SQ)$2' +endef + +define do_install_plugins +	for plugin in $1; do				\ +	  $(call do_install,$$plugin,$(plugin_dir_SQ));	\ +	done +endef + +install_lib: all_cmd install_plugins +	$(call QUIET_INSTALL, $(LIB_FILE)) \ +		$(call do_install,$(LIB_FILE),$(bindir_SQ)) + +install_plugins: $(PLUGINS) +	$(call QUIET_INSTALL, trace_plugins) \ +		$(call do_install_plugins, $(PLUGINS)) + +install: install_lib + +clean: +	$(call QUIET_CLEAN, libtraceevent) \ +		$(RM) *.o *~ $(TARGETS) *.a *.so $(VERSION_FILES) .*.d \ +		$(RM) TRACEEVENT-CFLAGS tags TAGS + +endif # skip-makefile + +PHONY += force plugins +force: + +plugins: +	@echo > /dev/null + +# Declare the contents of the .PHONY variable as phony.  We keep that +# information in a variable so we can use it in if_changed and friends. +.PHONY: $(PHONY) diff --git a/tools/lib/traceevent/event-parse.c b/tools/lib/traceevent/event-parse.c new file mode 100644 index 00000000000..93825a17dcc --- /dev/null +++ b/tools/lib/traceevent/event-parse.c @@ -0,0 +1,6025 @@ +/* + * 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 "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; + +	item->comm = strdup(comm); +	if (!item->comm) { +		free(item); +		return -1; +	} +	item->pid = pid; +	item->next = pevent->cmdlist; + +	pevent->cmdlist = item; +	pevent->cmdline_count++; + +	return 0; +} + +void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock) +{ +	pevent->trace_clock = trace_clock; +} + +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; +} + +/** + * 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_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: +		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 = 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 = 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: +	/* 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); +		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 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; + +	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; + +	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) +{ +	struct print_arg *field; +	enum event_type type; +	char *token; + +	memset(arg, 0, sizeof(*arg)); +	arg->type = PRINT_HEX; + +	field = alloc_arg(); +	if (!field) { +		do_warning_event(event, "%s: not enough memory!", __func__); +		goto out_free; +	} + +	type = process_arg(event, field, &token); + +	if (test_type_token(type, token, EVENT_DELIM, ",")) +		goto out_free; + +	arg->hex.field = field; + +	free_token(token); + +	field = alloc_arg(); +	if (!field) { +		do_warning_event(event, "%s: not enough memory!", __func__); +		*tok = NULL; +		return EVENT_ERROR; +	} + +	type = process_arg(event, field, &token); + +	if (test_type_token(type, token, EVENT_DELIM, ")")) +		goto out_free; + +	arg->hex.size = field; + +	free_token(token); +	type = read_token_item(tok); +	return type; + + out_free: +	free_arg(field); +	free_token(token); +	*tok = NULL; +	return EVENT_ERROR; +} + +static enum event_type +process_dynamic_array(struct event_format *event, struct print_arg *arg, char **tok) +{ +	struct format_field *field; +	enum event_type type; +	char *token; + +	memset(arg, 0, sizeof(*arg)); +	arg->type = PRINT_DYNAMIC_ARRAY; + +	/* +	 * The item within the parenthesis is another field that holds +	 * the index into where the array starts. +	 */ +	type = read_token(&token); +	*tok = token; +	if (type != EVENT_ITEM) +		goto out_free; + +	/* Find the field */ + +	field = pevent_find_field(event, token); +	if (!field) +		goto out_free; + +	arg->dynarray.field = field; +	arg->dynarray.index = 0; + +	if (read_expected(EVENT_DELIM, ")") < 0) +		goto out_free; + +	free_token(token); +	type = read_token_item(&token); +	*tok = token; +	if (type != EVENT_OP || strcmp(token, "[") != 0) +		return type; + +	free_token(token); +	arg = alloc_arg(); +	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_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, "__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); +	} + +	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_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: +		/* Without [], we pass the address to the dynamic data */ +		offset = pevent_read_number(pevent, +					    data + arg->dynarray.field->offset, +					    arg->dynarray.field->size); +		/* +		 * 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; +		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 }, +	{ "BLOCK_IOPOLL_SOFTIRQ", 5 }, +	{ "TASKLET_SOFTIRQ", 6 }, +	{ "SCHED_SOFTIRQ", 7 }, +	{ "HRTIMER_SOFTIRQ", 8 }, +	{ "RCU_SOFTIRQ", 9 }, + +	{ "HRTIMER_NORESTART", 0 }, +	{ "HRTIMER_RESTART", 1 }, +}; + +static unsigned 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 0; +} + +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; +	unsigned long long val, fval; +	unsigned 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) { +			addr = *(unsigned long *)(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, "%lx", 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) { +				print_str_to_seq(s, format, len_arg, flag->str); +				break; +			} +			if (fval && (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_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 "%pf: " */ +	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 '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 int is_printable_array(char *p, unsigned int len) +{ +	unsigned int i; + +	for (i = 0; i < len && p[i]; i++) +		if (!isprint(p[i]) && !isspace(p[i])) +		    return 0; +	return 1; +} + +static void print_event_fields(struct trace_seq *s, void *data, +			       int size __maybe_unused, +			       struct event_format *event) +{ +	struct format_field *field; +	unsigned long long val; +	unsigned int offset, len, i; + +	field = event->format.fields; +	while (field) { +		trace_seq_printf(s, " %s=", field->name); +		if (field->flags & FIELD_IS_ARRAY) { +			offset = field->offset; +			len = field->size; +			if (field->flags & FIELD_IS_DYNAMIC) { +				val = pevent_read_number(event->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(event->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); +			} +		} +		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]"); +		print_event_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': +				goto cont_process; +			case 'p': +				if (pevent->long_size == 4) +					ls = 1; +				else +					ls = 2; + +				if (*(ptr+1) == 'F' || +				    *(ptr+1) == 'f') { +					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; +				} + +				/* 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; + +					ls = 2; +					/* make %l into %ll */ +					p = strchr(format, 'l'); +					if (p) +						memmove(p+1, p, strlen(p)+1); +					else if (strcmp(format, "%p") == 0) +						strcpy(format, "0x%llx"); +				} +				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; +} + +/** + * 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)) +		print_event_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_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++; +} + +static void free_format_fields(struct format_field *field) +{ +	struct format_field *next; + +	while (field) { +		next = field->next; +		free(field->type); +		free(field->name); +		free(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->events); +	free(pevent->sort_events); + +	free(pevent); +} + +void pevent_unref(struct pevent *pevent) +{ +	pevent_free(pevent); +} diff --git a/tools/lib/traceevent/event-parse.h b/tools/lib/traceevent/event-parse.h new file mode 100644 index 00000000000..7a3873ff9a4 --- /dev/null +++ b/tools/lib/traceevent/event-parse.h @@ -0,0 +1,944 @@ +/* + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef _PARSE_EVENTS_H +#define _PARSE_EVENTS_H + +#include <stdbool.h> +#include <stdarg.h> +#include <regex.h> +#include <string.h> + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((unused)) +#endif + +/* ----------------------- trace_seq ----------------------- */ + + +#ifndef TRACE_SEQ_BUF_SIZE +#define TRACE_SEQ_BUF_SIZE 4096 +#endif + +#ifndef DEBUG_RECORD +#define DEBUG_RECORD 0 +#endif + +struct pevent_record { +	unsigned long long	ts; +	unsigned long long	offset; +	long long		missed_events;	/* buffer dropped events before */ +	int			record_size;	/* size of binary record */ +	int			size;		/* size of data */ +	void			*data; +	int			cpu; +	int			ref_count; +	int			locked;		/* Do not free, even if ref_count is zero */ +	void			*priv; +#if DEBUG_RECORD +	struct pevent_record	*prev; +	struct pevent_record	*next; +	long			alloc_addr; +#endif +}; + +enum trace_seq_fail { +	TRACE_SEQ__GOOD, +	TRACE_SEQ__BUFFER_POISONED, +	TRACE_SEQ__MEM_ALLOC_FAILED, +}; + +/* + * Trace sequences are used to allow a function to call several other functions + * to create a string of data to use (up to a max of PAGE_SIZE). + */ + +struct trace_seq { +	char			*buffer; +	unsigned int		buffer_size; +	unsigned int		len; +	unsigned int		readpos; +	enum trace_seq_fail	state; +}; + +void trace_seq_init(struct trace_seq *s); +void trace_seq_reset(struct trace_seq *s); +void trace_seq_destroy(struct trace_seq *s); + +extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...) +	__attribute__ ((format (printf, 2, 3))); +extern int trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args) +	__attribute__ ((format (printf, 2, 0))); + +extern int trace_seq_puts(struct trace_seq *s, const char *str); +extern int trace_seq_putc(struct trace_seq *s, unsigned char c); + +extern void trace_seq_terminate(struct trace_seq *s); + +extern int trace_seq_do_printf(struct trace_seq *s); + + +/* ----------------------- pevent ----------------------- */ + +struct pevent; +struct event_format; + +typedef int (*pevent_event_handler_func)(struct trace_seq *s, +					 struct pevent_record *record, +					 struct event_format *event, +					 void *context); + +typedef int (*pevent_plugin_load_func)(struct pevent *pevent); +typedef int (*pevent_plugin_unload_func)(struct pevent *pevent); + +struct pevent_plugin_option { +	struct pevent_plugin_option	*next; +	void				*handle; +	char				*file; +	char				*name; +	char				*plugin_alias; +	char				*description; +	char				*value; +	void				*priv; +	int				set; +}; + +/* + * Plugin hooks that can be called: + * + * PEVENT_PLUGIN_LOADER:  (required) + *   The function name to initialized the plugin. + * + *   int PEVENT_PLUGIN_LOADER(struct pevent *pevent) + * + * PEVENT_PLUGIN_UNLOADER:  (optional) + *   The function called just before unloading + * + *   int PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) + * + * PEVENT_PLUGIN_OPTIONS:  (optional) + *   Plugin options that can be set before loading + * + *   struct pevent_plugin_option PEVENT_PLUGIN_OPTIONS[] = { + *	{ + *		.name = "option-name", + *		.plugin_alias = "overide-file-name", (optional) + *		.description = "description of option to show users", + *	}, + *	{ + *		.name = NULL, + *	}, + *   }; + * + *   Array must end with .name = NULL; + * + * + *   .plugin_alias is used to give a shorter name to access + *   the vairable. Useful if a plugin handles more than one event. + * + * PEVENT_PLUGIN_ALIAS: (optional) + *   The name to use for finding options (uses filename if not defined) + */ +#define PEVENT_PLUGIN_LOADER pevent_plugin_loader +#define PEVENT_PLUGIN_UNLOADER pevent_plugin_unloader +#define PEVENT_PLUGIN_OPTIONS pevent_plugin_options +#define PEVENT_PLUGIN_ALIAS pevent_plugin_alias +#define _MAKE_STR(x)	#x +#define MAKE_STR(x)	_MAKE_STR(x) +#define PEVENT_PLUGIN_LOADER_NAME MAKE_STR(PEVENT_PLUGIN_LOADER) +#define PEVENT_PLUGIN_UNLOADER_NAME MAKE_STR(PEVENT_PLUGIN_UNLOADER) +#define PEVENT_PLUGIN_OPTIONS_NAME MAKE_STR(PEVENT_PLUGIN_OPTIONS) +#define PEVENT_PLUGIN_ALIAS_NAME MAKE_STR(PEVENT_PLUGIN_ALIAS) + +#define NSECS_PER_SEC		1000000000ULL +#define NSECS_PER_USEC		1000ULL + +enum format_flags { +	FIELD_IS_ARRAY		= 1, +	FIELD_IS_POINTER	= 2, +	FIELD_IS_SIGNED		= 4, +	FIELD_IS_STRING		= 8, +	FIELD_IS_DYNAMIC	= 16, +	FIELD_IS_LONG		= 32, +	FIELD_IS_FLAG		= 64, +	FIELD_IS_SYMBOLIC	= 128, +}; + +struct format_field { +	struct format_field	*next; +	struct event_format	*event; +	char			*type; +	char			*name; +	int			offset; +	int			size; +	unsigned int		arraylen; +	unsigned int		elementsize; +	unsigned long		flags; +}; + +struct format { +	int			nr_common; +	int			nr_fields; +	struct format_field	*common_fields; +	struct format_field	*fields; +}; + +struct print_arg_atom { +	char			*atom; +}; + +struct print_arg_string { +	char			*string; +	int			offset; +}; + +struct print_arg_bitmask { +	char			*bitmask; +	int			offset; +}; + +struct print_arg_field { +	char			*name; +	struct format_field	*field; +}; + +struct print_flag_sym { +	struct print_flag_sym	*next; +	char			*value; +	char			*str; +}; + +struct print_arg_typecast { +	char 			*type; +	struct print_arg	*item; +}; + +struct print_arg_flags { +	struct print_arg	*field; +	char			*delim; +	struct print_flag_sym	*flags; +}; + +struct print_arg_symbol { +	struct print_arg	*field; +	struct print_flag_sym	*symbols; +}; + +struct print_arg_hex { +	struct print_arg	*field; +	struct print_arg	*size; +}; + +struct print_arg_dynarray { +	struct format_field	*field; +	struct print_arg	*index; +}; + +struct print_arg; + +struct print_arg_op { +	char			*op; +	int			prio; +	struct print_arg	*left; +	struct print_arg	*right; +}; + +struct pevent_function_handler; + +struct print_arg_func { +	struct pevent_function_handler	*func; +	struct print_arg		*args; +}; + +enum print_arg_type { +	PRINT_NULL, +	PRINT_ATOM, +	PRINT_FIELD, +	PRINT_FLAGS, +	PRINT_SYMBOL, +	PRINT_HEX, +	PRINT_TYPE, +	PRINT_STRING, +	PRINT_BSTRING, +	PRINT_DYNAMIC_ARRAY, +	PRINT_OP, +	PRINT_FUNC, +	PRINT_BITMASK, +}; + +struct print_arg { +	struct print_arg		*next; +	enum print_arg_type		type; +	union { +		struct print_arg_atom		atom; +		struct print_arg_field		field; +		struct print_arg_typecast	typecast; +		struct print_arg_flags		flags; +		struct print_arg_symbol		symbol; +		struct print_arg_hex		hex; +		struct print_arg_func		func; +		struct print_arg_string		string; +		struct print_arg_bitmask	bitmask; +		struct print_arg_op		op; +		struct print_arg_dynarray	dynarray; +	}; +}; + +struct print_fmt { +	char			*format; +	struct print_arg	*args; +}; + +struct event_format { +	struct pevent		*pevent; +	char			*name; +	int			id; +	int			flags; +	struct format		format; +	struct print_fmt	print_fmt; +	char			*system; +	pevent_event_handler_func handler; +	void			*context; +}; + +enum { +	EVENT_FL_ISFTRACE	= 0x01, +	EVENT_FL_ISPRINT	= 0x02, +	EVENT_FL_ISBPRINT	= 0x04, +	EVENT_FL_ISFUNCENT	= 0x10, +	EVENT_FL_ISFUNCRET	= 0x20, +	EVENT_FL_NOHANDLE	= 0x40, +	EVENT_FL_PRINTRAW	= 0x80, + +	EVENT_FL_FAILED		= 0x80000000 +}; + +enum event_sort_type { +	EVENT_SORT_ID, +	EVENT_SORT_NAME, +	EVENT_SORT_SYSTEM, +}; + +enum event_type { +	EVENT_ERROR, +	EVENT_NONE, +	EVENT_SPACE, +	EVENT_NEWLINE, +	EVENT_OP, +	EVENT_DELIM, +	EVENT_ITEM, +	EVENT_DQUOTE, +	EVENT_SQUOTE, +}; + +typedef unsigned long long (*pevent_func_handler)(struct trace_seq *s, +					     unsigned long long *args); + +enum pevent_func_arg_type { +	PEVENT_FUNC_ARG_VOID, +	PEVENT_FUNC_ARG_INT, +	PEVENT_FUNC_ARG_LONG, +	PEVENT_FUNC_ARG_STRING, +	PEVENT_FUNC_ARG_PTR, +	PEVENT_FUNC_ARG_MAX_TYPES +}; + +enum pevent_flag { +	PEVENT_NSEC_OUTPUT		= 1,	/* output in NSECS */ +	PEVENT_DISABLE_SYS_PLUGINS	= 1 << 1, +	PEVENT_DISABLE_PLUGINS		= 1 << 2, +}; + +#define PEVENT_ERRORS 							      \ +	_PE(MEM_ALLOC_FAILED,	"failed to allocate memory"),		      \ +	_PE(PARSE_EVENT_FAILED,	"failed to parse event"),		      \ +	_PE(READ_ID_FAILED,	"failed to read event id"),		      \ +	_PE(READ_FORMAT_FAILED,	"failed to read event format"),		      \ +	_PE(READ_PRINT_FAILED,	"failed to read event print fmt"), 	      \ +	_PE(OLD_FTRACE_ARG_FAILED,"failed to allocate field name for ftrace"),\ +	_PE(INVALID_ARG_TYPE,	"invalid argument type"),		      \ +	_PE(INVALID_EXP_TYPE,	"invalid expression type"),		      \ +	_PE(INVALID_OP_TYPE,	"invalid operator type"),		      \ +	_PE(INVALID_EVENT_NAME,	"invalid event name"),			      \ +	_PE(EVENT_NOT_FOUND,	"no event found"),			      \ +	_PE(SYNTAX_ERROR,	"syntax error"),			      \ +	_PE(ILLEGAL_RVALUE,	"illegal rvalue"),			      \ +	_PE(ILLEGAL_LVALUE,	"illegal lvalue for string comparison"),      \ +	_PE(INVALID_REGEX,	"regex did not compute"),		      \ +	_PE(ILLEGAL_STRING_CMP,	"illegal comparison for string"), 	      \ +	_PE(ILLEGAL_INTEGER_CMP,"illegal comparison for integer"), 	      \ +	_PE(REPARENT_NOT_OP,	"cannot reparent other than OP"),	      \ +	_PE(REPARENT_FAILED,	"failed to reparent filter OP"),	      \ +	_PE(BAD_FILTER_ARG,	"bad arg in filter tree"),		      \ +	_PE(UNEXPECTED_TYPE,	"unexpected type (not a value)"),	      \ +	_PE(ILLEGAL_TOKEN,	"illegal token"),			      \ +	_PE(INVALID_PAREN,	"open parenthesis cannot come here"), 	      \ +	_PE(UNBALANCED_PAREN,	"unbalanced number of parenthesis"),	      \ +	_PE(UNKNOWN_TOKEN,	"unknown token"),			      \ +	_PE(FILTER_NOT_FOUND,	"no filter found"),			      \ +	_PE(NOT_A_NUMBER,	"must have number field"),		      \ +	_PE(NO_FILTER,		"no filters exists"),			      \ +	_PE(FILTER_MISS,	"record does not match to filter") + +#undef _PE +#define _PE(__code, __str) PEVENT_ERRNO__ ## __code +enum pevent_errno { +	PEVENT_ERRNO__SUCCESS			= 0, +	PEVENT_ERRNO__FILTER_MATCH		= PEVENT_ERRNO__SUCCESS, + +	/* +	 * Choose an arbitrary negative big number not to clash with standard +	 * errno since SUS requires the errno has distinct positive values. +	 * See 'Issue 6' in the link below. +	 * +	 * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html +	 */ +	__PEVENT_ERRNO__START			= -100000, + +	PEVENT_ERRORS, + +	__PEVENT_ERRNO__END, +}; +#undef _PE + +struct plugin_list; + +#define INVALID_PLUGIN_LIST_OPTION	((char **)((unsigned long)-1)) + +struct plugin_list *traceevent_load_plugins(struct pevent *pevent); +void traceevent_unload_plugins(struct plugin_list *plugin_list, +			       struct pevent *pevent); +char **traceevent_plugin_list_options(void); +void traceevent_plugin_free_options_list(char **list); +int traceevent_plugin_add_options(const char *name, +				  struct pevent_plugin_option *options); +void traceevent_plugin_remove_options(struct pevent_plugin_option *options); +void traceevent_print_plugins(struct trace_seq *s, +			      const char *prefix, const char *suffix, +			      const struct plugin_list *list); + +struct cmdline; +struct cmdline_list; +struct func_map; +struct func_list; +struct event_handler; + +struct pevent { +	int ref_count; + +	int header_page_ts_offset; +	int header_page_ts_size; +	int header_page_size_offset; +	int header_page_size_size; +	int header_page_data_offset; +	int header_page_data_size; +	int header_page_overwrite; + +	int file_bigendian; +	int host_bigendian; + +	int latency_format; + +	int old_format; + +	int cpus; +	int long_size; +	int page_size; + +	struct cmdline *cmdlines; +	struct cmdline_list *cmdlist; +	int cmdline_count; + +	struct func_map *func_map; +	struct func_list *funclist; +	unsigned int func_count; + +	struct printk_map *printk_map; +	struct printk_list *printklist; +	unsigned int printk_count; + + +	struct event_format **events; +	int nr_events; +	struct event_format **sort_events; +	enum event_sort_type last_type; + +	int type_offset; +	int type_size; + +	int pid_offset; +	int pid_size; + + 	int pc_offset; +	int pc_size; + +	int flags_offset; +	int flags_size; + +	int ld_offset; +	int ld_size; + +	int print_raw; + +	int test_filters; + +	int flags; + +	struct format_field *bprint_ip_field; +	struct format_field *bprint_fmt_field; +	struct format_field *bprint_buf_field; + +	struct event_handler *handlers; +	struct pevent_function_handler *func_handlers; + +	/* cache */ +	struct event_format *last_event; + +	char *trace_clock; +}; + +static inline void pevent_set_flag(struct pevent *pevent, int flag) +{ +	pevent->flags |= flag; +} + +static inline unsigned short +__data2host2(struct pevent *pevent, unsigned short data) +{ +	unsigned short swap; + +	if (pevent->host_bigendian == pevent->file_bigendian) +		return data; + +	swap = ((data & 0xffULL) << 8) | +		((data & (0xffULL << 8)) >> 8); + +	return swap; +} + +static inline unsigned int +__data2host4(struct pevent *pevent, unsigned int data) +{ +	unsigned int swap; + +	if (pevent->host_bigendian == pevent->file_bigendian) +		return data; + +	swap = ((data & 0xffULL) << 24) | +		((data & (0xffULL << 8)) << 8) | +		((data & (0xffULL << 16)) >> 8) | +		((data & (0xffULL << 24)) >> 24); + +	return swap; +} + +static inline unsigned long long +__data2host8(struct pevent *pevent, unsigned long long data) +{ +	unsigned long long swap; + +	if (pevent->host_bigendian == pevent->file_bigendian) +		return data; + +	swap = ((data & 0xffULL) << 56) | +		((data & (0xffULL << 8)) << 40) | +		((data & (0xffULL << 16)) << 24) | +		((data & (0xffULL << 24)) << 8) | +		((data & (0xffULL << 32)) >> 8) | +		((data & (0xffULL << 40)) >> 24) | +		((data & (0xffULL << 48)) >> 40) | +		((data & (0xffULL << 56)) >> 56); + +	return swap; +} + +#define data2host2(pevent, ptr)		__data2host2(pevent, *(unsigned short *)(ptr)) +#define data2host4(pevent, ptr)		__data2host4(pevent, *(unsigned int *)(ptr)) +#define data2host8(pevent, ptr)					\ +({								\ +	unsigned long long __val;				\ +								\ +	memcpy(&__val, (ptr), sizeof(unsigned long long));	\ +	__data2host8(pevent, __val);				\ +}) + +static inline int traceevent_host_bigendian(void) +{ +	unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 }; +	unsigned int val; + +	memcpy(&val, str, 4); +	return val == 0x01020304; +} + +/* taken from kernel/trace/trace.h */ +enum trace_flag_type { +	TRACE_FLAG_IRQS_OFF		= 0x01, +	TRACE_FLAG_IRQS_NOSUPPORT	= 0x02, +	TRACE_FLAG_NEED_RESCHED		= 0x04, +	TRACE_FLAG_HARDIRQ		= 0x08, +	TRACE_FLAG_SOFTIRQ		= 0x10, +}; + +int pevent_register_comm(struct pevent *pevent, const char *comm, int pid); +void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock); +int pevent_register_function(struct pevent *pevent, char *name, +			     unsigned long long addr, char *mod); +int pevent_register_print_string(struct pevent *pevent, const char *fmt, +				 unsigned long long addr); +int pevent_pid_is_registered(struct pevent *pevent, int pid); + +void pevent_print_event(struct pevent *pevent, struct trace_seq *s, +			struct pevent_record *record, bool use_trace_clock); + +int pevent_parse_header_page(struct pevent *pevent, char *buf, unsigned long size, +			     int long_size); + +enum pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf, +				     unsigned long size, const char *sys); +enum pevent_errno pevent_parse_format(struct pevent *pevent, +				      struct event_format **eventp, +				      const char *buf, +				      unsigned long size, const char *sys); +void pevent_free_format(struct event_format *event); + +void *pevent_get_field_raw(struct trace_seq *s, struct event_format *event, +			   const char *name, struct pevent_record *record, +			   int *len, int err); + +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); +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); +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); + +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); + +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); + +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); +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); +int pevent_register_print_function(struct pevent *pevent, +				   pevent_func_handler func, +				   enum pevent_func_arg_type ret_type, +				   char *name, ...); +int pevent_unregister_print_function(struct pevent *pevent, +				     pevent_func_handler func, char *name); + +struct format_field *pevent_find_common_field(struct event_format *event, const char *name); +struct format_field *pevent_find_field(struct event_format *event, const char *name); +struct format_field *pevent_find_any_field(struct event_format *event, const char *name); + +const char *pevent_find_function(struct pevent *pevent, unsigned long long addr); +unsigned long long +pevent_find_function_address(struct pevent *pevent, unsigned long long addr); +unsigned long long pevent_read_number(struct pevent *pevent, const void *ptr, int size); +int pevent_read_number_field(struct format_field *field, const void *data, +			     unsigned long long *value); + +struct event_format *pevent_find_event(struct pevent *pevent, int id); + +struct event_format * +pevent_find_event_by_name(struct pevent *pevent, const char *sys, const char *name); + +void pevent_data_lat_fmt(struct pevent *pevent, +			 struct trace_seq *s, struct pevent_record *record); +int pevent_data_type(struct pevent *pevent, struct pevent_record *rec); +struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type); +int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec); +const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid); +void pevent_event_info(struct trace_seq *s, struct event_format *event, +		       struct pevent_record *record); +int pevent_strerror(struct pevent *pevent, enum pevent_errno errnum, +		    char *buf, size_t buflen); + +struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type); +struct format_field **pevent_event_common_fields(struct event_format *event); +struct format_field **pevent_event_fields(struct event_format *event); + +static inline int pevent_get_cpus(struct pevent *pevent) +{ +	return pevent->cpus; +} + +static inline void pevent_set_cpus(struct pevent *pevent, int cpus) +{ +	pevent->cpus = cpus; +} + +static inline int pevent_get_long_size(struct pevent *pevent) +{ +	return pevent->long_size; +} + +static inline void pevent_set_long_size(struct pevent *pevent, int long_size) +{ +	pevent->long_size = long_size; +} + +static inline int pevent_get_page_size(struct pevent *pevent) +{ +	return pevent->page_size; +} + +static inline void pevent_set_page_size(struct pevent *pevent, int _page_size) +{ +	pevent->page_size = _page_size; +} + +static inline int pevent_is_file_bigendian(struct pevent *pevent) +{ +	return pevent->file_bigendian; +} + +static inline void pevent_set_file_bigendian(struct pevent *pevent, int endian) +{ +	pevent->file_bigendian = endian; +} + +static inline int pevent_is_host_bigendian(struct pevent *pevent) +{ +	return pevent->host_bigendian; +} + +static inline void pevent_set_host_bigendian(struct pevent *pevent, int endian) +{ +	pevent->host_bigendian = endian; +} + +static inline int pevent_is_latency_format(struct pevent *pevent) +{ +	return pevent->latency_format; +} + +static inline void pevent_set_latency_format(struct pevent *pevent, int lat) +{ +	pevent->latency_format = lat; +} + +struct pevent *pevent_alloc(void); +void pevent_free(struct pevent *pevent); +void pevent_ref(struct pevent *pevent); +void pevent_unref(struct pevent *pevent); + +/* access to the internal parser */ +void pevent_buffer_init(const char *buf, unsigned long long size); +enum event_type pevent_read_token(char **tok); +void pevent_free_token(char *token); +int pevent_peek_char(void); +const char *pevent_get_input_buf(void); +unsigned long long pevent_get_input_buf_ptr(void); + +/* for debugging */ +void pevent_print_funcs(struct pevent *pevent); +void pevent_print_printk(struct pevent *pevent); + +/* ----------------------- filtering ----------------------- */ + +enum filter_boolean_type { +	FILTER_FALSE, +	FILTER_TRUE, +}; + +enum filter_op_type { +	FILTER_OP_AND = 1, +	FILTER_OP_OR, +	FILTER_OP_NOT, +}; + +enum filter_cmp_type { +	FILTER_CMP_NONE, +	FILTER_CMP_EQ, +	FILTER_CMP_NE, +	FILTER_CMP_GT, +	FILTER_CMP_LT, +	FILTER_CMP_GE, +	FILTER_CMP_LE, +	FILTER_CMP_MATCH, +	FILTER_CMP_NOT_MATCH, +	FILTER_CMP_REGEX, +	FILTER_CMP_NOT_REGEX, +}; + +enum filter_exp_type { +	FILTER_EXP_NONE, +	FILTER_EXP_ADD, +	FILTER_EXP_SUB, +	FILTER_EXP_MUL, +	FILTER_EXP_DIV, +	FILTER_EXP_MOD, +	FILTER_EXP_RSHIFT, +	FILTER_EXP_LSHIFT, +	FILTER_EXP_AND, +	FILTER_EXP_OR, +	FILTER_EXP_XOR, +	FILTER_EXP_NOT, +}; + +enum filter_arg_type { +	FILTER_ARG_NONE, +	FILTER_ARG_BOOLEAN, +	FILTER_ARG_VALUE, +	FILTER_ARG_FIELD, +	FILTER_ARG_EXP, +	FILTER_ARG_OP, +	FILTER_ARG_NUM, +	FILTER_ARG_STR, +}; + +enum filter_value_type { +	FILTER_NUMBER, +	FILTER_STRING, +	FILTER_CHAR +}; + +struct fliter_arg; + +struct filter_arg_boolean { +	enum filter_boolean_type	value; +}; + +struct filter_arg_field { +	struct format_field	*field; +}; + +struct filter_arg_value { +	enum filter_value_type	type; +	union { +		char			*str; +		unsigned long long	val; +	}; +}; + +struct filter_arg_op { +	enum filter_op_type	type; +	struct filter_arg	*left; +	struct filter_arg	*right; +}; + +struct filter_arg_exp { +	enum filter_exp_type	type; +	struct filter_arg	*left; +	struct filter_arg	*right; +}; + +struct filter_arg_num { +	enum filter_cmp_type	type; +	struct filter_arg	*left; +	struct filter_arg	*right; +}; + +struct filter_arg_str { +	enum filter_cmp_type	type; +	struct format_field	*field; +	char			*val; +	char			*buffer; +	regex_t			reg; +}; + +struct filter_arg { +	enum filter_arg_type	type; +	union { +		struct filter_arg_boolean	boolean; +		struct filter_arg_field		field; +		struct filter_arg_value		value; +		struct filter_arg_op		op; +		struct filter_arg_exp		exp; +		struct filter_arg_num		num; +		struct filter_arg_str		str; +	}; +}; + +struct filter_type { +	int			event_id; +	struct event_format	*event; +	struct filter_arg	*filter; +}; + +#define PEVENT_FILTER_ERROR_BUFSZ  1024 + +struct event_filter { +	struct pevent		*pevent; +	int			filters; +	struct filter_type	*event_filters; +	char			error_buffer[PEVENT_FILTER_ERROR_BUFSZ]; +}; + +struct event_filter *pevent_filter_alloc(struct pevent *pevent); + +/* for backward compatibility */ +#define FILTER_NONE		PEVENT_ERRNO__NO_FILTER +#define FILTER_NOEXIST		PEVENT_ERRNO__FILTER_NOT_FOUND +#define FILTER_MISS		PEVENT_ERRNO__FILTER_MISS +#define FILTER_MATCH		PEVENT_ERRNO__FILTER_MATCH + +enum filter_trivial_type { +	FILTER_TRIVIAL_FALSE, +	FILTER_TRIVIAL_TRUE, +	FILTER_TRIVIAL_BOTH, +}; + +enum pevent_errno pevent_filter_add_filter_str(struct event_filter *filter, +					       const char *filter_str); + +enum pevent_errno pevent_filter_match(struct event_filter *filter, +				      struct pevent_record *record); + +int pevent_filter_strerror(struct event_filter *filter, enum pevent_errno err, +			   char *buf, size_t buflen); + +int pevent_event_filtered(struct event_filter *filter, +			  int event_id); + +void pevent_filter_reset(struct event_filter *filter); + +int pevent_filter_clear_trivial(struct event_filter *filter, +				 enum filter_trivial_type type); + +void pevent_filter_free(struct event_filter *filter); + +char *pevent_filter_make_string(struct event_filter *filter, int event_id); + +int pevent_filter_remove_event(struct event_filter *filter, +			       int event_id); + +int pevent_filter_event_has_trivial(struct event_filter *filter, +				    int event_id, +				    enum filter_trivial_type type); + +int pevent_filter_copy(struct event_filter *dest, struct event_filter *source); + +int pevent_update_trivial(struct event_filter *dest, struct event_filter *source, +			  enum filter_trivial_type type); + +int pevent_filter_compare(struct event_filter *filter1, struct event_filter *filter2); + +#endif /* _PARSE_EVENTS_H */ diff --git a/tools/lib/traceevent/event-plugin.c b/tools/lib/traceevent/event-plugin.c new file mode 100644 index 00000000000..136162c03af --- /dev/null +++ b/tools/lib/traceevent/event-plugin.c @@ -0,0 +1,416 @@ +/* + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <stdio.h> +#include <string.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include "event-parse.h" +#include "event-utils.h" + +#define LOCAL_PLUGIN_DIR ".traceevent/plugins" + +static struct registered_plugin_options { +	struct registered_plugin_options	*next; +	struct pevent_plugin_option		*options; +} *registered_options; + +static struct trace_plugin_options { +	struct trace_plugin_options	*next; +	char				*plugin; +	char				*option; +	char				*value; +} *trace_plugin_options; + +struct plugin_list { +	struct plugin_list	*next; +	char			*name; +	void			*handle; +}; + +/** + * traceevent_plugin_list_options - get list of plugin options + * + * Returns an array of char strings that list the currently registered + * plugin options in the format of <plugin>:<option>. This list can be + * used by toggling the option. + * + * Returns NULL if there's no options registered. On error it returns + * INVALID_PLUGIN_LIST_OPTION + * + * Must be freed with traceevent_plugin_free_options_list(). + */ +char **traceevent_plugin_list_options(void) +{ +	struct registered_plugin_options *reg; +	struct pevent_plugin_option *op; +	char **list = NULL; +	char *name; +	int count = 0; + +	for (reg = registered_options; reg; reg = reg->next) { +		for (op = reg->options; op->name; op++) { +			char *alias = op->plugin_alias ? op->plugin_alias : op->file; +			char **temp = list; + +			name = malloc(strlen(op->name) + strlen(alias) + 2); +			if (!name) +				goto err; + +			sprintf(name, "%s:%s", alias, op->name); +			list = realloc(list, count + 2); +			if (!list) { +				list = temp; +				free(name); +				goto err; +			} +			list[count++] = name; +			list[count] = NULL; +		} +	} +	return list; + + err: +	while (--count >= 0) +		free(list[count]); +	free(list); + +	return INVALID_PLUGIN_LIST_OPTION; +} + +void traceevent_plugin_free_options_list(char **list) +{ +	int i; + +	if (!list) +		return; + +	if (list == INVALID_PLUGIN_LIST_OPTION) +		return; + +	for (i = 0; list[i]; i++) +		free(list[i]); + +	free(list); +} + +static int +update_option(const char *file, struct pevent_plugin_option *option) +{ +	struct trace_plugin_options *op; +	char *plugin; + +	if (option->plugin_alias) { +		plugin = strdup(option->plugin_alias); +		if (!plugin) +			return -1; +	} else { +		char *p; +		plugin = strdup(file); +		if (!plugin) +			return -1; +		p = strstr(plugin, "."); +		if (p) +			*p = '\0'; +	} + +	/* first look for named options */ +	for (op = trace_plugin_options; op; op = op->next) { +		if (!op->plugin) +			continue; +		if (strcmp(op->plugin, plugin) != 0) +			continue; +		if (strcmp(op->option, option->name) != 0) +			continue; + +		option->value = op->value; +		option->set ^= 1; +		goto out; +	} + +	/* first look for unnamed options */ +	for (op = trace_plugin_options; op; op = op->next) { +		if (op->plugin) +			continue; +		if (strcmp(op->option, option->name) != 0) +			continue; + +		option->value = op->value; +		option->set ^= 1; +		break; +	} + + out: +	free(plugin); +	return 0; +} + +/** + * traceevent_plugin_add_options - Add a set of options by a plugin + * @name: The name of the plugin adding the options + * @options: The set of options being loaded + * + * Sets the options with the values that have been added by user. + */ +int traceevent_plugin_add_options(const char *name, +				  struct pevent_plugin_option *options) +{ +	struct registered_plugin_options *reg; + +	reg = malloc(sizeof(*reg)); +	if (!reg) +		return -1; +	reg->next = registered_options; +	reg->options = options; +	registered_options = reg; + +	while (options->name) { +		update_option(name, options); +		options++; +	} +	return 0; +} + +/** + * traceevent_plugin_remove_options - remove plugin options that were registered + * @options: Options to removed that were registered with traceevent_plugin_add_options + */ +void traceevent_plugin_remove_options(struct pevent_plugin_option *options) +{ +	struct registered_plugin_options **last; +	struct registered_plugin_options *reg; + +	for (last = ®istered_options; *last; last = &(*last)->next) { +		if ((*last)->options == options) { +			reg = *last; +			*last = reg->next; +			free(reg); +			return; +		} +	} +} + +/** + * traceevent_print_plugins - print out the list of plugins loaded + * @s: the trace_seq descripter to write to + * @prefix: The prefix string to add before listing the option name + * @suffix: The suffix string ot append after the option name + * @list: The list of plugins (usually returned by traceevent_load_plugins() + * + * Writes to the trace_seq @s the list of plugins (files) that is + * returned by traceevent_load_plugins(). Use @prefix and @suffix for formating: + * @prefix = "  ", @suffix = "\n". + */ +void traceevent_print_plugins(struct trace_seq *s, +			      const char *prefix, const char *suffix, +			      const struct plugin_list *list) +{ +	while (list) { +		trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix); +		list = list->next; +	} +} + +static void +load_plugin(struct pevent *pevent, const char *path, +	    const char *file, void *data) +{ +	struct plugin_list **plugin_list = data; +	pevent_plugin_load_func func; +	struct plugin_list *list; +	const char *alias; +	char *plugin; +	void *handle; + +	plugin = malloc(strlen(path) + strlen(file) + 2); +	if (!plugin) { +		warning("could not allocate plugin memory\n"); +		return; +	} + +	strcpy(plugin, path); +	strcat(plugin, "/"); +	strcat(plugin, file); + +	handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL); +	if (!handle) { +		warning("could not load plugin '%s'\n%s\n", +			plugin, dlerror()); +		goto out_free; +	} + +	alias = dlsym(handle, PEVENT_PLUGIN_ALIAS_NAME); +	if (!alias) +		alias = file; + +	func = dlsym(handle, PEVENT_PLUGIN_LOADER_NAME); +	if (!func) { +		warning("could not find func '%s' in plugin '%s'\n%s\n", +			PEVENT_PLUGIN_LOADER_NAME, plugin, dlerror()); +		goto out_free; +	} + +	list = malloc(sizeof(*list)); +	if (!list) { +		warning("could not allocate plugin memory\n"); +		goto out_free; +	} + +	list->next = *plugin_list; +	list->handle = handle; +	list->name = plugin; +	*plugin_list = list; + +	pr_stat("registering plugin: %s", plugin); +	func(pevent); +	return; + + out_free: +	free(plugin); +} + +static void +load_plugins_dir(struct pevent *pevent, const char *suffix, +		 const char *path, +		 void (*load_plugin)(struct pevent *pevent, +				     const char *path, +				     const char *name, +				     void *data), +		 void *data) +{ +	struct dirent *dent; +	struct stat st; +	DIR *dir; +	int ret; + +	ret = stat(path, &st); +	if (ret < 0) +		return; + +	if (!S_ISDIR(st.st_mode)) +		return; + +	dir = opendir(path); +	if (!dir) +		return; + +	while ((dent = readdir(dir))) { +		const char *name = dent->d_name; + +		if (strcmp(name, ".") == 0 || +		    strcmp(name, "..") == 0) +			continue; + +		/* Only load plugins that end in suffix */ +		if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0) +			continue; + +		load_plugin(pevent, path, name, data); +	} + +	closedir(dir); +} + +static void +load_plugins(struct pevent *pevent, const char *suffix, +	     void (*load_plugin)(struct pevent *pevent, +				 const char *path, +				 const char *name, +				 void *data), +	     void *data) +{ +	char *home; +	char *path; +	char *envdir; + +	if (pevent->flags & PEVENT_DISABLE_PLUGINS) +		return; + +	/* +	 * If a system plugin directory was defined, +	 * check that first. +	 */ +#ifdef PLUGIN_DIR +	if (!(pevent->flags & PEVENT_DISABLE_SYS_PLUGINS)) +		load_plugins_dir(pevent, suffix, PLUGIN_DIR, +				 load_plugin, data); +#endif + +	/* +	 * Next let the environment-set plugin directory +	 * override the system defaults. +	 */ +	envdir = getenv("TRACEEVENT_PLUGIN_DIR"); +	if (envdir) +		load_plugins_dir(pevent, suffix, envdir, load_plugin, data); + +	/* +	 * Now let the home directory override the environment +	 * or system defaults. +	 */ +	home = getenv("HOME"); +	if (!home) +		return; + +	path = malloc(strlen(home) + strlen(LOCAL_PLUGIN_DIR) + 2); +	if (!path) { +		warning("could not allocate plugin memory\n"); +		return; +	} + +	strcpy(path, home); +	strcat(path, "/"); +	strcat(path, LOCAL_PLUGIN_DIR); + +	load_plugins_dir(pevent, suffix, path, load_plugin, data); + +	free(path); +} + +struct plugin_list* +traceevent_load_plugins(struct pevent *pevent) +{ +	struct plugin_list *list = NULL; + +	load_plugins(pevent, ".so", load_plugin, &list); +	return list; +} + +void +traceevent_unload_plugins(struct plugin_list *plugin_list, struct pevent *pevent) +{ +	pevent_plugin_unload_func func; +	struct plugin_list *list; + +	while (plugin_list) { +		list = plugin_list; +		plugin_list = list->next; +		func = dlsym(list->handle, PEVENT_PLUGIN_UNLOADER_NAME); +		if (func) +			func(pevent); +		dlclose(list->handle); +		free(list->name); +		free(list); +	} +} diff --git a/tools/lib/traceevent/event-utils.h b/tools/lib/traceevent/event-utils.h new file mode 100644 index 00000000000..d1dc2170e40 --- /dev/null +++ b/tools/lib/traceevent/event-utils.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef __UTIL_H +#define __UTIL_H + +#include <ctype.h> + +/* Can be overridden */ +void warning(const char *fmt, ...); +void pr_stat(const char *fmt, ...); +void vpr_stat(const char *fmt, va_list ap); + +/* Always available */ +void __warning(const char *fmt, ...); +void __pr_stat(const char *fmt, ...); + +void __vwarning(const char *fmt, ...); +void __vpr_stat(const char *fmt, ...); + +#define min(x, y) ({				\ +	typeof(x) _min1 = (x);			\ +	typeof(y) _min2 = (y);			\ +	(void) (&_min1 == &_min2);		\ +	_min1 < _min2 ? _min1 : _min2; }) + +static inline char *strim(char *string) +{ +	char *ret; + +	if (!string) +		return NULL; +	while (*string) { +		if (!isspace(*string)) +			break; +		string++; +	} +	ret = string; + +	string = ret + strlen(ret) - 1; +	while (string > ret) { +		if (!isspace(*string)) +			break; +		string--; +	} +	string[1] = 0; + +	return ret; +} + +static inline int has_text(const char *text) +{ +	if (!text) +		return 0; + +	while (*text) { +		if (!isspace(*text)) +			return 1; +		text++; +	} + +	return 0; +} + +#endif diff --git a/tools/lib/traceevent/kbuffer-parse.c b/tools/lib/traceevent/kbuffer-parse.c new file mode 100644 index 00000000000..dcc665228c7 --- /dev/null +++ b/tools/lib/traceevent/kbuffer-parse.c @@ -0,0 +1,732 @@ +/* + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "kbuffer.h" + +#define MISSING_EVENTS (1 << 31) +#define MISSING_STORED (1 << 30) + +#define COMMIT_MASK ((1 << 27) - 1) + +enum { +	KBUFFER_FL_HOST_BIG_ENDIAN	= (1<<0), +	KBUFFER_FL_BIG_ENDIAN		= (1<<1), +	KBUFFER_FL_LONG_8		= (1<<2), +	KBUFFER_FL_OLD_FORMAT		= (1<<3), +}; + +#define ENDIAN_MASK (KBUFFER_FL_HOST_BIG_ENDIAN | KBUFFER_FL_BIG_ENDIAN) + +/** kbuffer + * @timestamp		- timestamp of current event + * @lost_events		- # of lost events between this subbuffer and previous + * @flags		- special flags of the kbuffer + * @subbuffer		- pointer to the sub-buffer page + * @data		- pointer to the start of data on the sub-buffer page + * @index		- index from @data to the @curr event data + * @curr		- offset from @data to the start of current event + *			   (includes metadata) + * @next		- offset from @data to the start of next event + * @size		- The size of data on @data + * @start		- The offset from @subbuffer where @data lives + * + * @read_4		- Function to read 4 raw bytes (may swap) + * @read_8		- Function to read 8 raw bytes (may swap) + * @read_long		- Function to read a long word (4 or 8 bytes with needed swap) + */ +struct kbuffer { +	unsigned long long 	timestamp; +	long long		lost_events; +	unsigned long		flags; +	void			*subbuffer; +	void			*data; +	unsigned int		index; +	unsigned int		curr; +	unsigned int		next; +	unsigned int		size; +	unsigned int		start; + +	unsigned int (*read_4)(void *ptr); +	unsigned long long (*read_8)(void *ptr); +	unsigned long long (*read_long)(struct kbuffer *kbuf, void *ptr); +	int (*next_event)(struct kbuffer *kbuf); +}; + +static void *zmalloc(size_t size) +{ +	return calloc(1, size); +} + +static int host_is_bigendian(void) +{ +	unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 }; +	unsigned int *ptr; + +	ptr = (unsigned int *)str; +	return *ptr == 0x01020304; +} + +static int do_swap(struct kbuffer *kbuf) +{ +	return ((kbuf->flags & KBUFFER_FL_HOST_BIG_ENDIAN) + kbuf->flags) & +		ENDIAN_MASK; +} + +static unsigned long long __read_8(void *ptr) +{ +	unsigned long long data = *(unsigned long long *)ptr; + +	return data; +} + +static unsigned long long __read_8_sw(void *ptr) +{ +	unsigned long long data = *(unsigned long long *)ptr; +	unsigned long long swap; + +	swap = ((data & 0xffULL) << 56) | +		((data & (0xffULL << 8)) << 40) | +		((data & (0xffULL << 16)) << 24) | +		((data & (0xffULL << 24)) << 8) | +		((data & (0xffULL << 32)) >> 8) | +		((data & (0xffULL << 40)) >> 24) | +		((data & (0xffULL << 48)) >> 40) | +		((data & (0xffULL << 56)) >> 56); + +	return swap; +} + +static unsigned int __read_4(void *ptr) +{ +	unsigned int data = *(unsigned int *)ptr; + +	return data; +} + +static unsigned int __read_4_sw(void *ptr) +{ +	unsigned int data = *(unsigned int *)ptr; +	unsigned int swap; + +	swap = ((data & 0xffULL) << 24) | +		((data & (0xffULL << 8)) << 8) | +		((data & (0xffULL << 16)) >> 8) | +		((data & (0xffULL << 24)) >> 24); + +	return swap; +} + +static unsigned long long read_8(struct kbuffer *kbuf, void *ptr) +{ +	return kbuf->read_8(ptr); +} + +static unsigned int read_4(struct kbuffer *kbuf, void *ptr) +{ +	return kbuf->read_4(ptr); +} + +static unsigned long long __read_long_8(struct kbuffer *kbuf, void *ptr) +{ +	return kbuf->read_8(ptr); +} + +static unsigned long long __read_long_4(struct kbuffer *kbuf, void *ptr) +{ +	return kbuf->read_4(ptr); +} + +static unsigned long long read_long(struct kbuffer *kbuf, void *ptr) +{ +	return kbuf->read_long(kbuf, ptr); +} + +static int calc_index(struct kbuffer *kbuf, void *ptr) +{ +	return (unsigned long)ptr - (unsigned long)kbuf->data; +} + +static int __next_event(struct kbuffer *kbuf); + +/** + * kbuffer_alloc - allocat a new kbuffer + * @size;	enum to denote size of word + * @endian:	enum to denote endianness + * + * Allocates and returns a new kbuffer. + */ +struct kbuffer * +kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian) +{ +	struct kbuffer *kbuf; +	int flags = 0; + +	switch (size) { +	case KBUFFER_LSIZE_4: +		break; +	case KBUFFER_LSIZE_8: +		flags |= KBUFFER_FL_LONG_8; +		break; +	default: +		return NULL; +	} + +	switch (endian) { +	case KBUFFER_ENDIAN_LITTLE: +		break; +	case KBUFFER_ENDIAN_BIG: +		flags |= KBUFFER_FL_BIG_ENDIAN; +		break; +	default: +		return NULL; +	} + +	kbuf = zmalloc(sizeof(*kbuf)); +	if (!kbuf) +		return NULL; + +	kbuf->flags = flags; + +	if (host_is_bigendian()) +		kbuf->flags |= KBUFFER_FL_HOST_BIG_ENDIAN; + +	if (do_swap(kbuf)) { +		kbuf->read_8 = __read_8_sw; +		kbuf->read_4 = __read_4_sw; +	} else { +		kbuf->read_8 = __read_8; +		kbuf->read_4 = __read_4; +	} + +	if (kbuf->flags & KBUFFER_FL_LONG_8) +		kbuf->read_long = __read_long_8; +	else +		kbuf->read_long = __read_long_4; + +	/* May be changed by kbuffer_set_old_format() */ +	kbuf->next_event = __next_event; + +	return kbuf; +} + +/** kbuffer_free - free an allocated kbuffer + * @kbuf:	The kbuffer to free + * + * Can take NULL as a parameter. + */ +void kbuffer_free(struct kbuffer *kbuf) +{ +	free(kbuf); +} + +static unsigned int type4host(struct kbuffer *kbuf, +			      unsigned int type_len_ts) +{ +	if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) +		return (type_len_ts >> 29) & 3; +	else +		return type_len_ts & 3; +} + +static unsigned int len4host(struct kbuffer *kbuf, +			     unsigned int type_len_ts) +{ +	if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) +		return (type_len_ts >> 27) & 7; +	else +		return (type_len_ts >> 2) & 7; +} + +static unsigned int type_len4host(struct kbuffer *kbuf, +				  unsigned int type_len_ts) +{ +	if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) +		return (type_len_ts >> 27) & ((1 << 5) - 1); +	else +		return type_len_ts & ((1 << 5) - 1); +} + +static unsigned int ts4host(struct kbuffer *kbuf, +			    unsigned int type_len_ts) +{ +	if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) +		return type_len_ts & ((1 << 27) - 1); +	else +		return type_len_ts >> 5; +} + +/* + * Linux 2.6.30 and earlier (not much ealier) had a different + * ring buffer format. It should be obsolete, but we handle it anyway. + */ +enum old_ring_buffer_type { +	OLD_RINGBUF_TYPE_PADDING, +	OLD_RINGBUF_TYPE_TIME_EXTEND, +	OLD_RINGBUF_TYPE_TIME_STAMP, +	OLD_RINGBUF_TYPE_DATA, +}; + +static unsigned int old_update_pointers(struct kbuffer *kbuf) +{ +	unsigned long long extend; +	unsigned int type_len_ts; +	unsigned int type; +	unsigned int len; +	unsigned int delta; +	unsigned int length; +	void *ptr = kbuf->data + kbuf->curr; + +	type_len_ts = read_4(kbuf, ptr); +	ptr += 4; + +	type = type4host(kbuf, type_len_ts); +	len = len4host(kbuf, type_len_ts); +	delta = ts4host(kbuf, type_len_ts); + +	switch (type) { +	case OLD_RINGBUF_TYPE_PADDING: +		kbuf->next = kbuf->size; +		return 0; + +	case OLD_RINGBUF_TYPE_TIME_EXTEND: +		extend = read_4(kbuf, ptr); +		extend <<= TS_SHIFT; +		extend += delta; +		delta = extend; +		ptr += 4; +		break; + +	case OLD_RINGBUF_TYPE_TIME_STAMP: +		/* should never happen! */ +		kbuf->curr = kbuf->size; +		kbuf->next = kbuf->size; +		kbuf->index = kbuf->size; +		return -1; +	default: +		if (len) +			length = len * 4; +		else { +			length = read_4(kbuf, ptr); +			length -= 4; +			ptr += 4; +		} +		break; +	} + +	kbuf->timestamp += delta; +	kbuf->index = calc_index(kbuf, ptr); +	kbuf->next = kbuf->index + length; + +	return type; +} + +static int __old_next_event(struct kbuffer *kbuf) +{ +	int type; + +	do { +		kbuf->curr = kbuf->next; +		if (kbuf->next >= kbuf->size) +			return -1; +		type = old_update_pointers(kbuf); +	} while (type == OLD_RINGBUF_TYPE_TIME_EXTEND || type == OLD_RINGBUF_TYPE_PADDING); + +	return 0; +} + +static unsigned int +translate_data(struct kbuffer *kbuf, void *data, void **rptr, +	       unsigned long long *delta, int *length) +{ +	unsigned long long extend; +	unsigned int type_len_ts; +	unsigned int type_len; + +	type_len_ts = read_4(kbuf, data); +	data += 4; + +	type_len = type_len4host(kbuf, type_len_ts); +	*delta = ts4host(kbuf, type_len_ts); + +	switch (type_len) { +	case KBUFFER_TYPE_PADDING: +		*length = read_4(kbuf, data); +		data += *length; +		break; + +	case KBUFFER_TYPE_TIME_EXTEND: +		extend = read_4(kbuf, data); +		data += 4; +		extend <<= TS_SHIFT; +		extend += *delta; +		*delta = extend; +		*length = 0; +		break; + +	case KBUFFER_TYPE_TIME_STAMP: +		data += 12; +		*length = 0; +		break; +	case 0: +		*length = read_4(kbuf, data) - 4; +		*length = (*length + 3) & ~3; +		data += 4; +		break; +	default: +		*length = type_len * 4; +		break; +	} + +	*rptr = data; + +	return type_len; +} + +static unsigned int update_pointers(struct kbuffer *kbuf) +{ +	unsigned long long delta; +	unsigned int type_len; +	int length; +	void *ptr = kbuf->data + kbuf->curr; + +	type_len = translate_data(kbuf, ptr, &ptr, &delta, &length); + +	kbuf->timestamp += delta; +	kbuf->index = calc_index(kbuf, ptr); +	kbuf->next = kbuf->index + length; + +	return type_len; +} + +/** + * kbuffer_translate_data - read raw data to get a record + * @swap:	Set to 1 if bytes in words need to be swapped when read + * @data:	The raw data to read + * @size:	Address to store the size of the event data. + * + * Returns a pointer to the event data. To determine the entire + * record size (record metadata + data) just add the difference between + * @data and the returned value to @size. + */ +void *kbuffer_translate_data(int swap, void *data, unsigned int *size) +{ +	unsigned long long delta; +	struct kbuffer kbuf; +	int type_len; +	int length; +	void *ptr; + +	if (swap) { +		kbuf.read_8 = __read_8_sw; +		kbuf.read_4 = __read_4_sw; +		kbuf.flags = host_is_bigendian() ? 0 : KBUFFER_FL_BIG_ENDIAN; +	} else { +		kbuf.read_8 = __read_8; +		kbuf.read_4 = __read_4; +		kbuf.flags = host_is_bigendian() ? KBUFFER_FL_BIG_ENDIAN: 0; +	} + +	type_len = translate_data(&kbuf, data, &ptr, &delta, &length); +	switch (type_len) { +	case KBUFFER_TYPE_PADDING: +	case KBUFFER_TYPE_TIME_EXTEND: +	case KBUFFER_TYPE_TIME_STAMP: +		return NULL; +	}; + +	*size = length; + +	return ptr; +} + +static int __next_event(struct kbuffer *kbuf) +{ +	int type; + +	do { +		kbuf->curr = kbuf->next; +		if (kbuf->next >= kbuf->size) +			return -1; +		type = update_pointers(kbuf); +	} while (type == KBUFFER_TYPE_TIME_EXTEND || type == KBUFFER_TYPE_PADDING); + +	return 0; +} + +static int next_event(struct kbuffer *kbuf) +{ +	return kbuf->next_event(kbuf); +} + +/** + * kbuffer_next_event - increment the current pointer + * @kbuf:	The kbuffer to read + * @ts:		Address to store the next record's timestamp (may be NULL to ignore) + * + * Increments the pointers into the subbuffer of the kbuffer to point to the + * next event so that the next kbuffer_read_event() will return a + * new event. + * + * Returns the data of the next event if a new event exists on the subbuffer, + * NULL otherwise. + */ +void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts) +{ +	int ret; + +	if (!kbuf || !kbuf->subbuffer) +		return NULL; + +	ret = next_event(kbuf); +	if (ret < 0) +		return NULL; + +	if (ts) +		*ts = kbuf->timestamp; + +	return kbuf->data + kbuf->index; +} + +/** + * kbuffer_load_subbuffer - load a new subbuffer into the kbuffer + * @kbuf:	The kbuffer to load + * @subbuffer:	The subbuffer to load into @kbuf. + * + * Load a new subbuffer (page) into @kbuf. This will reset all + * the pointers and update the @kbuf timestamp. The next read will + * return the first event on @subbuffer. + * + * Returns 0 on succes, -1 otherwise. + */ +int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer) +{ +	unsigned long long flags; +	void *ptr = subbuffer; + +	if (!kbuf || !subbuffer) +		return -1; + +	kbuf->subbuffer = subbuffer; + +	kbuf->timestamp = read_8(kbuf, ptr); +	ptr += 8; + +	kbuf->curr = 0; + +	if (kbuf->flags & KBUFFER_FL_LONG_8) +		kbuf->start = 16; +	else +		kbuf->start = 12; + +	kbuf->data = subbuffer + kbuf->start; + +	flags = read_long(kbuf, ptr); +	kbuf->size = (unsigned int)flags & COMMIT_MASK; + +	if (flags & MISSING_EVENTS) { +		if (flags & MISSING_STORED) { +			ptr = kbuf->data + kbuf->size; +			kbuf->lost_events = read_long(kbuf, ptr); +		} else +			kbuf->lost_events = -1; +	} else +		kbuf->lost_events = 0; + +	kbuf->index = 0; +	kbuf->next = 0; + +	next_event(kbuf); + +	return 0; +} + +/** + * kbuffer_read_event - read the next event in the kbuffer subbuffer + * @kbuf:	The kbuffer to read from + * @ts:		The address to store the timestamp of the event (may be NULL to ignore) + * + * Returns a pointer to the data part of the current event. + * NULL if no event is left on the subbuffer. + */ +void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts) +{ +	if (!kbuf || !kbuf->subbuffer) +		return NULL; + +	if (kbuf->curr >= kbuf->size) +		return NULL; + +	if (ts) +		*ts = kbuf->timestamp; +	return kbuf->data + kbuf->index; +} + +/** + * kbuffer_timestamp - Return the timestamp of the current event + * @kbuf:	The kbuffer to read from + * + * Returns the timestamp of the current (next) event. + */ +unsigned long long kbuffer_timestamp(struct kbuffer *kbuf) +{ +	return kbuf->timestamp; +} + +/** + * kbuffer_read_at_offset - read the event that is at offset + * @kbuf:	The kbuffer to read from + * @offset:	The offset into the subbuffer + * @ts:		The address to store the timestamp of the event (may be NULL to ignore) + * + * The @offset must be an index from the @kbuf subbuffer beginning. + * If @offset is bigger than the stored subbuffer, NULL will be returned. + * + * Returns the data of the record that is at @offset. Note, @offset does + * not need to be the start of the record, the offset just needs to be + * in the record (or beginning of it). + * + * Note, the kbuf timestamp and pointers are updated to the + * returned record. That is, kbuffer_read_event() will return the same + * data and timestamp, and kbuffer_next_event() will increment from + * this record. + */ +void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset, +			     unsigned long long *ts) +{ +	void *data; + +	if (offset < kbuf->start) +		offset = 0; +	else +		offset -= kbuf->start; + +	/* Reset the buffer */ +	kbuffer_load_subbuffer(kbuf, kbuf->subbuffer); + +	while (kbuf->curr < offset) { +		data = kbuffer_next_event(kbuf, ts); +		if (!data) +			break; +	} + +	return data; +} + +/** + * kbuffer_subbuffer_size - the size of the loaded subbuffer + * @kbuf:	The kbuffer to read from + * + * Returns the size of the subbuffer. Note, this size is + * where the last event resides. The stored subbuffer may actually be + * bigger due to padding and such. + */ +int kbuffer_subbuffer_size(struct kbuffer *kbuf) +{ +	return kbuf->size; +} + +/** + * kbuffer_curr_index - Return the index of the record + * @kbuf:	The kbuffer to read from + * + * Returns the index from the start of the data part of + * the subbuffer to the current location. Note this is not + * from the start of the subbuffer. An index of zero will + * point to the first record. Use kbuffer_curr_offset() for + * the actually offset (that can be used by kbuffer_read_at_offset()) + */ +int kbuffer_curr_index(struct kbuffer *kbuf) +{ +	return kbuf->curr; +} + +/** + * kbuffer_curr_offset - Return the offset of the record + * @kbuf:	The kbuffer to read from + * + * Returns the offset from the start of the subbuffer to the + * current location. + */ +int kbuffer_curr_offset(struct kbuffer *kbuf) +{ +	return kbuf->curr + kbuf->start; +} + +/** + * kbuffer_event_size - return the size of the event data + * @kbuf:	The kbuffer to read + * + * Returns the size of the event data (the payload not counting + * the meta data of the record) of the current event. + */ +int kbuffer_event_size(struct kbuffer *kbuf) +{ +	return kbuf->next - kbuf->index; +} + +/** + * kbuffer_curr_size - return the size of the entire record + * @kbuf:	The kbuffer to read + * + * Returns the size of the entire record (meta data and payload) + * of the current event. + */ +int kbuffer_curr_size(struct kbuffer *kbuf) +{ +	return kbuf->next - kbuf->curr; +} + +/** + * kbuffer_missed_events - return the # of missed events from last event. + * @kbuf: 	The kbuffer to read from + * + * Returns the # of missed events (if recorded) before the current + * event. Note, only events on the beginning of a subbuffer can + * have missed events, all other events within the buffer will be + * zero. + */ +int kbuffer_missed_events(struct kbuffer *kbuf) +{ +	/* Only the first event can have missed events */ +	if (kbuf->curr) +		return 0; + +	return kbuf->lost_events; +} + +/** + * kbuffer_set_old_forma - set the kbuffer to use the old format parsing + * @kbuf:	The kbuffer to set + * + * This is obsolete (or should be). The first kernels to use the + * new ring buffer had a slightly different ring buffer format + * (2.6.30 and earlier). It is still somewhat supported by kbuffer, + * but should not be counted on in the future. + */ +void kbuffer_set_old_format(struct kbuffer *kbuf) +{ +	kbuf->flags |= KBUFFER_FL_OLD_FORMAT; + +	kbuf->next_event = __old_next_event; +} diff --git a/tools/lib/traceevent/kbuffer.h b/tools/lib/traceevent/kbuffer.h new file mode 100644 index 00000000000..c831f64b17a --- /dev/null +++ b/tools/lib/traceevent/kbuffer.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef _KBUFFER_H +#define _KBUFFER_H + +#ifndef TS_SHIFT +#define TS_SHIFT		27 +#endif + +enum kbuffer_endian { +	KBUFFER_ENDIAN_BIG, +	KBUFFER_ENDIAN_LITTLE, +}; + +enum kbuffer_long_size { +	KBUFFER_LSIZE_4, +	KBUFFER_LSIZE_8, +}; + +enum { +	KBUFFER_TYPE_PADDING		= 29, +	KBUFFER_TYPE_TIME_EXTEND	= 30, +	KBUFFER_TYPE_TIME_STAMP		= 31, +}; + +struct kbuffer; + +struct kbuffer *kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian); +void kbuffer_free(struct kbuffer *kbuf); +int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer); +void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts); +void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts); +unsigned long long kbuffer_timestamp(struct kbuffer *kbuf); + +void *kbuffer_translate_data(int swap, void *data, unsigned int *size); + +void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset, unsigned long long *ts); + +int kbuffer_curr_index(struct kbuffer *kbuf); + +int kbuffer_curr_offset(struct kbuffer *kbuf); +int kbuffer_curr_size(struct kbuffer *kbuf); +int kbuffer_event_size(struct kbuffer *kbuf); +int kbuffer_missed_events(struct kbuffer *kbuf); +int kbuffer_subbuffer_size(struct kbuffer *kbuf); + +void kbuffer_set_old_format(struct kbuffer *kbuf); + +#endif /* _K_BUFFER_H */ diff --git a/tools/lib/traceevent/parse-filter.c b/tools/lib/traceevent/parse-filter.c new file mode 100644 index 00000000000..b50234402fc --- /dev/null +++ b/tools/lib/traceevent/parse-filter.c @@ -0,0 +1,2432 @@ +/* + * Copyright (C) 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <sys/types.h> + +#include "event-parse.h" +#include "event-utils.h" + +#define COMM "COMM" + +static struct format_field comm = { +	.name = "COMM", +}; + +struct event_list { +	struct event_list	*next; +	struct event_format	*event; +}; + +static void show_error(char *error_buf, const char *fmt, ...) +{ +	unsigned long long index; +	const char *input; +	va_list ap; +	int len; +	int i; + +	input = pevent_get_input_buf(); +	index = pevent_get_input_buf_ptr(); +	len = input ? strlen(input) : 0; + +	if (len) { +		strcpy(error_buf, input); +		error_buf[len] = '\n'; +		for (i = 1; i < len && i < index; i++) +			error_buf[len+i] = ' '; +		error_buf[len + i] = '^'; +		error_buf[len + i + 1] = '\n'; +		len += i+2; +	} + +	va_start(ap, fmt); +	vsnprintf(error_buf + len, PEVENT_FILTER_ERROR_BUFSZ - len, fmt, ap); +	va_end(ap); +} + +static void free_token(char *token) +{ +	pevent_free_token(token); +} + +static enum event_type read_token(char **tok) +{ +	enum event_type type; +	char *token = NULL; + +	do { +		free_token(token); +		type = pevent_read_token(&token); +	} while (type == EVENT_NEWLINE || type == EVENT_SPACE); + +	/* If token is = or ! check to see if the next char is ~ */ +	if (token && +	    (strcmp(token, "=") == 0 || strcmp(token, "!") == 0) && +	    pevent_peek_char() == '~') { +		/* append it */ +		*tok = malloc(3); +		if (*tok == NULL) { +			free_token(token); +			return EVENT_ERROR; +		} +		sprintf(*tok, "%c%c", *token, '~'); +		free_token(token); +		/* Now remove the '~' from the buffer */ +		pevent_read_token(&token); +		free_token(token); +	} else +		*tok = token; + +	return type; +} + +static int filter_cmp(const void *a, const void *b) +{ +	const struct filter_type *ea = a; +	const struct filter_type *eb = b; + +	if (ea->event_id < eb->event_id) +		return -1; + +	if (ea->event_id > eb->event_id) +		return 1; + +	return 0; +} + +static struct filter_type * +find_filter_type(struct event_filter *filter, int id) +{ +	struct filter_type *filter_type; +	struct filter_type key; + +	key.event_id = id; + +	filter_type = bsearch(&key, filter->event_filters, +			      filter->filters, +			      sizeof(*filter->event_filters), +			      filter_cmp); + +	return filter_type; +} + +static struct filter_type * +add_filter_type(struct event_filter *filter, int id) +{ +	struct filter_type *filter_type; +	int i; + +	filter_type = find_filter_type(filter, id); +	if (filter_type) +		return filter_type; + +	filter_type = realloc(filter->event_filters, +			      sizeof(*filter->event_filters) * +			      (filter->filters + 1)); +	if (!filter_type) +		return NULL; + +	filter->event_filters = filter_type; + +	for (i = 0; i < filter->filters; i++) { +		if (filter->event_filters[i].event_id > id) +			break; +	} + +	if (i < filter->filters) +		memmove(&filter->event_filters[i+1], +			&filter->event_filters[i], +			sizeof(*filter->event_filters) * +			(filter->filters - i)); + +	filter_type = &filter->event_filters[i]; +	filter_type->event_id = id; +	filter_type->event = pevent_find_event(filter->pevent, id); +	filter_type->filter = NULL; + +	filter->filters++; + +	return filter_type; +} + +/** + * pevent_filter_alloc - create a new event filter + * @pevent: The pevent that this filter is associated with + */ +struct event_filter *pevent_filter_alloc(struct pevent *pevent) +{ +	struct event_filter *filter; + +	filter = malloc(sizeof(*filter)); +	if (filter == NULL) +		return NULL; + +	memset(filter, 0, sizeof(*filter)); +	filter->pevent = pevent; +	pevent_ref(pevent); + +	return filter; +} + +static struct filter_arg *allocate_arg(void) +{ +	return calloc(1, sizeof(struct filter_arg)); +} + +static void free_arg(struct filter_arg *arg) +{ +	if (!arg) +		return; + +	switch (arg->type) { +	case FILTER_ARG_NONE: +	case FILTER_ARG_BOOLEAN: +		break; + +	case FILTER_ARG_NUM: +		free_arg(arg->num.left); +		free_arg(arg->num.right); +		break; + +	case FILTER_ARG_EXP: +		free_arg(arg->exp.left); +		free_arg(arg->exp.right); +		break; + +	case FILTER_ARG_STR: +		free(arg->str.val); +		regfree(&arg->str.reg); +		free(arg->str.buffer); +		break; + +	case FILTER_ARG_VALUE: +		if (arg->value.type == FILTER_STRING || +		    arg->value.type == FILTER_CHAR) +			free(arg->value.str); +		break; + +	case FILTER_ARG_OP: +		free_arg(arg->op.left); +		free_arg(arg->op.right); +	default: +		break; +	} + +	free(arg); +} + +static int add_event(struct event_list **events, +		      struct event_format *event) +{ +	struct event_list *list; + +	list = malloc(sizeof(*list)); +	if (list == NULL) +		return -1; + +	list->next = *events; +	*events = list; +	list->event = event; +	return 0; +} + +static int event_match(struct event_format *event, +		       regex_t *sreg, regex_t *ereg) +{ +	if (sreg) { +		return !regexec(sreg, event->system, 0, NULL, 0) && +			!regexec(ereg, event->name, 0, NULL, 0); +	} + +	return !regexec(ereg, event->system, 0, NULL, 0) || +		!regexec(ereg, event->name, 0, NULL, 0); +} + +static enum pevent_errno +find_event(struct pevent *pevent, struct event_list **events, +	   char *sys_name, char *event_name) +{ +	struct event_format *event; +	regex_t ereg; +	regex_t sreg; +	int match = 0; +	int fail = 0; +	char *reg; +	int ret; +	int i; + +	if (!event_name) { +		/* if no name is given, then swap sys and name */ +		event_name = sys_name; +		sys_name = NULL; +	} + +	reg = malloc(strlen(event_name) + 3); +	if (reg == NULL) +		return PEVENT_ERRNO__MEM_ALLOC_FAILED; + +	sprintf(reg, "^%s$", event_name); + +	ret = regcomp(&ereg, reg, REG_ICASE|REG_NOSUB); +	free(reg); + +	if (ret) +		return PEVENT_ERRNO__INVALID_EVENT_NAME; + +	if (sys_name) { +		reg = malloc(strlen(sys_name) + 3); +		if (reg == NULL) { +			regfree(&ereg); +			return PEVENT_ERRNO__MEM_ALLOC_FAILED; +		} + +		sprintf(reg, "^%s$", sys_name); +		ret = regcomp(&sreg, reg, REG_ICASE|REG_NOSUB); +		free(reg); +		if (ret) { +			regfree(&ereg); +			return PEVENT_ERRNO__INVALID_EVENT_NAME; +		} +	} + +	for (i = 0; i < pevent->nr_events; i++) { +		event = pevent->events[i]; +		if (event_match(event, sys_name ? &sreg : NULL, &ereg)) { +			match = 1; +			if (add_event(events, event) < 0) { +				fail = 1; +				break; +			} +		} +	} + +	regfree(&ereg); +	if (sys_name) +		regfree(&sreg); + +	if (!match) +		return PEVENT_ERRNO__EVENT_NOT_FOUND; +	if (fail) +		return PEVENT_ERRNO__MEM_ALLOC_FAILED; + +	return 0; +} + +static void free_events(struct event_list *events) +{ +	struct event_list *event; + +	while (events) { +		event = events; +		events = events->next; +		free(event); +	} +} + +static enum pevent_errno +create_arg_item(struct event_format *event, const char *token, +		enum event_type type, struct filter_arg **parg, char *error_str) +{ +	struct format_field *field; +	struct filter_arg *arg; + +	arg = allocate_arg(); +	if (arg == NULL) { +		show_error(error_str, "failed to allocate filter arg"); +		return PEVENT_ERRNO__MEM_ALLOC_FAILED; +	} + +	switch (type) { + +	case EVENT_SQUOTE: +	case EVENT_DQUOTE: +		arg->type = FILTER_ARG_VALUE; +		arg->value.type = +			type == EVENT_DQUOTE ? FILTER_STRING : FILTER_CHAR; +		arg->value.str = strdup(token); +		if (!arg->value.str) { +			free_arg(arg); +			show_error(error_str, "failed to allocate string filter arg"); +			return PEVENT_ERRNO__MEM_ALLOC_FAILED; +		} +		break; +	case EVENT_ITEM: +		/* if it is a number, then convert it */ +		if (isdigit(token[0])) { +			arg->type = FILTER_ARG_VALUE; +			arg->value.type = FILTER_NUMBER; +			arg->value.val = strtoull(token, NULL, 0); +			break; +		} +		/* Consider this a field */ +		field = pevent_find_any_field(event, token); +		if (!field) { +			if (strcmp(token, COMM) != 0) { +				/* not a field, Make it false */ +				arg->type = FILTER_ARG_BOOLEAN; +				arg->boolean.value = FILTER_FALSE; +				break; +			} +			/* If token is 'COMM' then it is special */ +			field = &comm; +		} +		arg->type = FILTER_ARG_FIELD; +		arg->field.field = field; +		break; +	default: +		free_arg(arg); +		show_error(error_str, "expected a value but found %s", token); +		return PEVENT_ERRNO__UNEXPECTED_TYPE; +	} +	*parg = arg; +	return 0; +} + +static struct filter_arg * +create_arg_op(enum filter_op_type btype) +{ +	struct filter_arg *arg; + +	arg = allocate_arg(); +	if (!arg) +		return NULL; + +	arg->type = FILTER_ARG_OP; +	arg->op.type = btype; + +	return arg; +} + +static struct filter_arg * +create_arg_exp(enum filter_exp_type etype) +{ +	struct filter_arg *arg; + +	arg = allocate_arg(); +	if (!arg) +		return NULL; + +	arg->type = FILTER_ARG_EXP; +	arg->op.type = etype; + +	return arg; +} + +static struct filter_arg * +create_arg_cmp(enum filter_exp_type etype) +{ +	struct filter_arg *arg; + +	arg = allocate_arg(); +	if (!arg) +		return NULL; + +	/* Use NUM and change if necessary */ +	arg->type = FILTER_ARG_NUM; +	arg->op.type = etype; + +	return arg; +} + +static enum pevent_errno +add_right(struct filter_arg *op, struct filter_arg *arg, char *error_str) +{ +	struct filter_arg *left; +	char *str; +	int op_type; +	int ret; + +	switch (op->type) { +	case FILTER_ARG_EXP: +		if (op->exp.right) +			goto out_fail; +		op->exp.right = arg; +		break; + +	case FILTER_ARG_OP: +		if (op->op.right) +			goto out_fail; +		op->op.right = arg; +		break; + +	case FILTER_ARG_NUM: +		if (op->op.right) +			goto out_fail; +		/* +		 * The arg must be num, str, or field +		 */ +		switch (arg->type) { +		case FILTER_ARG_VALUE: +		case FILTER_ARG_FIELD: +			break; +		default: +			show_error(error_str, "Illegal rvalue"); +			return PEVENT_ERRNO__ILLEGAL_RVALUE; +		} + +		/* +		 * Depending on the type, we may need to +		 * convert this to a string or regex. +		 */ +		switch (arg->value.type) { +		case FILTER_CHAR: +			/* +			 * A char should be converted to number if +			 * the string is 1 byte, and the compare +			 * is not a REGEX. +			 */ +			if (strlen(arg->value.str) == 1 && +			    op->num.type != FILTER_CMP_REGEX && +			    op->num.type != FILTER_CMP_NOT_REGEX) { +				arg->value.type = FILTER_NUMBER; +				goto do_int; +			} +			/* fall through */ +		case FILTER_STRING: + +			/* convert op to a string arg */ +			op_type = op->num.type; +			left = op->num.left; +			str = arg->value.str; + +			/* reset the op for the new field */ +			memset(op, 0, sizeof(*op)); + +			/* +			 * If left arg was a field not found then +			 * NULL the entire op. +			 */ +			if (left->type == FILTER_ARG_BOOLEAN) { +				free_arg(left); +				free_arg(arg); +				op->type = FILTER_ARG_BOOLEAN; +				op->boolean.value = FILTER_FALSE; +				break; +			} + +			/* Left arg must be a field */ +			if (left->type != FILTER_ARG_FIELD) { +				show_error(error_str, +					   "Illegal lvalue for string comparison"); +				return PEVENT_ERRNO__ILLEGAL_LVALUE; +			} + +			/* Make sure this is a valid string compare */ +			switch (op_type) { +			case FILTER_CMP_EQ: +				op_type = FILTER_CMP_MATCH; +				break; +			case FILTER_CMP_NE: +				op_type = FILTER_CMP_NOT_MATCH; +				break; + +			case FILTER_CMP_REGEX: +			case FILTER_CMP_NOT_REGEX: +				ret = regcomp(&op->str.reg, str, REG_ICASE|REG_NOSUB); +				if (ret) { +					show_error(error_str, +						   "RegEx '%s' did not compute", +						   str); +					return PEVENT_ERRNO__INVALID_REGEX; +				} +				break; +			default: +				show_error(error_str, +					   "Illegal comparison for string"); +				return PEVENT_ERRNO__ILLEGAL_STRING_CMP; +			} + +			op->type = FILTER_ARG_STR; +			op->str.type = op_type; +			op->str.field = left->field.field; +			op->str.val = strdup(str); +			if (!op->str.val) { +				show_error(error_str, "Failed to allocate string filter"); +				return PEVENT_ERRNO__MEM_ALLOC_FAILED; +			} +			/* +			 * Need a buffer to copy data for tests +			 */ +			op->str.buffer = malloc(op->str.field->size + 1); +			if (!op->str.buffer) { +				show_error(error_str, "Failed to allocate string filter"); +				return PEVENT_ERRNO__MEM_ALLOC_FAILED; +			} +			/* Null terminate this buffer */ +			op->str.buffer[op->str.field->size] = 0; + +			/* We no longer have left or right args */ +			free_arg(arg); +			free_arg(left); + +			break; + +		case FILTER_NUMBER: + + do_int: +			switch (op->num.type) { +			case FILTER_CMP_REGEX: +			case FILTER_CMP_NOT_REGEX: +				show_error(error_str, +					   "Op not allowed with integers"); +				return PEVENT_ERRNO__ILLEGAL_INTEGER_CMP; + +			default: +				break; +			} + +			/* numeric compare */ +			op->num.right = arg; +			break; +		default: +			goto out_fail; +		} +		break; +	default: +		goto out_fail; +	} + +	return 0; + + out_fail: +	show_error(error_str, "Syntax error"); +	return PEVENT_ERRNO__SYNTAX_ERROR; +} + +static struct filter_arg * +rotate_op_right(struct filter_arg *a, struct filter_arg *b) +{ +	struct filter_arg *arg; + +	arg = a->op.right; +	a->op.right = b; +	return arg; +} + +static enum pevent_errno add_left(struct filter_arg *op, struct filter_arg *arg) +{ +	switch (op->type) { +	case FILTER_ARG_EXP: +		if (arg->type == FILTER_ARG_OP) +			arg = rotate_op_right(arg, op); +		op->exp.left = arg; +		break; + +	case FILTER_ARG_OP: +		op->op.left = arg; +		break; +	case FILTER_ARG_NUM: +		if (arg->type == FILTER_ARG_OP) +			arg = rotate_op_right(arg, op); + +		/* left arg of compares must be a field */ +		if (arg->type != FILTER_ARG_FIELD && +		    arg->type != FILTER_ARG_BOOLEAN) +			return PEVENT_ERRNO__INVALID_ARG_TYPE; +		op->num.left = arg; +		break; +	default: +		return PEVENT_ERRNO__INVALID_ARG_TYPE; +	} +	return 0; +} + +enum op_type { +	OP_NONE, +	OP_BOOL, +	OP_NOT, +	OP_EXP, +	OP_CMP, +}; + +static enum op_type process_op(const char *token, +			       enum filter_op_type *btype, +			       enum filter_cmp_type *ctype, +			       enum filter_exp_type *etype) +{ +	*btype = FILTER_OP_NOT; +	*etype = FILTER_EXP_NONE; +	*ctype = FILTER_CMP_NONE; + +	if (strcmp(token, "&&") == 0) +		*btype = FILTER_OP_AND; +	else if (strcmp(token, "||") == 0) +		*btype = FILTER_OP_OR; +	else if (strcmp(token, "!") == 0) +		return OP_NOT; + +	if (*btype != FILTER_OP_NOT) +		return OP_BOOL; + +	/* Check for value expressions */ +	if (strcmp(token, "+") == 0) { +		*etype = FILTER_EXP_ADD; +	} else if (strcmp(token, "-") == 0) { +		*etype = FILTER_EXP_SUB; +	} else if (strcmp(token, "*") == 0) { +		*etype = FILTER_EXP_MUL; +	} else if (strcmp(token, "/") == 0) { +		*etype = FILTER_EXP_DIV; +	} else if (strcmp(token, "%") == 0) { +		*etype = FILTER_EXP_MOD; +	} else if (strcmp(token, ">>") == 0) { +		*etype = FILTER_EXP_RSHIFT; +	} else if (strcmp(token, "<<") == 0) { +		*etype = FILTER_EXP_LSHIFT; +	} else if (strcmp(token, "&") == 0) { +		*etype = FILTER_EXP_AND; +	} else if (strcmp(token, "|") == 0) { +		*etype = FILTER_EXP_OR; +	} else if (strcmp(token, "^") == 0) { +		*etype = FILTER_EXP_XOR; +	} else if (strcmp(token, "~") == 0) +		*etype = FILTER_EXP_NOT; + +	if (*etype != FILTER_EXP_NONE) +		return OP_EXP; + +	/* Check for compares */ +	if (strcmp(token, "==") == 0) +		*ctype = FILTER_CMP_EQ; +	else if (strcmp(token, "!=") == 0) +		*ctype = FILTER_CMP_NE; +	else if (strcmp(token, "<") == 0) +		*ctype = FILTER_CMP_LT; +	else if (strcmp(token, ">") == 0) +		*ctype = FILTER_CMP_GT; +	else if (strcmp(token, "<=") == 0) +		*ctype = FILTER_CMP_LE; +	else if (strcmp(token, ">=") == 0) +		*ctype = FILTER_CMP_GE; +	else if (strcmp(token, "=~") == 0) +		*ctype = FILTER_CMP_REGEX; +	else if (strcmp(token, "!~") == 0) +		*ctype = FILTER_CMP_NOT_REGEX; +	else +		return OP_NONE; + +	return OP_CMP; +} + +static int check_op_done(struct filter_arg *arg) +{ +	switch (arg->type) { +	case FILTER_ARG_EXP: +		return arg->exp.right != NULL; + +	case FILTER_ARG_OP: +		return arg->op.right != NULL; + +	case FILTER_ARG_NUM: +		return arg->num.right != NULL; + +	case FILTER_ARG_STR: +		/* A string conversion is always done */ +		return 1; + +	case FILTER_ARG_BOOLEAN: +		/* field not found, is ok */ +		return 1; + +	default: +		return 0; +	} +} + +enum filter_vals { +	FILTER_VAL_NORM, +	FILTER_VAL_FALSE, +	FILTER_VAL_TRUE, +}; + +static enum pevent_errno +reparent_op_arg(struct filter_arg *parent, struct filter_arg *old_child, +		struct filter_arg *arg, char *error_str) +{ +	struct filter_arg *other_child; +	struct filter_arg **ptr; + +	if (parent->type != FILTER_ARG_OP && +	    arg->type != FILTER_ARG_OP) { +		show_error(error_str, "can not reparent other than OP"); +		return PEVENT_ERRNO__REPARENT_NOT_OP; +	} + +	/* Get the sibling */ +	if (old_child->op.right == arg) { +		ptr = &old_child->op.right; +		other_child = old_child->op.left; +	} else if (old_child->op.left == arg) { +		ptr = &old_child->op.left; +		other_child = old_child->op.right; +	} else { +		show_error(error_str, "Error in reparent op, find other child"); +		return PEVENT_ERRNO__REPARENT_FAILED; +	} + +	/* Detach arg from old_child */ +	*ptr = NULL; + +	/* Check for root */ +	if (parent == old_child) { +		free_arg(other_child); +		*parent = *arg; +		/* Free arg without recussion */ +		free(arg); +		return 0; +	} + +	if (parent->op.right == old_child) +		ptr = &parent->op.right; +	else if (parent->op.left == old_child) +		ptr = &parent->op.left; +	else { +		show_error(error_str, "Error in reparent op"); +		return PEVENT_ERRNO__REPARENT_FAILED; +	} + +	*ptr = arg; + +	free_arg(old_child); +	return 0; +} + +/* Returns either filter_vals (success) or pevent_errno (failfure) */ +static int test_arg(struct filter_arg *parent, struct filter_arg *arg, +		    char *error_str) +{ +	int lval, rval; + +	switch (arg->type) { + +		/* bad case */ +	case FILTER_ARG_BOOLEAN: +		return FILTER_VAL_FALSE + arg->boolean.value; + +		/* good cases: */ +	case FILTER_ARG_STR: +	case FILTER_ARG_VALUE: +	case FILTER_ARG_FIELD: +		return FILTER_VAL_NORM; + +	case FILTER_ARG_EXP: +		lval = test_arg(arg, arg->exp.left, error_str); +		if (lval != FILTER_VAL_NORM) +			return lval; +		rval = test_arg(arg, arg->exp.right, error_str); +		if (rval != FILTER_VAL_NORM) +			return rval; +		return FILTER_VAL_NORM; + +	case FILTER_ARG_NUM: +		lval = test_arg(arg, arg->num.left, error_str); +		if (lval != FILTER_VAL_NORM) +			return lval; +		rval = test_arg(arg, arg->num.right, error_str); +		if (rval != FILTER_VAL_NORM) +			return rval; +		return FILTER_VAL_NORM; + +	case FILTER_ARG_OP: +		if (arg->op.type != FILTER_OP_NOT) { +			lval = test_arg(arg, arg->op.left, error_str); +			switch (lval) { +			case FILTER_VAL_NORM: +				break; +			case FILTER_VAL_TRUE: +				if (arg->op.type == FILTER_OP_OR) +					return FILTER_VAL_TRUE; +				rval = test_arg(arg, arg->op.right, error_str); +				if (rval != FILTER_VAL_NORM) +					return rval; + +				return reparent_op_arg(parent, arg, arg->op.right, +						       error_str); + +			case FILTER_VAL_FALSE: +				if (arg->op.type == FILTER_OP_AND) +					return FILTER_VAL_FALSE; +				rval = test_arg(arg, arg->op.right, error_str); +				if (rval != FILTER_VAL_NORM) +					return rval; + +				return reparent_op_arg(parent, arg, arg->op.right, +						       error_str); + +			default: +				return lval; +			} +		} + +		rval = test_arg(arg, arg->op.right, error_str); +		switch (rval) { +		case FILTER_VAL_NORM: +		default: +			break; + +		case FILTER_VAL_TRUE: +			if (arg->op.type == FILTER_OP_OR) +				return FILTER_VAL_TRUE; +			if (arg->op.type == FILTER_OP_NOT) +				return FILTER_VAL_FALSE; + +			return reparent_op_arg(parent, arg, arg->op.left, +					       error_str); + +		case FILTER_VAL_FALSE: +			if (arg->op.type == FILTER_OP_AND) +				return FILTER_VAL_FALSE; +			if (arg->op.type == FILTER_OP_NOT) +				return FILTER_VAL_TRUE; + +			return reparent_op_arg(parent, arg, arg->op.left, +					       error_str); +		} + +		return rval; +	default: +		show_error(error_str, "bad arg in filter tree"); +		return PEVENT_ERRNO__BAD_FILTER_ARG; +	} +	return FILTER_VAL_NORM; +} + +/* Remove any unknown event fields */ +static int collapse_tree(struct filter_arg *arg, +			 struct filter_arg **arg_collapsed, char *error_str) +{ +	int ret; + +	ret = test_arg(arg, arg, error_str); +	switch (ret) { +	case FILTER_VAL_NORM: +		break; + +	case FILTER_VAL_TRUE: +	case FILTER_VAL_FALSE: +		free_arg(arg); +		arg = allocate_arg(); +		if (arg) { +			arg->type = FILTER_ARG_BOOLEAN; +			arg->boolean.value = ret == FILTER_VAL_TRUE; +		} else { +			show_error(error_str, "Failed to allocate filter arg"); +			ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; +		} +		break; + +	default: +		/* test_arg() already set the error_str */ +		free_arg(arg); +		arg = NULL; +		break; +	} + +	*arg_collapsed = arg; +	return ret; +} + +static enum pevent_errno +process_filter(struct event_format *event, struct filter_arg **parg, +	       char *error_str, int not) +{ +	enum event_type type; +	char *token = NULL; +	struct filter_arg *current_op = NULL; +	struct filter_arg *current_exp = NULL; +	struct filter_arg *left_item = NULL; +	struct filter_arg *arg = NULL; +	enum op_type op_type; +	enum filter_op_type btype; +	enum filter_exp_type etype; +	enum filter_cmp_type ctype; +	enum pevent_errno ret; + +	*parg = NULL; + +	do { +		free(token); +		type = read_token(&token); +		switch (type) { +		case EVENT_SQUOTE: +		case EVENT_DQUOTE: +		case EVENT_ITEM: +			ret = create_arg_item(event, token, type, &arg, error_str); +			if (ret < 0) +				goto fail; +			if (!left_item) +				left_item = arg; +			else if (current_exp) { +				ret = add_right(current_exp, arg, error_str); +				if (ret < 0) +					goto fail; +				left_item = NULL; +				/* Not's only one one expression */ +				if (not) { +					arg = NULL; +					if (current_op) +						goto fail_syntax; +					free(token); +					*parg = current_exp; +					return 0; +				} +			} else +				goto fail_syntax; +			arg = NULL; +			break; + +		case EVENT_DELIM: +			if (*token == ',') { +				show_error(error_str, "Illegal token ','"); +				ret = PEVENT_ERRNO__ILLEGAL_TOKEN; +				goto fail; +			} + +			if (*token == '(') { +				if (left_item) { +					show_error(error_str, +						   "Open paren can not come after item"); +					ret = PEVENT_ERRNO__INVALID_PAREN; +					goto fail; +				} +				if (current_exp) { +					show_error(error_str, +						   "Open paren can not come after expression"); +					ret = PEVENT_ERRNO__INVALID_PAREN; +					goto fail; +				} + +				ret = process_filter(event, &arg, error_str, 0); +				if (ret != PEVENT_ERRNO__UNBALANCED_PAREN) { +					if (ret == 0) { +						show_error(error_str, +							   "Unbalanced number of '('"); +						ret = PEVENT_ERRNO__UNBALANCED_PAREN; +					} +					goto fail; +				} +				ret = 0; + +				/* A not wants just one expression */ +				if (not) { +					if (current_op) +						goto fail_syntax; +					*parg = arg; +					return 0; +				} + +				if (current_op) +					ret = add_right(current_op, arg, error_str); +				else +					current_exp = arg; + +				if (ret < 0) +					goto fail; + +			} else { /* ')' */ +				if (!current_op && !current_exp) +					goto fail_syntax; + +				/* Make sure everything is finished at this level */ +				if (current_exp && !check_op_done(current_exp)) +					goto fail_syntax; +				if (current_op && !check_op_done(current_op)) +					goto fail_syntax; + +				if (current_op) +					*parg = current_op; +				else +					*parg = current_exp; +				return PEVENT_ERRNO__UNBALANCED_PAREN; +			} +			break; + +		case EVENT_OP: +			op_type = process_op(token, &btype, &ctype, &etype); + +			/* All expect a left arg except for NOT */ +			switch (op_type) { +			case OP_BOOL: +				/* Logic ops need a left expression */ +				if (!current_exp && !current_op) +					goto fail_syntax; +				/* fall through */ +			case OP_NOT: +				/* logic only processes ops and exp */ +				if (left_item) +					goto fail_syntax; +				break; +			case OP_EXP: +			case OP_CMP: +				if (!left_item) +					goto fail_syntax; +				break; +			case OP_NONE: +				show_error(error_str, +					   "Unknown op token %s", token); +				ret = PEVENT_ERRNO__UNKNOWN_TOKEN; +				goto fail; +			} + +			ret = 0; +			switch (op_type) { +			case OP_BOOL: +				arg = create_arg_op(btype); +				if (arg == NULL) +					goto fail_alloc; +				if (current_op) +					ret = add_left(arg, current_op); +				else +					ret = add_left(arg, current_exp); +				current_op = arg; +				current_exp = NULL; +				break; + +			case OP_NOT: +				arg = create_arg_op(btype); +				if (arg == NULL) +					goto fail_alloc; +				if (current_op) +					ret = add_right(current_op, arg, error_str); +				if (ret < 0) +					goto fail; +				current_exp = arg; +				ret = process_filter(event, &arg, error_str, 1); +				if (ret < 0) +					goto fail; +				ret = add_right(current_exp, arg, error_str); +				if (ret < 0) +					goto fail; +				break; + +			case OP_EXP: +			case OP_CMP: +				if (op_type == OP_EXP) +					arg = create_arg_exp(etype); +				else +					arg = create_arg_cmp(ctype); +				if (arg == NULL) +					goto fail_alloc; + +				if (current_op) +					ret = add_right(current_op, arg, error_str); +				if (ret < 0) +					goto fail; +				ret = add_left(arg, left_item); +				if (ret < 0) { +					arg = NULL; +					goto fail_syntax; +				} +				current_exp = arg; +				break; +			default: +				break; +			} +			arg = NULL; +			if (ret < 0) +				goto fail_syntax; +			break; +		case EVENT_NONE: +			break; +		case EVENT_ERROR: +			goto fail_alloc; +		default: +			goto fail_syntax; +		} +	} while (type != EVENT_NONE); + +	if (!current_op && !current_exp) +		goto fail_syntax; + +	if (!current_op) +		current_op = current_exp; + +	ret = collapse_tree(current_op, parg, error_str); +	if (ret < 0) +		goto fail; + +	*parg = current_op; + +	return 0; + + fail_alloc: +	show_error(error_str, "failed to allocate filter arg"); +	ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; +	goto fail; + fail_syntax: +	show_error(error_str, "Syntax error"); +	ret = PEVENT_ERRNO__SYNTAX_ERROR; + fail: +	free_arg(current_op); +	free_arg(current_exp); +	free_arg(arg); +	free(token); +	return ret; +} + +static enum pevent_errno +process_event(struct event_format *event, const char *filter_str, +	      struct filter_arg **parg, char *error_str) +{ +	int ret; + +	pevent_buffer_init(filter_str, strlen(filter_str)); + +	ret = process_filter(event, parg, error_str, 0); +	if (ret < 0) +		return ret; + +	/* If parg is NULL, then make it into FALSE */ +	if (!*parg) { +		*parg = allocate_arg(); +		if (*parg == NULL) +			return PEVENT_ERRNO__MEM_ALLOC_FAILED; + +		(*parg)->type = FILTER_ARG_BOOLEAN; +		(*parg)->boolean.value = FILTER_FALSE; +	} + +	return 0; +} + +static enum pevent_errno +filter_event(struct event_filter *filter, struct event_format *event, +	     const char *filter_str, char *error_str) +{ +	struct filter_type *filter_type; +	struct filter_arg *arg; +	enum pevent_errno ret; + +	if (filter_str) { +		ret = process_event(event, filter_str, &arg, error_str); +		if (ret < 0) +			return ret; + +	} else { +		/* just add a TRUE arg */ +		arg = allocate_arg(); +		if (arg == NULL) +			return PEVENT_ERRNO__MEM_ALLOC_FAILED; + +		arg->type = FILTER_ARG_BOOLEAN; +		arg->boolean.value = FILTER_TRUE; +	} + +	filter_type = add_filter_type(filter, event->id); +	if (filter_type == NULL) +		return PEVENT_ERRNO__MEM_ALLOC_FAILED; + +	if (filter_type->filter) +		free_arg(filter_type->filter); +	filter_type->filter = arg; + +	return 0; +} + +static void filter_init_error_buf(struct event_filter *filter) +{ +	/* clear buffer to reset show error */ +	pevent_buffer_init("", 0); +	filter->error_buffer[0] = '\0'; +} + +/** + * pevent_filter_add_filter_str - add a new filter + * @filter: the event filter to add to + * @filter_str: the filter string that contains the filter + * + * Returns 0 if the filter was successfully added or a + * negative error code.  Use pevent_filter_strerror() to see + * actual error message in case of error. + */ +enum pevent_errno pevent_filter_add_filter_str(struct event_filter *filter, +					       const char *filter_str) +{ +	struct pevent *pevent = filter->pevent; +	struct event_list *event; +	struct event_list *events = NULL; +	const char *filter_start; +	const char *next_event; +	char *this_event; +	char *event_name = NULL; +	char *sys_name = NULL; +	char *sp; +	enum pevent_errno rtn = 0; /* PEVENT_ERRNO__SUCCESS */ +	int len; +	int ret; + +	filter_init_error_buf(filter); + +	filter_start = strchr(filter_str, ':'); +	if (filter_start) +		len = filter_start - filter_str; +	else +		len = strlen(filter_str); + +	do { +		next_event = strchr(filter_str, ','); +		if (next_event && +		    (!filter_start || next_event < filter_start)) +			len = next_event - filter_str; +		else if (filter_start) +			len = filter_start - filter_str; +		else +			len = strlen(filter_str); + +		this_event = malloc(len + 1); +		if (this_event == NULL) { +			/* This can only happen when events is NULL, but still */ +			free_events(events); +			return PEVENT_ERRNO__MEM_ALLOC_FAILED; +		} +		memcpy(this_event, filter_str, len); +		this_event[len] = 0; + +		if (next_event) +			next_event++; + +		filter_str = next_event; + +		sys_name = strtok_r(this_event, "/", &sp); +		event_name = strtok_r(NULL, "/", &sp); + +		if (!sys_name) { +			/* This can only happen when events is NULL, but still */ +			free_events(events); +			free(this_event); +			return PEVENT_ERRNO__FILTER_NOT_FOUND; +		} + +		/* Find this event */ +		ret = find_event(pevent, &events, strim(sys_name), strim(event_name)); +		if (ret < 0) { +			free_events(events); +			free(this_event); +			return ret; +		} +		free(this_event); +	} while (filter_str); + +	/* Skip the ':' */ +	if (filter_start) +		filter_start++; + +	/* filter starts here */ +	for (event = events; event; event = event->next) { +		ret = filter_event(filter, event->event, filter_start, +				   filter->error_buffer); +		/* Failures are returned if a parse error happened */ +		if (ret < 0) +			rtn = ret; + +		if (ret >= 0 && pevent->test_filters) { +			char *test; +			test = pevent_filter_make_string(filter, event->event->id); +			if (test) { +				printf(" '%s: %s'\n", event->event->name, test); +				free(test); +			} +		} +	} + +	free_events(events); + +	if (rtn >= 0 && pevent->test_filters) +		exit(0); + +	return rtn; +} + +static void free_filter_type(struct filter_type *filter_type) +{ +	free_arg(filter_type->filter); +} + +/** + * pevent_filter_strerror - fill error message in a buffer + * @filter: the event filter contains error + * @err: the error code + * @buf: the buffer to be filled in + * @buflen: the size of the buffer + * + * Returns 0 if message was filled successfully, -1 if error + */ +int pevent_filter_strerror(struct event_filter *filter, enum pevent_errno err, +			   char *buf, size_t buflen) +{ +	if (err <= __PEVENT_ERRNO__START || err >= __PEVENT_ERRNO__END) +		return -1; + +	if (strlen(filter->error_buffer) > 0) { +		size_t len = snprintf(buf, buflen, "%s", filter->error_buffer); + +		if (len > buflen) +			return -1; +		return 0; +	} + +	return pevent_strerror(filter->pevent, err, buf, buflen); +} + +/** + * pevent_filter_remove_event - remove a filter for an event + * @filter: the event filter to remove from + * @event_id: the event to remove a filter for + * + * Removes the filter saved for an event defined by @event_id + * from the @filter. + * + * Returns 1: if an event was removed + *   0: if the event was not found + */ +int pevent_filter_remove_event(struct event_filter *filter, +			       int event_id) +{ +	struct filter_type *filter_type; +	unsigned long len; + +	if (!filter->filters) +		return 0; + +	filter_type = find_filter_type(filter, event_id); + +	if (!filter_type) +		return 0; + +	free_filter_type(filter_type); + +	/* The filter_type points into the event_filters array */ +	len = (unsigned long)(filter->event_filters + filter->filters) - +		(unsigned long)(filter_type + 1); + +	memmove(filter_type, filter_type + 1, len); +	filter->filters--; + +	memset(&filter->event_filters[filter->filters], 0, +	       sizeof(*filter_type)); + +	return 1; +} + +/** + * pevent_filter_reset - clear all filters in a filter + * @filter: the event filter to reset + * + * Removes all filters from a filter and resets it. + */ +void pevent_filter_reset(struct event_filter *filter) +{ +	int i; + +	for (i = 0; i < filter->filters; i++) +		free_filter_type(&filter->event_filters[i]); + +	free(filter->event_filters); +	filter->filters = 0; +	filter->event_filters = NULL; +} + +void pevent_filter_free(struct event_filter *filter) +{ +	pevent_unref(filter->pevent); + +	pevent_filter_reset(filter); + +	free(filter); +} + +static char *arg_to_str(struct event_filter *filter, struct filter_arg *arg); + +static int copy_filter_type(struct event_filter *filter, +			     struct event_filter *source, +			     struct filter_type *filter_type) +{ +	struct filter_arg *arg; +	struct event_format *event; +	const char *sys; +	const char *name; +	char *str; + +	/* Can't assume that the pevent's are the same */ +	sys = filter_type->event->system; +	name = filter_type->event->name; +	event = pevent_find_event_by_name(filter->pevent, sys, name); +	if (!event) +		return -1; + +	str = arg_to_str(source, filter_type->filter); +	if (!str) +		return -1; + +	if (strcmp(str, "TRUE") == 0 || strcmp(str, "FALSE") == 0) { +		/* Add trivial event */ +		arg = allocate_arg(); +		if (arg == NULL) +			return -1; + +		arg->type = FILTER_ARG_BOOLEAN; +		if (strcmp(str, "TRUE") == 0) +			arg->boolean.value = 1; +		else +			arg->boolean.value = 0; + +		filter_type = add_filter_type(filter, event->id); +		if (filter_type == NULL) +			return -1; + +		filter_type->filter = arg; + +		free(str); +		return 0; +	} + +	filter_event(filter, event, str, NULL); +	free(str); + +	return 0; +} + +/** + * pevent_filter_copy - copy a filter using another filter + * @dest - the filter to copy to + * @source - the filter to copy from + * + * Returns 0 on success and -1 if not all filters were copied + */ +int pevent_filter_copy(struct event_filter *dest, struct event_filter *source) +{ +	int ret = 0; +	int i; + +	pevent_filter_reset(dest); + +	for (i = 0; i < source->filters; i++) { +		if (copy_filter_type(dest, source, &source->event_filters[i])) +			ret = -1; +	} +	return ret; +} + + +/** + * pevent_update_trivial - update the trivial filters with the given filter + * @dest - the filter to update + * @source - the filter as the source of the update + * @type - the type of trivial filter to update. + * + * Scan dest for trivial events matching @type to replace with the source. + * + * Returns 0 on success and -1 if there was a problem updating, but + *   events may have still been updated on error. + */ +int pevent_update_trivial(struct event_filter *dest, struct event_filter *source, +			  enum filter_trivial_type type) +{ +	struct pevent *src_pevent; +	struct pevent *dest_pevent; +	struct event_format *event; +	struct filter_type *filter_type; +	struct filter_arg *arg; +	char *str; +	int i; + +	src_pevent = source->pevent; +	dest_pevent = dest->pevent; + +	/* Do nothing if either of the filters has nothing to filter */ +	if (!dest->filters || !source->filters) +		return 0; + +	for (i = 0; i < dest->filters; i++) { +		filter_type = &dest->event_filters[i]; +		arg = filter_type->filter; +		if (arg->type != FILTER_ARG_BOOLEAN) +			continue; +		if ((arg->boolean.value && type == FILTER_TRIVIAL_FALSE) || +		    (!arg->boolean.value && type == FILTER_TRIVIAL_TRUE)) +			continue; + +		event = filter_type->event; + +		if (src_pevent != dest_pevent) { +			/* do a look up */ +			event = pevent_find_event_by_name(src_pevent, +							  event->system, +							  event->name); +			if (!event) +				return -1; +		} + +		str = pevent_filter_make_string(source, event->id); +		if (!str) +			continue; + +		/* Don't bother if the filter is trivial too */ +		if (strcmp(str, "TRUE") != 0 && strcmp(str, "FALSE") != 0) +			filter_event(dest, event, str, NULL); +		free(str); +	} +	return 0; +} + +/** + * pevent_filter_clear_trivial - clear TRUE and FALSE filters + * @filter: the filter to remove trivial filters from + * @type: remove only true, false, or both + * + * Removes filters that only contain a TRUE or FALES boolean arg. + * + * Returns 0 on success and -1 if there was a problem. + */ +int pevent_filter_clear_trivial(struct event_filter *filter, +				 enum filter_trivial_type type) +{ +	struct filter_type *filter_type; +	int count = 0; +	int *ids = NULL; +	int i; + +	if (!filter->filters) +		return 0; + +	/* +	 * Two steps, first get all ids with trivial filters. +	 *  then remove those ids. +	 */ +	for (i = 0; i < filter->filters; i++) { +		int *new_ids; + +		filter_type = &filter->event_filters[i]; +		if (filter_type->filter->type != FILTER_ARG_BOOLEAN) +			continue; +		switch (type) { +		case FILTER_TRIVIAL_FALSE: +			if (filter_type->filter->boolean.value) +				continue; +		case FILTER_TRIVIAL_TRUE: +			if (!filter_type->filter->boolean.value) +				continue; +		default: +			break; +		} + +		new_ids = realloc(ids, sizeof(*ids) * (count + 1)); +		if (!new_ids) { +			free(ids); +			return -1; +		} + +		ids = new_ids; +		ids[count++] = filter_type->event_id; +	} + +	if (!count) +		return 0; + +	for (i = 0; i < count; i++) +		pevent_filter_remove_event(filter, ids[i]); + +	free(ids); +	return 0; +} + +/** + * pevent_filter_event_has_trivial - return true event contains trivial filter + * @filter: the filter with the information + * @event_id: the id of the event to test + * @type: trivial type to test for (TRUE, FALSE, EITHER) + * + * Returns 1 if the event contains a matching trivial type + *  otherwise 0. + */ +int pevent_filter_event_has_trivial(struct event_filter *filter, +				    int event_id, +				    enum filter_trivial_type type) +{ +	struct filter_type *filter_type; + +	if (!filter->filters) +		return 0; + +	filter_type = find_filter_type(filter, event_id); + +	if (!filter_type) +		return 0; + +	if (filter_type->filter->type != FILTER_ARG_BOOLEAN) +		return 0; + +	switch (type) { +	case FILTER_TRIVIAL_FALSE: +		return !filter_type->filter->boolean.value; + +	case FILTER_TRIVIAL_TRUE: +		return filter_type->filter->boolean.value; +	default: +		return 1; +	} +} + +static int test_filter(struct event_format *event, struct filter_arg *arg, +		       struct pevent_record *record, enum pevent_errno *err); + +static const char * +get_comm(struct event_format *event, struct pevent_record *record) +{ +	const char *comm; +	int pid; + +	pid = pevent_data_pid(event->pevent, record); +	comm = pevent_data_comm_from_pid(event->pevent, pid); +	return comm; +} + +static unsigned long long +get_value(struct event_format *event, +	  struct format_field *field, struct pevent_record *record) +{ +	unsigned long long val; + +	/* Handle our dummy "comm" field */ +	if (field == &comm) { +		const char *name; + +		name = get_comm(event, record); +		return (unsigned long)name; +	} + +	pevent_read_number_field(field, record->data, &val); + +	if (!(field->flags & FIELD_IS_SIGNED)) +		return val; + +	switch (field->size) { +	case 1: +		return (char)val; +	case 2: +		return (short)val; +	case 4: +		return (int)val; +	case 8: +		return (long long)val; +	} +	return val; +} + +static unsigned long long +get_arg_value(struct event_format *event, struct filter_arg *arg, +	      struct pevent_record *record, enum pevent_errno *err); + +static unsigned long long +get_exp_value(struct event_format *event, struct filter_arg *arg, +	      struct pevent_record *record, enum pevent_errno *err) +{ +	unsigned long long lval, rval; + +	lval = get_arg_value(event, arg->exp.left, record, err); +	rval = get_arg_value(event, arg->exp.right, record, err); + +	if (*err) { +		/* +		 * There was an error, no need to process anymore. +		 */ +		return 0; +	} + +	switch (arg->exp.type) { +	case FILTER_EXP_ADD: +		return lval + rval; + +	case FILTER_EXP_SUB: +		return lval - rval; + +	case FILTER_EXP_MUL: +		return lval * rval; + +	case FILTER_EXP_DIV: +		return lval / rval; + +	case FILTER_EXP_MOD: +		return lval % rval; + +	case FILTER_EXP_RSHIFT: +		return lval >> rval; + +	case FILTER_EXP_LSHIFT: +		return lval << rval; + +	case FILTER_EXP_AND: +		return lval & rval; + +	case FILTER_EXP_OR: +		return lval | rval; + +	case FILTER_EXP_XOR: +		return lval ^ rval; + +	case FILTER_EXP_NOT: +	default: +		if (!*err) +			*err = PEVENT_ERRNO__INVALID_EXP_TYPE; +	} +	return 0; +} + +static unsigned long long +get_arg_value(struct event_format *event, struct filter_arg *arg, +	      struct pevent_record *record, enum pevent_errno *err) +{ +	switch (arg->type) { +	case FILTER_ARG_FIELD: +		return get_value(event, arg->field.field, record); + +	case FILTER_ARG_VALUE: +		if (arg->value.type != FILTER_NUMBER) { +			if (!*err) +				*err = PEVENT_ERRNO__NOT_A_NUMBER; +		} +		return arg->value.val; + +	case FILTER_ARG_EXP: +		return get_exp_value(event, arg, record, err); + +	default: +		if (!*err) +			*err = PEVENT_ERRNO__INVALID_ARG_TYPE; +	} +	return 0; +} + +static int test_num(struct event_format *event, struct filter_arg *arg, +		    struct pevent_record *record, enum pevent_errno *err) +{ +	unsigned long long lval, rval; + +	lval = get_arg_value(event, arg->num.left, record, err); +	rval = get_arg_value(event, arg->num.right, record, err); + +	if (*err) { +		/* +		 * There was an error, no need to process anymore. +		 */ +		return 0; +	} + +	switch (arg->num.type) { +	case FILTER_CMP_EQ: +		return lval == rval; + +	case FILTER_CMP_NE: +		return lval != rval; + +	case FILTER_CMP_GT: +		return lval > rval; + +	case FILTER_CMP_LT: +		return lval < rval; + +	case FILTER_CMP_GE: +		return lval >= rval; + +	case FILTER_CMP_LE: +		return lval <= rval; + +	default: +		if (!*err) +			*err = PEVENT_ERRNO__ILLEGAL_INTEGER_CMP; +		return 0; +	} +} + +static const char *get_field_str(struct filter_arg *arg, struct pevent_record *record) +{ +	struct event_format *event; +	struct pevent *pevent; +	unsigned long long addr; +	const char *val = NULL; +	char hex[64]; + +	/* If the field is not a string convert it */ +	if (arg->str.field->flags & FIELD_IS_STRING) { +		val = record->data + arg->str.field->offset; + +		/* +		 * We need to copy the data since we can't be sure the field +		 * is null terminated. +		 */ +		if (*(val + arg->str.field->size - 1)) { +			/* copy it */ +			memcpy(arg->str.buffer, val, arg->str.field->size); +			/* the buffer is already NULL terminated */ +			val = arg->str.buffer; +		} + +	} else { +		event = arg->str.field->event; +		pevent = event->pevent; +		addr = get_value(event, arg->str.field, record); + +		if (arg->str.field->flags & (FIELD_IS_POINTER | FIELD_IS_LONG)) +			/* convert to a kernel symbol */ +			val = pevent_find_function(pevent, addr); + +		if (val == NULL) { +			/* just use the hex of the string name */ +			snprintf(hex, 64, "0x%llx", addr); +			val = hex; +		} +	} + +	return val; +} + +static int test_str(struct event_format *event, struct filter_arg *arg, +		    struct pevent_record *record, enum pevent_errno *err) +{ +	const char *val; + +	if (arg->str.field == &comm) +		val = get_comm(event, record); +	else +		val = get_field_str(arg, record); + +	switch (arg->str.type) { +	case FILTER_CMP_MATCH: +		return strcmp(val, arg->str.val) == 0; + +	case FILTER_CMP_NOT_MATCH: +		return strcmp(val, arg->str.val) != 0; + +	case FILTER_CMP_REGEX: +		/* Returns zero on match */ +		return !regexec(&arg->str.reg, val, 0, NULL, 0); + +	case FILTER_CMP_NOT_REGEX: +		return regexec(&arg->str.reg, val, 0, NULL, 0); + +	default: +		if (!*err) +			*err = PEVENT_ERRNO__ILLEGAL_STRING_CMP; +		return 0; +	} +} + +static int test_op(struct event_format *event, struct filter_arg *arg, +		   struct pevent_record *record, enum pevent_errno *err) +{ +	switch (arg->op.type) { +	case FILTER_OP_AND: +		return test_filter(event, arg->op.left, record, err) && +			test_filter(event, arg->op.right, record, err); + +	case FILTER_OP_OR: +		return test_filter(event, arg->op.left, record, err) || +			test_filter(event, arg->op.right, record, err); + +	case FILTER_OP_NOT: +		return !test_filter(event, arg->op.right, record, err); + +	default: +		if (!*err) +			*err = PEVENT_ERRNO__INVALID_OP_TYPE; +		return 0; +	} +} + +static int test_filter(struct event_format *event, struct filter_arg *arg, +		       struct pevent_record *record, enum pevent_errno *err) +{ +	if (*err) { +		/* +		 * There was an error, no need to process anymore. +		 */ +		return 0; +	} + +	switch (arg->type) { +	case FILTER_ARG_BOOLEAN: +		/* easy case */ +		return arg->boolean.value; + +	case FILTER_ARG_OP: +		return test_op(event, arg, record, err); + +	case FILTER_ARG_NUM: +		return test_num(event, arg, record, err); + +	case FILTER_ARG_STR: +		return test_str(event, arg, record, err); + +	case FILTER_ARG_EXP: +	case FILTER_ARG_VALUE: +	case FILTER_ARG_FIELD: +		/* +		 * Expressions, fields and values evaluate +		 * to true if they return non zero +		 */ +		return !!get_arg_value(event, arg, record, err); + +	default: +		if (!*err) +			*err = PEVENT_ERRNO__INVALID_ARG_TYPE; +		return 0; +	} +} + +/** + * pevent_event_filtered - return true if event has filter + * @filter: filter struct with filter information + * @event_id: event id to test if filter exists + * + * Returns 1 if filter found for @event_id + *   otherwise 0; + */ +int pevent_event_filtered(struct event_filter *filter, int event_id) +{ +	struct filter_type *filter_type; + +	if (!filter->filters) +		return 0; + +	filter_type = find_filter_type(filter, event_id); + +	return filter_type ? 1 : 0; +} + +/** + * pevent_filter_match - test if a record matches a filter + * @filter: filter struct with filter information + * @record: the record to test against the filter + * + * Returns: match result or error code (prefixed with PEVENT_ERRNO__) + * FILTER_MATCH - filter found for event and @record matches + * FILTER_MISS  - filter found for event and @record does not match + * FILTER_NOT_FOUND - no filter found for @record's event + * NO_FILTER - if no filters exist + * otherwise - error occurred during test + */ +enum pevent_errno pevent_filter_match(struct event_filter *filter, +				      struct pevent_record *record) +{ +	struct pevent *pevent = filter->pevent; +	struct filter_type *filter_type; +	int event_id; +	int ret; +	enum pevent_errno err = 0; + +	filter_init_error_buf(filter); + +	if (!filter->filters) +		return PEVENT_ERRNO__NO_FILTER; + +	event_id = pevent_data_type(pevent, record); + +	filter_type = find_filter_type(filter, event_id); +	if (!filter_type) +		return PEVENT_ERRNO__FILTER_NOT_FOUND; + +	ret = test_filter(filter_type->event, filter_type->filter, record, &err); +	if (err) +		return err; + +	return ret ? PEVENT_ERRNO__FILTER_MATCH : PEVENT_ERRNO__FILTER_MISS; +} + +static char *op_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *str = NULL; +	char *left = NULL; +	char *right = NULL; +	char *op = NULL; +	int left_val = -1; +	int right_val = -1; +	int val; + +	switch (arg->op.type) { +	case FILTER_OP_AND: +		op = "&&"; +		/* fall through */ +	case FILTER_OP_OR: +		if (!op) +			op = "||"; + +		left = arg_to_str(filter, arg->op.left); +		right = arg_to_str(filter, arg->op.right); +		if (!left || !right) +			break; + +		/* Try to consolidate boolean values */ +		if (strcmp(left, "TRUE") == 0) +			left_val = 1; +		else if (strcmp(left, "FALSE") == 0) +			left_val = 0; + +		if (strcmp(right, "TRUE") == 0) +			right_val = 1; +		else if (strcmp(right, "FALSE") == 0) +			right_val = 0; + +		if (left_val >= 0) { +			if ((arg->op.type == FILTER_OP_AND && !left_val) || +			    (arg->op.type == FILTER_OP_OR && left_val)) { +				/* Just return left value */ +				str = left; +				left = NULL; +				break; +			} +			if (right_val >= 0) { +				/* just evaluate this. */ +				val = 0; +				switch (arg->op.type) { +				case FILTER_OP_AND: +					val = left_val && right_val; +					break; +				case FILTER_OP_OR: +					val = left_val || right_val; +					break; +				default: +					break; +				} +				asprintf(&str, val ? "TRUE" : "FALSE"); +				break; +			} +		} +		if (right_val >= 0) { +			if ((arg->op.type == FILTER_OP_AND && !right_val) || +			    (arg->op.type == FILTER_OP_OR && right_val)) { +				/* Just return right value */ +				str = right; +				right = NULL; +				break; +			} +			/* The right value is meaningless */ +			str = left; +			left = NULL; +			break; +		} + +		asprintf(&str, "(%s) %s (%s)", left, op, right); +		break; + +	case FILTER_OP_NOT: +		op = "!"; +		right = arg_to_str(filter, arg->op.right); +		if (!right) +			break; + +		/* See if we can consolidate */ +		if (strcmp(right, "TRUE") == 0) +			right_val = 1; +		else if (strcmp(right, "FALSE") == 0) +			right_val = 0; +		if (right_val >= 0) { +			/* just return the opposite */ +			asprintf(&str, right_val ? "FALSE" : "TRUE"); +			break; +		} +		asprintf(&str, "%s(%s)", op, right); +		break; + +	default: +		/* ?? */ +		break; +	} +	free(left); +	free(right); +	return str; +} + +static char *val_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *str = NULL; + +	asprintf(&str, "%lld", arg->value.val); + +	return str; +} + +static char *field_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	return strdup(arg->field.field->name); +} + +static char *exp_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *lstr; +	char *rstr; +	char *op; +	char *str = NULL; + +	lstr = arg_to_str(filter, arg->exp.left); +	rstr = arg_to_str(filter, arg->exp.right); +	if (!lstr || !rstr) +		goto out; + +	switch (arg->exp.type) { +	case FILTER_EXP_ADD: +		op = "+"; +		break; +	case FILTER_EXP_SUB: +		op = "-"; +		break; +	case FILTER_EXP_MUL: +		op = "*"; +		break; +	case FILTER_EXP_DIV: +		op = "/"; +		break; +	case FILTER_EXP_MOD: +		op = "%"; +		break; +	case FILTER_EXP_RSHIFT: +		op = ">>"; +		break; +	case FILTER_EXP_LSHIFT: +		op = "<<"; +		break; +	case FILTER_EXP_AND: +		op = "&"; +		break; +	case FILTER_EXP_OR: +		op = "|"; +		break; +	case FILTER_EXP_XOR: +		op = "^"; +		break; +	default: +		op = "[ERROR IN EXPRESSION TYPE]"; +		break; +	} + +	asprintf(&str, "%s %s %s", lstr, op, rstr); +out: +	free(lstr); +	free(rstr); + +	return str; +} + +static char *num_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *lstr; +	char *rstr; +	char *str = NULL; +	char *op = NULL; + +	lstr = arg_to_str(filter, arg->num.left); +	rstr = arg_to_str(filter, arg->num.right); +	if (!lstr || !rstr) +		goto out; + +	switch (arg->num.type) { +	case FILTER_CMP_EQ: +		op = "=="; +		/* fall through */ +	case FILTER_CMP_NE: +		if (!op) +			op = "!="; +		/* fall through */ +	case FILTER_CMP_GT: +		if (!op) +			op = ">"; +		/* fall through */ +	case FILTER_CMP_LT: +		if (!op) +			op = "<"; +		/* fall through */ +	case FILTER_CMP_GE: +		if (!op) +			op = ">="; +		/* fall through */ +	case FILTER_CMP_LE: +		if (!op) +			op = "<="; + +		asprintf(&str, "%s %s %s", lstr, op, rstr); +		break; + +	default: +		/* ?? */ +		break; +	} + +out: +	free(lstr); +	free(rstr); +	return str; +} + +static char *str_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *str = NULL; +	char *op = NULL; + +	switch (arg->str.type) { +	case FILTER_CMP_MATCH: +		op = "=="; +		/* fall through */ +	case FILTER_CMP_NOT_MATCH: +		if (!op) +			op = "!="; +		/* fall through */ +	case FILTER_CMP_REGEX: +		if (!op) +			op = "=~"; +		/* fall through */ +	case FILTER_CMP_NOT_REGEX: +		if (!op) +			op = "!~"; + +		asprintf(&str, "%s %s \"%s\"", +			 arg->str.field->name, op, arg->str.val); +		break; + +	default: +		/* ?? */ +		break; +	} +	return str; +} + +static char *arg_to_str(struct event_filter *filter, struct filter_arg *arg) +{ +	char *str = NULL; + +	switch (arg->type) { +	case FILTER_ARG_BOOLEAN: +		asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE"); +		return str; + +	case FILTER_ARG_OP: +		return op_to_str(filter, arg); + +	case FILTER_ARG_NUM: +		return num_to_str(filter, arg); + +	case FILTER_ARG_STR: +		return str_to_str(filter, arg); + +	case FILTER_ARG_VALUE: +		return val_to_str(filter, arg); + +	case FILTER_ARG_FIELD: +		return field_to_str(filter, arg); + +	case FILTER_ARG_EXP: +		return exp_to_str(filter, arg); + +	default: +		/* ?? */ +		return NULL; +	} + +} + +/** + * pevent_filter_make_string - return a string showing the filter + * @filter: filter struct with filter information + * @event_id: the event id to return the filter string with + * + * Returns a string that displays the filter contents. + *  This string must be freed with free(str). + *  NULL is returned if no filter is found or allocation failed. + */ +char * +pevent_filter_make_string(struct event_filter *filter, int event_id) +{ +	struct filter_type *filter_type; + +	if (!filter->filters) +		return NULL; + +	filter_type = find_filter_type(filter, event_id); + +	if (!filter_type) +		return NULL; + +	return arg_to_str(filter, filter_type->filter); +} + +/** + * pevent_filter_compare - compare two filters and return if they are the same + * @filter1: Filter to compare with @filter2 + * @filter2: Filter to compare with @filter1 + * + * Returns: + *  1 if the two filters hold the same content. + *  0 if they do not. + */ +int pevent_filter_compare(struct event_filter *filter1, struct event_filter *filter2) +{ +	struct filter_type *filter_type1; +	struct filter_type *filter_type2; +	char *str1, *str2; +	int result; +	int i; + +	/* Do the easy checks first */ +	if (filter1->filters != filter2->filters) +		return 0; +	if (!filter1->filters && !filter2->filters) +		return 1; + +	/* +	 * Now take a look at each of the events to see if they have the same +	 * filters to them. +	 */ +	for (i = 0; i < filter1->filters; i++) { +		filter_type1 = &filter1->event_filters[i]; +		filter_type2 = find_filter_type(filter2, filter_type1->event_id); +		if (!filter_type2) +			break; +		if (filter_type1->filter->type != filter_type2->filter->type) +			break; +		switch (filter_type1->filter->type) { +		case FILTER_TRIVIAL_FALSE: +		case FILTER_TRIVIAL_TRUE: +			/* trivial types just need the type compared */ +			continue; +		default: +			break; +		} +		/* The best way to compare complex filters is with strings */ +		str1 = arg_to_str(filter1, filter_type1->filter); +		str2 = arg_to_str(filter2, filter_type2->filter); +		if (str1 && str2) +			result = strcmp(str1, str2) != 0; +		else +			/* bail out if allocation fails */ +			result = 1; + +		free(str1); +		free(str2); +		if (result) +			break; +	} + +	if (i < filter1->filters) +		return 0; +	return 1; +} + diff --git a/tools/lib/traceevent/parse-utils.c b/tools/lib/traceevent/parse-utils.c new file mode 100644 index 00000000000..eda07fa31dc --- /dev/null +++ b/tools/lib/traceevent/parse-utils.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> + +#define __weak __attribute__((weak)) + +void __vwarning(const char *fmt, va_list ap) +{ +	if (errno) +		perror("trace-cmd"); +	errno = 0; + +	fprintf(stderr, "  "); +	vfprintf(stderr, fmt, ap); + +	fprintf(stderr, "\n"); +} + +void __warning(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	__vwarning(fmt, ap); +	va_end(ap); +} + +void __weak warning(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	__vwarning(fmt, ap); +	va_end(ap); +} + +void __vpr_stat(const char *fmt, va_list ap) +{ +	vprintf(fmt, ap); +	printf("\n"); +} + +void __pr_stat(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	__vpr_stat(fmt, ap); +	va_end(ap); +} + +void __weak vpr_stat(const char *fmt, va_list ap) +{ +	__vpr_stat(fmt, ap); +} + +void __weak pr_stat(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	__vpr_stat(fmt, ap); +	va_end(ap); +} diff --git a/tools/lib/traceevent/plugin_cfg80211.c b/tools/lib/traceevent/plugin_cfg80211.c new file mode 100644 index 00000000000..c066b25905f --- /dev/null +++ b/tools/lib/traceevent/plugin_cfg80211.c @@ -0,0 +1,30 @@ +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <endian.h> +#include "event-parse.h" + +static unsigned long long +process___le16_to_cpup(struct trace_seq *s, +		       unsigned long long *args) +{ +	uint16_t *val = (uint16_t *) (unsigned long) args[0]; +	return val ? (long long) le16toh(*val) : 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_print_function(pevent, +				       process___le16_to_cpup, +				       PEVENT_FUNC_ARG_INT, +				       "__le16_to_cpup", +				       PEVENT_FUNC_ARG_PTR, +				       PEVENT_FUNC_ARG_VOID); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_print_function(pevent, process___le16_to_cpup, +					 "__le16_to_cpup"); +} diff --git a/tools/lib/traceevent/plugin_function.c b/tools/lib/traceevent/plugin_function.c new file mode 100644 index 00000000000..a00ec190821 --- /dev/null +++ b/tools/lib/traceevent/plugin_function.c @@ -0,0 +1,194 @@ +/* + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "event-utils.h" + +static struct func_stack { +	int size; +	char **stack; +} *fstack; + +static int cpus = -1; + +#define STK_BLK 10 + +struct pevent_plugin_option plugin_options[] = +{ +	{ +		.name = "parent", +		.plugin_alias = "ftrace", +		.description = +		"Print parent of functions for function events", +	}, +	{ +		.name = "indent", +		.plugin_alias = "ftrace", +		.description = +		"Try to show function call indents, based on parents", +		.set = 1, +	}, +	{ +		.name = NULL, +	} +}; + +static struct pevent_plugin_option *ftrace_parent = &plugin_options[0]; +static struct pevent_plugin_option *ftrace_indent = &plugin_options[1]; + +static void add_child(struct func_stack *stack, const char *child, int pos) +{ +	int i; + +	if (!child) +		return; + +	if (pos < stack->size) +		free(stack->stack[pos]); +	else { +		char **ptr; + +		ptr = realloc(stack->stack, sizeof(char *) * +			      (stack->size + STK_BLK)); +		if (!ptr) { +			warning("could not allocate plugin memory\n"); +			return; +		} + +		stack->stack = ptr; + +		for (i = stack->size; i < stack->size + STK_BLK; i++) +			stack->stack[i] = NULL; +		stack->size += STK_BLK; +	} + +	stack->stack[pos] = strdup(child); +} + +static int add_and_get_index(const char *parent, const char *child, int cpu) +{ +	int i; + +	if (cpu < 0) +		return 0; + +	if (cpu > cpus) { +		struct func_stack *ptr; + +		ptr = realloc(fstack, sizeof(*fstack) * (cpu + 1)); +		if (!ptr) { +			warning("could not allocate plugin memory\n"); +			return 0; +		} + +		fstack = ptr; + +		/* Account for holes in the cpu count */ +		for (i = cpus + 1; i <= cpu; i++) +			memset(&fstack[i], 0, sizeof(fstack[i])); +		cpus = cpu; +	} + +	for (i = 0; i < fstack[cpu].size && fstack[cpu].stack[i]; i++) { +		if (strcmp(parent, fstack[cpu].stack[i]) == 0) { +			add_child(&fstack[cpu], child, i+1); +			return i; +		} +	} + +	/* Not found */ +	add_child(&fstack[cpu], parent, 0); +	add_child(&fstack[cpu], child, 1); +	return 0; +} + +static int function_handler(struct trace_seq *s, struct pevent_record *record, +			    struct event_format *event, void *context) +{ +	struct pevent *pevent = event->pevent; +	unsigned long long function; +	unsigned long long pfunction; +	const char *func; +	const char *parent; +	int index; + +	if (pevent_get_field_val(s, event, "ip", record, &function, 1)) +		return trace_seq_putc(s, '!'); + +	func = pevent_find_function(pevent, function); + +	if (pevent_get_field_val(s, event, "parent_ip", record, &pfunction, 1)) +		return trace_seq_putc(s, '!'); + +	parent = pevent_find_function(pevent, pfunction); + +	if (parent && ftrace_indent->set) +		index = add_and_get_index(parent, func, record->cpu); + +	trace_seq_printf(s, "%*s", index*3, ""); + +	if (func) +		trace_seq_printf(s, "%s", func); +	else +		trace_seq_printf(s, "0x%llx", function); + +	if (ftrace_parent->set) { +		trace_seq_printf(s, " <-- "); +		if (parent) +			trace_seq_printf(s, "%s", parent); +		else +			trace_seq_printf(s, "0x%llx", pfunction); +	} + +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_event_handler(pevent, -1, "ftrace", "function", +				      function_handler, NULL); + +	traceevent_plugin_add_options("ftrace", plugin_options); + +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	int i, x; + +	pevent_unregister_event_handler(pevent, -1, "ftrace", "function", +					function_handler, NULL); + +	for (i = 0; i <= cpus; i++) { +		for (x = 0; x < fstack[i].size && fstack[i].stack[x]; x++) +			free(fstack[i].stack[x]); +		free(fstack[i].stack); +	} + +	traceevent_plugin_remove_options(plugin_options); + +	free(fstack); +	fstack = NULL; +	cpus = -1; +} diff --git a/tools/lib/traceevent/plugin_hrtimer.c b/tools/lib/traceevent/plugin_hrtimer.c new file mode 100644 index 00000000000..12bf14cc115 --- /dev/null +++ b/tools/lib/traceevent/plugin_hrtimer.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +static int timer_expire_handler(struct trace_seq *s, +				struct pevent_record *record, +				struct event_format *event, void *context) +{ +	trace_seq_printf(s, "hrtimer="); + +	if (pevent_print_num_field(s, "0x%llx", event, "timer", +				   record, 0) == -1) +		pevent_print_num_field(s, "0x%llx", event, "hrtimer", +				       record, 1); + +	trace_seq_printf(s, " now="); + +	pevent_print_num_field(s, "%llu", event, "now", record, 1); + +	pevent_print_func_field(s, " function=%s", event, "function", +				record, 0); +	return 0; +} + +static int timer_start_handler(struct trace_seq *s, +			       struct pevent_record *record, +			       struct event_format *event, void *context) +{ +	trace_seq_printf(s, "hrtimer="); + +	if (pevent_print_num_field(s, "0x%llx", event, "timer", +				   record, 0) == -1) +		pevent_print_num_field(s, "0x%llx", event, "hrtimer", +				       record, 1); + +	pevent_print_func_field(s, " function=%s", event, "function", +				record, 0); + +	trace_seq_printf(s, " expires="); +	pevent_print_num_field(s, "%llu", event, "expires", record, 1); + +	trace_seq_printf(s, " softexpires="); +	pevent_print_num_field(s, "%llu", event, "softexpires", record, 1); +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_event_handler(pevent, -1, +				      "timer", "hrtimer_expire_entry", +				      timer_expire_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "timer", "hrtimer_start", +				      timer_start_handler, NULL); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_event_handler(pevent, -1, +					"timer", "hrtimer_expire_entry", +					timer_expire_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "timer", "hrtimer_start", +					timer_start_handler, NULL); +} diff --git a/tools/lib/traceevent/plugin_jbd2.c b/tools/lib/traceevent/plugin_jbd2.c new file mode 100644 index 00000000000..0db714c721b --- /dev/null +++ b/tools/lib/traceevent/plugin_jbd2.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +#define MINORBITS	20 +#define MINORMASK	((1U << MINORBITS) - 1) + +#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS)) +#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK)) + +static unsigned long long +process_jbd2_dev_to_name(struct trace_seq *s, +			 unsigned long long *args) +{ +	unsigned int dev = args[0]; + +	trace_seq_printf(s, "%d:%d", MAJOR(dev), MINOR(dev)); +	return 0; +} + +static unsigned long long +process_jiffies_to_msecs(struct trace_seq *s, +			 unsigned long long *args) +{ +	unsigned long long jiffies = args[0]; + +	trace_seq_printf(s, "%lld", jiffies); +	return jiffies; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_print_function(pevent, +				       process_jbd2_dev_to_name, +				       PEVENT_FUNC_ARG_STRING, +				       "jbd2_dev_to_name", +				       PEVENT_FUNC_ARG_INT, +				       PEVENT_FUNC_ARG_VOID); + +	pevent_register_print_function(pevent, +				       process_jiffies_to_msecs, +				       PEVENT_FUNC_ARG_LONG, +				       "jiffies_to_msecs", +				       PEVENT_FUNC_ARG_LONG, +				       PEVENT_FUNC_ARG_VOID); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_print_function(pevent, process_jbd2_dev_to_name, +					 "jbd2_dev_to_name"); + +	pevent_unregister_print_function(pevent, process_jiffies_to_msecs, +					 "jiffies_to_msecs"); +} diff --git a/tools/lib/traceevent/plugin_kmem.c b/tools/lib/traceevent/plugin_kmem.c new file mode 100644 index 00000000000..70650ff48d7 --- /dev/null +++ b/tools/lib/traceevent/plugin_kmem.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +static int call_site_handler(struct trace_seq *s, struct pevent_record *record, +			     struct event_format *event, void *context) +{ +	struct format_field *field; +	unsigned long long val, addr; +	void *data = record->data; +	const char *func; + +	field = pevent_find_field(event, "call_site"); +	if (!field) +		return 1; + +	if (pevent_read_number_field(field, data, &val)) +		return 1; + +	func = pevent_find_function(event->pevent, val); +	if (!func) +		return 1; + +	addr = pevent_find_function_address(event->pevent, val); + +	trace_seq_printf(s, "(%s+0x%x) ", func, (int)(val - addr)); +	return 1; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_event_handler(pevent, -1, "kmem", "kfree", +				      call_site_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kmem", "kmalloc", +				      call_site_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kmem", "kmalloc_node", +				      call_site_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kmem", "kmem_cache_alloc", +				      call_site_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kmem", +				      "kmem_cache_alloc_node", +				      call_site_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kmem", "kmem_cache_free", +				      call_site_handler, NULL); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_event_handler(pevent, -1, "kmem", "kfree", +					call_site_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kmem", "kmalloc", +					call_site_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kmem", "kmalloc_node", +					call_site_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kmem", "kmem_cache_alloc", +					call_site_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kmem", +					"kmem_cache_alloc_node", +					call_site_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kmem", "kmem_cache_free", +					call_site_handler, NULL); +} diff --git a/tools/lib/traceevent/plugin_kvm.c b/tools/lib/traceevent/plugin_kvm.c new file mode 100644 index 00000000000..9e0e8c61b43 --- /dev/null +++ b/tools/lib/traceevent/plugin_kvm.c @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2009 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "event-parse.h" + +#ifdef HAVE_UDIS86 + +#include <udis86.h> + +static ud_t ud; + +static void init_disassembler(void) +{ +	ud_init(&ud); +	ud_set_syntax(&ud, UD_SYN_ATT); +} + +static const char *disassemble(unsigned char *insn, int len, uint64_t rip, +			       int cr0_pe, int eflags_vm, +			       int cs_d, int cs_l) +{ +	int mode; + +	if (!cr0_pe) +		mode = 16; +	else if (eflags_vm) +		mode = 16; +	else if (cs_l) +		mode = 64; +	else if (cs_d) +		mode = 32; +	else +		mode = 16; + +	ud_set_pc(&ud, rip); +	ud_set_mode(&ud, mode); +	ud_set_input_buffer(&ud, insn, len); +	ud_disassemble(&ud); +	return ud_insn_asm(&ud); +} + +#else + +static void init_disassembler(void) +{ +} + +static const char *disassemble(unsigned char *insn, int len, uint64_t rip, +			       int cr0_pe, int eflags_vm, +			       int cs_d, int cs_l) +{ +	static char out[15*3+1]; +	int i; + +	for (i = 0; i < len; ++i) +		sprintf(out + i * 3, "%02x ", insn[i]); +	out[len*3-1] = '\0'; +	return out; +} + +#endif + + +#define VMX_EXIT_REASONS			\ +	_ER(EXCEPTION_NMI,	 0)		\ +	_ER(EXTERNAL_INTERRUPT,	 1)		\ +	_ER(TRIPLE_FAULT,	 2)		\ +	_ER(PENDING_INTERRUPT,	 7)		\ +	_ER(NMI_WINDOW,		 8)		\ +	_ER(TASK_SWITCH,	 9)		\ +	_ER(CPUID,		 10)		\ +	_ER(HLT,		 12)		\ +	_ER(INVD,		 13)		\ +	_ER(INVLPG,		 14)		\ +	_ER(RDPMC,		 15)		\ +	_ER(RDTSC,		 16)		\ +	_ER(VMCALL,		 18)		\ +	_ER(VMCLEAR,		 19)		\ +	_ER(VMLAUNCH,		 20)		\ +	_ER(VMPTRLD,		 21)		\ +	_ER(VMPTRST,		 22)		\ +	_ER(VMREAD,		 23)		\ +	_ER(VMRESUME,		 24)		\ +	_ER(VMWRITE,		 25)		\ +	_ER(VMOFF,		 26)		\ +	_ER(VMON,		 27)		\ +	_ER(CR_ACCESS,		 28)		\ +	_ER(DR_ACCESS,		 29)		\ +	_ER(IO_INSTRUCTION,	 30)		\ +	_ER(MSR_READ,		 31)		\ +	_ER(MSR_WRITE,		 32)		\ +	_ER(MWAIT_INSTRUCTION,	 36)		\ +	_ER(MONITOR_INSTRUCTION, 39)		\ +	_ER(PAUSE_INSTRUCTION,	 40)		\ +	_ER(MCE_DURING_VMENTRY,	 41)		\ +	_ER(TPR_BELOW_THRESHOLD, 43)		\ +	_ER(APIC_ACCESS,	 44)		\ +	_ER(EOI_INDUCED,	 45)		\ +	_ER(EPT_VIOLATION,	 48)		\ +	_ER(EPT_MISCONFIG,	 49)		\ +	_ER(INVEPT,		 50)		\ +	_ER(PREEMPTION_TIMER,	 52)		\ +	_ER(WBINVD,		 54)		\ +	_ER(XSETBV,		 55)		\ +	_ER(APIC_WRITE,		 56)		\ +	_ER(INVPCID,		 58) + +#define SVM_EXIT_REASONS \ +	_ER(EXIT_READ_CR0,	0x000)		\ +	_ER(EXIT_READ_CR3,	0x003)		\ +	_ER(EXIT_READ_CR4,	0x004)		\ +	_ER(EXIT_READ_CR8,	0x008)		\ +	_ER(EXIT_WRITE_CR0,	0x010)		\ +	_ER(EXIT_WRITE_CR3,	0x013)		\ +	_ER(EXIT_WRITE_CR4,	0x014)		\ +	_ER(EXIT_WRITE_CR8,	0x018)		\ +	_ER(EXIT_READ_DR0,	0x020)		\ +	_ER(EXIT_READ_DR1,	0x021)		\ +	_ER(EXIT_READ_DR2,	0x022)		\ +	_ER(EXIT_READ_DR3,	0x023)		\ +	_ER(EXIT_READ_DR4,	0x024)		\ +	_ER(EXIT_READ_DR5,	0x025)		\ +	_ER(EXIT_READ_DR6,	0x026)		\ +	_ER(EXIT_READ_DR7,	0x027)		\ +	_ER(EXIT_WRITE_DR0,	0x030)		\ +	_ER(EXIT_WRITE_DR1,	0x031)		\ +	_ER(EXIT_WRITE_DR2,	0x032)		\ +	_ER(EXIT_WRITE_DR3,	0x033)		\ +	_ER(EXIT_WRITE_DR4,	0x034)		\ +	_ER(EXIT_WRITE_DR5,	0x035)		\ +	_ER(EXIT_WRITE_DR6,	0x036)		\ +	_ER(EXIT_WRITE_DR7,	0x037)		\ +	_ER(EXIT_EXCP_BASE,     0x040)		\ +	_ER(EXIT_INTR,		0x060)		\ +	_ER(EXIT_NMI,		0x061)		\ +	_ER(EXIT_SMI,		0x062)		\ +	_ER(EXIT_INIT,		0x063)		\ +	_ER(EXIT_VINTR,		0x064)		\ +	_ER(EXIT_CR0_SEL_WRITE,	0x065)		\ +	_ER(EXIT_IDTR_READ,	0x066)		\ +	_ER(EXIT_GDTR_READ,	0x067)		\ +	_ER(EXIT_LDTR_READ,	0x068)		\ +	_ER(EXIT_TR_READ,	0x069)		\ +	_ER(EXIT_IDTR_WRITE,	0x06a)		\ +	_ER(EXIT_GDTR_WRITE,	0x06b)		\ +	_ER(EXIT_LDTR_WRITE,	0x06c)		\ +	_ER(EXIT_TR_WRITE,	0x06d)		\ +	_ER(EXIT_RDTSC,		0x06e)		\ +	_ER(EXIT_RDPMC,		0x06f)		\ +	_ER(EXIT_PUSHF,		0x070)		\ +	_ER(EXIT_POPF,		0x071)		\ +	_ER(EXIT_CPUID,		0x072)		\ +	_ER(EXIT_RSM,		0x073)		\ +	_ER(EXIT_IRET,		0x074)		\ +	_ER(EXIT_SWINT,		0x075)		\ +	_ER(EXIT_INVD,		0x076)		\ +	_ER(EXIT_PAUSE,		0x077)		\ +	_ER(EXIT_HLT,		0x078)		\ +	_ER(EXIT_INVLPG,	0x079)		\ +	_ER(EXIT_INVLPGA,	0x07a)		\ +	_ER(EXIT_IOIO,		0x07b)		\ +	_ER(EXIT_MSR,		0x07c)		\ +	_ER(EXIT_TASK_SWITCH,	0x07d)		\ +	_ER(EXIT_FERR_FREEZE,	0x07e)		\ +	_ER(EXIT_SHUTDOWN,	0x07f)		\ +	_ER(EXIT_VMRUN,		0x080)		\ +	_ER(EXIT_VMMCALL,	0x081)		\ +	_ER(EXIT_VMLOAD,	0x082)		\ +	_ER(EXIT_VMSAVE,	0x083)		\ +	_ER(EXIT_STGI,		0x084)		\ +	_ER(EXIT_CLGI,		0x085)		\ +	_ER(EXIT_SKINIT,	0x086)		\ +	_ER(EXIT_RDTSCP,	0x087)		\ +	_ER(EXIT_ICEBP,		0x088)		\ +	_ER(EXIT_WBINVD,	0x089)		\ +	_ER(EXIT_MONITOR,	0x08a)		\ +	_ER(EXIT_MWAIT,		0x08b)		\ +	_ER(EXIT_MWAIT_COND,	0x08c)		\ +	_ER(EXIT_NPF,		0x400)		\ +	_ER(EXIT_ERR,		-1) + +#define _ER(reason, val)	{ #reason, val }, +struct str_values { +	const char	*str; +	int		val; +}; + +static struct str_values vmx_exit_reasons[] = { +	VMX_EXIT_REASONS +	{ NULL, -1} +}; + +static struct str_values svm_exit_reasons[] = { +	SVM_EXIT_REASONS +	{ NULL, -1} +}; + +static struct isa_exit_reasons { +	unsigned isa; +	struct str_values *strings; +} isa_exit_reasons[] = { +	{ .isa = 1, .strings = vmx_exit_reasons }, +	{ .isa = 2, .strings = svm_exit_reasons }, +	{ } +}; + +static const char *find_exit_reason(unsigned isa, int val) +{ +	struct str_values *strings = NULL; +	int i; + +	for (i = 0; isa_exit_reasons[i].strings; ++i) +		if (isa_exit_reasons[i].isa == isa) { +			strings = isa_exit_reasons[i].strings; +			break; +		} +	if (!strings) +		return "UNKNOWN-ISA"; +	for (i = 0; strings[i].val >= 0; i++) +		if (strings[i].val == val) +			break; +	if (strings[i].str) +		return strings[i].str; +	return "UNKNOWN"; +} + +static int kvm_exit_handler(struct trace_seq *s, struct pevent_record *record, +			    struct event_format *event, void *context) +{ +	unsigned long long isa; +	unsigned long long val; +	unsigned long long info1 = 0, info2 = 0; + +	if (pevent_get_field_val(s, event, "exit_reason", record, &val, 1) < 0) +		return -1; + +	if (pevent_get_field_val(s, event, "isa", record, &isa, 0) < 0) +		isa = 1; + +	trace_seq_printf(s, "reason %s", find_exit_reason(isa, val)); + +	pevent_print_num_field(s, " rip 0x%lx", event, "guest_rip", record, 1); + +	if (pevent_get_field_val(s, event, "info1", record, &info1, 0) >= 0 +	    && pevent_get_field_val(s, event, "info2", record, &info2, 0) >= 0) +		trace_seq_printf(s, " info %llx %llx", info1, info2); + +	return 0; +} + +#define KVM_EMUL_INSN_F_CR0_PE (1 << 0) +#define KVM_EMUL_INSN_F_EFL_VM (1 << 1) +#define KVM_EMUL_INSN_F_CS_D   (1 << 2) +#define KVM_EMUL_INSN_F_CS_L   (1 << 3) + +static int kvm_emulate_insn_handler(struct trace_seq *s, +				    struct pevent_record *record, +				    struct event_format *event, void *context) +{ +	unsigned long long rip, csbase, len, flags, failed; +	int llen; +	uint8_t *insn; +	const char *disasm; + +	if (pevent_get_field_val(s, event, "rip", record, &rip, 1) < 0) +		return -1; + +	if (pevent_get_field_val(s, event, "csbase", record, &csbase, 1) < 0) +		return -1; + +	if (pevent_get_field_val(s, event, "len", record, &len, 1) < 0) +		return -1; + +	if (pevent_get_field_val(s, event, "flags", record, &flags, 1) < 0) +		return -1; + +	if (pevent_get_field_val(s, event, "failed", record, &failed, 1) < 0) +		return -1; + +	insn = pevent_get_field_raw(s, event, "insn", record, &llen, 1); +	if (!insn) +		return -1; + +	disasm = disassemble(insn, len, rip, +			     flags & KVM_EMUL_INSN_F_CR0_PE, +			     flags & KVM_EMUL_INSN_F_EFL_VM, +			     flags & KVM_EMUL_INSN_F_CS_D, +			     flags & KVM_EMUL_INSN_F_CS_L); + +	trace_seq_printf(s, "%llx:%llx: %s%s", csbase, rip, disasm, +			 failed ? " FAIL" : ""); +	return 0; +} + +union kvm_mmu_page_role { +	unsigned word; +	struct { +		unsigned glevels:4; +		unsigned level:4; +		unsigned quadrant:2; +		unsigned pad_for_nice_hex_output:6; +		unsigned direct:1; +		unsigned access:3; +		unsigned invalid:1; +		unsigned cr4_pge:1; +		unsigned nxe:1; +	}; +}; + +static int kvm_mmu_print_role(struct trace_seq *s, struct pevent_record *record, +			      struct event_format *event, void *context) +{ +	unsigned long long val; +	static const char *access_str[] = { +		"---", "--x", "w--", "w-x", "-u-", "-ux", "wu-", "wux" +	}; +	union kvm_mmu_page_role role; + +	if (pevent_get_field_val(s, event, "role", record, &val, 1) < 0) +		return -1; + +	role.word = (int)val; + +	/* +	 * We can only use the structure if file is of the same +	 * endianess. +	 */ +	if (pevent_is_file_bigendian(event->pevent) == +	    pevent_is_host_bigendian(event->pevent)) { + +		trace_seq_printf(s, "%u/%u q%u%s %s%s %spge %snxe", +				 role.level, +				 role.glevels, +				 role.quadrant, +				 role.direct ? " direct" : "", +				 access_str[role.access], +				 role.invalid ? " invalid" : "", +				 role.cr4_pge ? "" : "!", +				 role.nxe ? "" : "!"); +	} else +		trace_seq_printf(s, "WORD: %08x", role.word); + +	pevent_print_num_field(s, " root %u ",  event, +			       "root_count", record, 1); + +	if (pevent_get_field_val(s, event, "unsync", record, &val, 1) < 0) +		return -1; + +	trace_seq_printf(s, "%s%c",  val ? "unsync" : "sync", 0); +	return 0; +} + +static int kvm_mmu_get_page_handler(struct trace_seq *s, +				    struct pevent_record *record, +				    struct event_format *event, void *context) +{ +	unsigned long long val; + +	if (pevent_get_field_val(s, event, "created", record, &val, 1) < 0) +		return -1; + +	trace_seq_printf(s, "%s ", val ? "new" : "existing"); + +	if (pevent_get_field_val(s, event, "gfn", record, &val, 1) < 0) +		return -1; + +	trace_seq_printf(s, "sp gfn %llx ", val); +	return kvm_mmu_print_role(s, record, event, context); +} + +#define PT_WRITABLE_SHIFT 1 +#define PT_WRITABLE_MASK (1ULL << PT_WRITABLE_SHIFT) + +static unsigned long long +process_is_writable_pte(struct trace_seq *s, unsigned long long *args) +{ +	unsigned long pte = args[0]; +	return pte & PT_WRITABLE_MASK; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	init_disassembler(); + +	pevent_register_event_handler(pevent, -1, "kvm", "kvm_exit", +				      kvm_exit_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kvm", "kvm_emulate_insn", +				      kvm_emulate_insn_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_get_page", +				      kvm_mmu_get_page_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_sync_page", +				      kvm_mmu_print_role, NULL); + +	pevent_register_event_handler(pevent, -1, +				      "kvmmmu", "kvm_mmu_unsync_page", +				      kvm_mmu_print_role, NULL); + +	pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_zap_page", +				      kvm_mmu_print_role, NULL); + +	pevent_register_event_handler(pevent, -1, "kvmmmu", +			"kvm_mmu_prepare_zap_page", kvm_mmu_print_role, +			NULL); + +	pevent_register_print_function(pevent, +				       process_is_writable_pte, +				       PEVENT_FUNC_ARG_INT, +				       "is_writable_pte", +				       PEVENT_FUNC_ARG_LONG, +				       PEVENT_FUNC_ARG_VOID); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_event_handler(pevent, -1, "kvm", "kvm_exit", +					kvm_exit_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kvm", "kvm_emulate_insn", +					kvm_emulate_insn_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_get_page", +					kvm_mmu_get_page_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_sync_page", +					kvm_mmu_print_role, NULL); + +	pevent_unregister_event_handler(pevent, -1, +					"kvmmmu", "kvm_mmu_unsync_page", +					kvm_mmu_print_role, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_zap_page", +					kvm_mmu_print_role, NULL); + +	pevent_unregister_event_handler(pevent, -1, "kvmmmu", +			"kvm_mmu_prepare_zap_page", kvm_mmu_print_role, +			NULL); + +	pevent_unregister_print_function(pevent, process_is_writable_pte, +					 "is_writable_pte"); +} diff --git a/tools/lib/traceevent/plugin_mac80211.c b/tools/lib/traceevent/plugin_mac80211.c new file mode 100644 index 00000000000..7e15a0f1c2f --- /dev/null +++ b/tools/lib/traceevent/plugin_mac80211.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +#define INDENT 65 + +static void print_string(struct trace_seq *s, struct event_format *event, +			 const char *name, const void *data) +{ +	struct format_field *f = pevent_find_field(event, name); +	int offset; +	int length; + +	if (!f) { +		trace_seq_printf(s, "NOTFOUND:%s", name); +		return; +	} + +	offset = f->offset; +	length = f->size; + +	if (!strncmp(f->type, "__data_loc", 10)) { +		unsigned long long v; +		if (pevent_read_number_field(f, data, &v)) { +			trace_seq_printf(s, "invalid_data_loc"); +			return; +		} +		offset = v & 0xffff; +		length = v >> 16; +	} + +	trace_seq_printf(s, "%.*s", length, (char *)data + offset); +} + +#define SF(fn)	pevent_print_num_field(s, fn ":%d", event, fn, record, 0) +#define SFX(fn)	pevent_print_num_field(s, fn ":%#x", event, fn, record, 0) +#define SP()	trace_seq_putc(s, ' ') + +static int drv_bss_info_changed(struct trace_seq *s, +				struct pevent_record *record, +				struct event_format *event, void *context) +{ +	void *data = record->data; + +	print_string(s, event, "wiphy_name", data); +	trace_seq_printf(s, " vif:"); +	print_string(s, event, "vif_name", data); +	pevent_print_num_field(s, "(%d)", event, "vif_type", record, 1); + +	trace_seq_printf(s, "\n%*s", INDENT, ""); +	SF("assoc"); SP(); +	SF("aid"); SP(); +	SF("cts"); SP(); +	SF("shortpre"); SP(); +	SF("shortslot"); SP(); +	SF("dtimper"); SP(); +	trace_seq_printf(s, "\n%*s", INDENT, ""); +	SF("bcnint"); SP(); +	SFX("assoc_cap"); SP(); +	SFX("basic_rates"); SP(); +	SF("enable_beacon"); +	trace_seq_printf(s, "\n%*s", INDENT, ""); +	SF("ht_operation_mode"); + +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_event_handler(pevent, -1, "mac80211", +				      "drv_bss_info_changed", +				      drv_bss_info_changed, NULL); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_event_handler(pevent, -1, "mac80211", +					"drv_bss_info_changed", +					drv_bss_info_changed, NULL); +} diff --git a/tools/lib/traceevent/plugin_sched_switch.c b/tools/lib/traceevent/plugin_sched_switch.c new file mode 100644 index 00000000000..f1ce6006525 --- /dev/null +++ b/tools/lib/traceevent/plugin_sched_switch.c @@ -0,0 +1,160 @@ +/* + * 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +static void write_state(struct trace_seq *s, int val) +{ +	const char states[] = "SDTtZXxW"; +	int found = 0; +	int i; + +	for (i = 0; i < (sizeof(states) - 1); i++) { +		if (!(val & (1 << i))) +			continue; + +		if (found) +			trace_seq_putc(s, '|'); + +		found = 1; +		trace_seq_putc(s, states[i]); +	} + +	if (!found) +		trace_seq_putc(s, 'R'); +} + +static void write_and_save_comm(struct format_field *field, +				struct pevent_record *record, +				struct trace_seq *s, int pid) +{ +	const char *comm; +	int len; + +	comm = (char *)(record->data + field->offset); +	len = s->len; +	trace_seq_printf(s, "%.*s", +			 field->size, comm); + +	/* make sure the comm has a \0 at the end. */ +	trace_seq_terminate(s); +	comm = &s->buffer[len]; + +	/* Help out the comm to ids. This will handle dups */ +	pevent_register_comm(field->event->pevent, comm, pid); +} + +static int sched_wakeup_handler(struct trace_seq *s, +				struct pevent_record *record, +				struct event_format *event, void *context) +{ +	struct format_field *field; +	unsigned long long val; + +	if (pevent_get_field_val(s, event, "pid", record, &val, 1)) +		return trace_seq_putc(s, '!'); + +	field = pevent_find_any_field(event, "comm"); +	if (field) { +		write_and_save_comm(field, record, s, val); +		trace_seq_putc(s, ':'); +	} +	trace_seq_printf(s, "%lld", val); + +	if (pevent_get_field_val(s, event, "prio", record, &val, 0) == 0) +		trace_seq_printf(s, " [%lld]", val); + +	if (pevent_get_field_val(s, event, "success", record, &val, 1) == 0) +		trace_seq_printf(s, " success=%lld", val); + +	if (pevent_get_field_val(s, event, "target_cpu", record, &val, 0) == 0) +		trace_seq_printf(s, " CPU:%03llu", val); + +	return 0; +} + +static int sched_switch_handler(struct trace_seq *s, +				struct pevent_record *record, +				struct event_format *event, void *context) +{ +	struct format_field *field; +	unsigned long long val; + +	if (pevent_get_field_val(s, event, "prev_pid", record, &val, 1)) +		return trace_seq_putc(s, '!'); + +	field = pevent_find_any_field(event, "prev_comm"); +	if (field) { +		write_and_save_comm(field, record, s, val); +		trace_seq_putc(s, ':'); +	} +	trace_seq_printf(s, "%lld ", val); + +	if (pevent_get_field_val(s, event, "prev_prio", record, &val, 0) == 0) +		trace_seq_printf(s, "[%lld] ", val); + +	if (pevent_get_field_val(s,  event, "prev_state", record, &val, 0) == 0) +		write_state(s, val); + +	trace_seq_puts(s, " ==> "); + +	if (pevent_get_field_val(s, event, "next_pid", record, &val, 1)) +		return trace_seq_putc(s, '!'); + +	field = pevent_find_any_field(event, "next_comm"); +	if (field) { +		write_and_save_comm(field, record, s, val); +		trace_seq_putc(s, ':'); +	} +	trace_seq_printf(s, "%lld", val); + +	if (pevent_get_field_val(s, event, "next_prio", record, &val, 0) == 0) +		trace_seq_printf(s, " [%lld]", val); + +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_event_handler(pevent, -1, "sched", "sched_switch", +				      sched_switch_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "sched", "sched_wakeup", +				      sched_wakeup_handler, NULL); + +	pevent_register_event_handler(pevent, -1, "sched", "sched_wakeup_new", +				      sched_wakeup_handler, NULL); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_event_handler(pevent, -1, "sched", "sched_switch", +					sched_switch_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "sched", "sched_wakeup", +					sched_wakeup_handler, NULL); + +	pevent_unregister_event_handler(pevent, -1, "sched", "sched_wakeup_new", +					sched_wakeup_handler, NULL); +} diff --git a/tools/lib/traceevent/plugin_scsi.c b/tools/lib/traceevent/plugin_scsi.c new file mode 100644 index 00000000000..eda326fc862 --- /dev/null +++ b/tools/lib/traceevent/plugin_scsi.c @@ -0,0 +1,429 @@ +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include "event-parse.h" + +typedef unsigned long sector_t; +typedef uint64_t u64; +typedef unsigned int u32; + +/* + *      SCSI opcodes + */ +#define TEST_UNIT_READY			0x00 +#define REZERO_UNIT			0x01 +#define REQUEST_SENSE			0x03 +#define FORMAT_UNIT			0x04 +#define READ_BLOCK_LIMITS		0x05 +#define REASSIGN_BLOCKS			0x07 +#define INITIALIZE_ELEMENT_STATUS	0x07 +#define READ_6				0x08 +#define WRITE_6				0x0a +#define SEEK_6				0x0b +#define READ_REVERSE			0x0f +#define WRITE_FILEMARKS			0x10 +#define SPACE				0x11 +#define INQUIRY				0x12 +#define RECOVER_BUFFERED_DATA		0x14 +#define MODE_SELECT			0x15 +#define RESERVE				0x16 +#define RELEASE				0x17 +#define COPY				0x18 +#define ERASE				0x19 +#define MODE_SENSE			0x1a +#define START_STOP			0x1b +#define RECEIVE_DIAGNOSTIC		0x1c +#define SEND_DIAGNOSTIC			0x1d +#define ALLOW_MEDIUM_REMOVAL		0x1e + +#define READ_FORMAT_CAPACITIES		0x23 +#define SET_WINDOW			0x24 +#define READ_CAPACITY			0x25 +#define READ_10				0x28 +#define WRITE_10			0x2a +#define SEEK_10				0x2b +#define POSITION_TO_ELEMENT		0x2b +#define WRITE_VERIFY			0x2e +#define VERIFY				0x2f +#define SEARCH_HIGH			0x30 +#define SEARCH_EQUAL			0x31 +#define SEARCH_LOW			0x32 +#define SET_LIMITS			0x33 +#define PRE_FETCH			0x34 +#define READ_POSITION			0x34 +#define SYNCHRONIZE_CACHE		0x35 +#define LOCK_UNLOCK_CACHE		0x36 +#define READ_DEFECT_DATA		0x37 +#define MEDIUM_SCAN			0x38 +#define COMPARE				0x39 +#define COPY_VERIFY			0x3a +#define WRITE_BUFFER			0x3b +#define READ_BUFFER			0x3c +#define UPDATE_BLOCK			0x3d +#define READ_LONG			0x3e +#define WRITE_LONG			0x3f +#define CHANGE_DEFINITION		0x40 +#define WRITE_SAME			0x41 +#define UNMAP				0x42 +#define READ_TOC			0x43 +#define READ_HEADER			0x44 +#define GET_EVENT_STATUS_NOTIFICATION	0x4a +#define LOG_SELECT			0x4c +#define LOG_SENSE			0x4d +#define XDWRITEREAD_10			0x53 +#define MODE_SELECT_10			0x55 +#define RESERVE_10			0x56 +#define RELEASE_10			0x57 +#define MODE_SENSE_10			0x5a +#define PERSISTENT_RESERVE_IN		0x5e +#define PERSISTENT_RESERVE_OUT		0x5f +#define VARIABLE_LENGTH_CMD		0x7f +#define REPORT_LUNS			0xa0 +#define SECURITY_PROTOCOL_IN		0xa2 +#define MAINTENANCE_IN			0xa3 +#define MAINTENANCE_OUT			0xa4 +#define MOVE_MEDIUM			0xa5 +#define EXCHANGE_MEDIUM			0xa6 +#define READ_12				0xa8 +#define WRITE_12			0xaa +#define READ_MEDIA_SERIAL_NUMBER	0xab +#define WRITE_VERIFY_12			0xae +#define VERIFY_12			0xaf +#define SEARCH_HIGH_12			0xb0 +#define SEARCH_EQUAL_12			0xb1 +#define SEARCH_LOW_12			0xb2 +#define SECURITY_PROTOCOL_OUT		0xb5 +#define READ_ELEMENT_STATUS		0xb8 +#define SEND_VOLUME_TAG			0xb6 +#define WRITE_LONG_2			0xea +#define EXTENDED_COPY			0x83 +#define RECEIVE_COPY_RESULTS		0x84 +#define ACCESS_CONTROL_IN		0x86 +#define ACCESS_CONTROL_OUT		0x87 +#define READ_16				0x88 +#define WRITE_16			0x8a +#define READ_ATTRIBUTE			0x8c +#define WRITE_ATTRIBUTE			0x8d +#define VERIFY_16			0x8f +#define SYNCHRONIZE_CACHE_16		0x91 +#define WRITE_SAME_16			0x93 +#define SERVICE_ACTION_IN		0x9e +/* values for service action in */ +#define	SAI_READ_CAPACITY_16		0x10 +#define SAI_GET_LBA_STATUS		0x12 +/* values for VARIABLE_LENGTH_CMD service action codes + * see spc4r17 Section D.3.5, table D.7 and D.8 */ +#define VLC_SA_RECEIVE_CREDENTIAL	0x1800 +/* values for maintenance in */ +#define MI_REPORT_IDENTIFYING_INFORMATION		0x05 +#define MI_REPORT_TARGET_PGS				0x0a +#define MI_REPORT_ALIASES				0x0b +#define MI_REPORT_SUPPORTED_OPERATION_CODES		0x0c +#define MI_REPORT_SUPPORTED_TASK_MANAGEMENT_FUNCTIONS	0x0d +#define MI_REPORT_PRIORITY				0x0e +#define MI_REPORT_TIMESTAMP				0x0f +#define MI_MANAGEMENT_PROTOCOL_IN			0x10 +/* value for MI_REPORT_TARGET_PGS ext header */ +#define MI_EXT_HDR_PARAM_FMT		0x20 +/* values for maintenance out */ +#define MO_SET_IDENTIFYING_INFORMATION	0x06 +#define MO_SET_TARGET_PGS		0x0a +#define MO_CHANGE_ALIASES		0x0b +#define MO_SET_PRIORITY			0x0e +#define MO_SET_TIMESTAMP		0x0f +#define MO_MANAGEMENT_PROTOCOL_OUT	0x10 +/* values for variable length command */ +#define XDREAD_32			0x03 +#define XDWRITE_32			0x04 +#define XPWRITE_32			0x06 +#define XDWRITEREAD_32			0x07 +#define READ_32				0x09 +#define VERIFY_32			0x0a +#define WRITE_32			0x0b +#define WRITE_SAME_32			0x0d + +#define SERVICE_ACTION16(cdb) (cdb[1] & 0x1f) +#define SERVICE_ACTION32(cdb) ((cdb[8] << 8) | cdb[9]) + +static const char * +scsi_trace_misc(struct trace_seq *, unsigned char *, int); + +static const char * +scsi_trace_rw6(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; +	sector_t lba = 0, txlen = 0; + +	lba |= ((cdb[1] & 0x1F) << 16); +	lba |=  (cdb[2] << 8); +	lba |=   cdb[3]; +	txlen = cdb[4]; + +	trace_seq_printf(p, "lba=%llu txlen=%llu", +			 (unsigned long long)lba, (unsigned long long)txlen); +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_rw10(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; +	sector_t lba = 0, txlen = 0; + +	lba |= (cdb[2] << 24); +	lba |= (cdb[3] << 16); +	lba |= (cdb[4] << 8); +	lba |=  cdb[5]; +	txlen |= (cdb[7] << 8); +	txlen |=  cdb[8]; + +	trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u", +			 (unsigned long long)lba, (unsigned long long)txlen, +			 cdb[1] >> 5); + +	if (cdb[0] == WRITE_SAME) +		trace_seq_printf(p, " unmap=%u", cdb[1] >> 3 & 1); + +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_rw12(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; +	sector_t lba = 0, txlen = 0; + +	lba |= (cdb[2] << 24); +	lba |= (cdb[3] << 16); +	lba |= (cdb[4] << 8); +	lba |=  cdb[5]; +	txlen |= (cdb[6] << 24); +	txlen |= (cdb[7] << 16); +	txlen |= (cdb[8] << 8); +	txlen |=  cdb[9]; + +	trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u", +			 (unsigned long long)lba, (unsigned long long)txlen, +			 cdb[1] >> 5); +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_rw16(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; +	sector_t lba = 0, txlen = 0; + +	lba |= ((u64)cdb[2] << 56); +	lba |= ((u64)cdb[3] << 48); +	lba |= ((u64)cdb[4] << 40); +	lba |= ((u64)cdb[5] << 32); +	lba |= (cdb[6] << 24); +	lba |= (cdb[7] << 16); +	lba |= (cdb[8] << 8); +	lba |=  cdb[9]; +	txlen |= (cdb[10] << 24); +	txlen |= (cdb[11] << 16); +	txlen |= (cdb[12] << 8); +	txlen |=  cdb[13]; + +	trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u", +			 (unsigned long long)lba, (unsigned long long)txlen, +			 cdb[1] >> 5); + +	if (cdb[0] == WRITE_SAME_16) +		trace_seq_printf(p, " unmap=%u", cdb[1] >> 3 & 1); + +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_rw32(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len, *cmd; +	sector_t lba = 0, txlen = 0; +	u32 ei_lbrt = 0; + +	switch (SERVICE_ACTION32(cdb)) { +	case READ_32: +		cmd = "READ"; +		break; +	case VERIFY_32: +		cmd = "VERIFY"; +		break; +	case WRITE_32: +		cmd = "WRITE"; +		break; +	case WRITE_SAME_32: +		cmd = "WRITE_SAME"; +		break; +	default: +		trace_seq_printf(p, "UNKNOWN"); +		goto out; +	} + +	lba |= ((u64)cdb[12] << 56); +	lba |= ((u64)cdb[13] << 48); +	lba |= ((u64)cdb[14] << 40); +	lba |= ((u64)cdb[15] << 32); +	lba |= (cdb[16] << 24); +	lba |= (cdb[17] << 16); +	lba |= (cdb[18] << 8); +	lba |=  cdb[19]; +	ei_lbrt |= (cdb[20] << 24); +	ei_lbrt |= (cdb[21] << 16); +	ei_lbrt |= (cdb[22] << 8); +	ei_lbrt |=  cdb[23]; +	txlen |= (cdb[28] << 24); +	txlen |= (cdb[29] << 16); +	txlen |= (cdb[30] << 8); +	txlen |=  cdb[31]; + +	trace_seq_printf(p, "%s_32 lba=%llu txlen=%llu protect=%u ei_lbrt=%u", +			 cmd, (unsigned long long)lba, +			 (unsigned long long)txlen, cdb[10] >> 5, ei_lbrt); + +	if (SERVICE_ACTION32(cdb) == WRITE_SAME_32) +		trace_seq_printf(p, " unmap=%u", cdb[10] >> 3 & 1); + +out: +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_unmap(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; +	unsigned int regions = cdb[7] << 8 | cdb[8]; + +	trace_seq_printf(p, "regions=%u", (regions - 8) / 16); +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_service_action_in(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len, *cmd; +	sector_t lba = 0; +	u32 alloc_len = 0; + +	switch (SERVICE_ACTION16(cdb)) { +	case SAI_READ_CAPACITY_16: +		cmd = "READ_CAPACITY_16"; +		break; +	case SAI_GET_LBA_STATUS: +		cmd = "GET_LBA_STATUS"; +		break; +	default: +		trace_seq_printf(p, "UNKNOWN"); +		goto out; +	} + +	lba |= ((u64)cdb[2] << 56); +	lba |= ((u64)cdb[3] << 48); +	lba |= ((u64)cdb[4] << 40); +	lba |= ((u64)cdb[5] << 32); +	lba |= (cdb[6] << 24); +	lba |= (cdb[7] << 16); +	lba |= (cdb[8] << 8); +	lba |=  cdb[9]; +	alloc_len |= (cdb[10] << 24); +	alloc_len |= (cdb[11] << 16); +	alloc_len |= (cdb[12] << 8); +	alloc_len |=  cdb[13]; + +	trace_seq_printf(p, "%s lba=%llu alloc_len=%u", cmd, +			 (unsigned long long)lba, alloc_len); + +out: +	trace_seq_putc(p, 0); +	return ret; +} + +static const char * +scsi_trace_varlen(struct trace_seq *p, unsigned char *cdb, int len) +{ +	switch (SERVICE_ACTION32(cdb)) { +	case READ_32: +	case VERIFY_32: +	case WRITE_32: +	case WRITE_SAME_32: +		return scsi_trace_rw32(p, cdb, len); +	default: +		return scsi_trace_misc(p, cdb, len); +	} +} + +static const char * +scsi_trace_misc(struct trace_seq *p, unsigned char *cdb, int len) +{ +	const char *ret = p->buffer + p->len; + +	trace_seq_printf(p, "-"); +	trace_seq_putc(p, 0); +	return ret; +} + +const char * +scsi_trace_parse_cdb(struct trace_seq *p, unsigned char *cdb, int len) +{ +	switch (cdb[0]) { +	case READ_6: +	case WRITE_6: +		return scsi_trace_rw6(p, cdb, len); +	case READ_10: +	case VERIFY: +	case WRITE_10: +	case WRITE_SAME: +		return scsi_trace_rw10(p, cdb, len); +	case READ_12: +	case VERIFY_12: +	case WRITE_12: +		return scsi_trace_rw12(p, cdb, len); +	case READ_16: +	case VERIFY_16: +	case WRITE_16: +	case WRITE_SAME_16: +		return scsi_trace_rw16(p, cdb, len); +	case UNMAP: +		return scsi_trace_unmap(p, cdb, len); +	case SERVICE_ACTION_IN: +		return scsi_trace_service_action_in(p, cdb, len); +	case VARIABLE_LENGTH_CMD: +		return scsi_trace_varlen(p, cdb, len); +	default: +		return scsi_trace_misc(p, cdb, len); +	} +} + +unsigned long long process_scsi_trace_parse_cdb(struct trace_seq *s, +						unsigned long long *args) +{ +	scsi_trace_parse_cdb(s, (unsigned char *) (unsigned long) args[1], args[2]); +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_print_function(pevent, +				       process_scsi_trace_parse_cdb, +				       PEVENT_FUNC_ARG_STRING, +				       "scsi_trace_parse_cdb", +				       PEVENT_FUNC_ARG_PTR, +				       PEVENT_FUNC_ARG_PTR, +				       PEVENT_FUNC_ARG_INT, +				       PEVENT_FUNC_ARG_VOID); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_print_function(pevent, process_scsi_trace_parse_cdb, +					 "scsi_trace_parse_cdb"); +} diff --git a/tools/lib/traceevent/plugin_xen.c b/tools/lib/traceevent/plugin_xen.c new file mode 100644 index 00000000000..3a413eaada6 --- /dev/null +++ b/tools/lib/traceevent/plugin_xen.c @@ -0,0 +1,136 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "event-parse.h" + +#define __HYPERVISOR_set_trap_table			0 +#define __HYPERVISOR_mmu_update				1 +#define __HYPERVISOR_set_gdt				2 +#define __HYPERVISOR_stack_switch			3 +#define __HYPERVISOR_set_callbacks			4 +#define __HYPERVISOR_fpu_taskswitch			5 +#define __HYPERVISOR_sched_op_compat			6 +#define __HYPERVISOR_dom0_op				7 +#define __HYPERVISOR_set_debugreg			8 +#define __HYPERVISOR_get_debugreg			9 +#define __HYPERVISOR_update_descriptor			10 +#define __HYPERVISOR_memory_op				12 +#define __HYPERVISOR_multicall				13 +#define __HYPERVISOR_update_va_mapping			14 +#define __HYPERVISOR_set_timer_op			15 +#define __HYPERVISOR_event_channel_op_compat		16 +#define __HYPERVISOR_xen_version			17 +#define __HYPERVISOR_console_io				18 +#define __HYPERVISOR_physdev_op_compat			19 +#define __HYPERVISOR_grant_table_op			20 +#define __HYPERVISOR_vm_assist				21 +#define __HYPERVISOR_update_va_mapping_otherdomain	22 +#define __HYPERVISOR_iret				23 /* x86 only */ +#define __HYPERVISOR_vcpu_op				24 +#define __HYPERVISOR_set_segment_base			25 /* x86/64 only */ +#define __HYPERVISOR_mmuext_op				26 +#define __HYPERVISOR_acm_op				27 +#define __HYPERVISOR_nmi_op				28 +#define __HYPERVISOR_sched_op				29 +#define __HYPERVISOR_callback_op			30 +#define __HYPERVISOR_xenoprof_op			31 +#define __HYPERVISOR_event_channel_op			32 +#define __HYPERVISOR_physdev_op				33 +#define __HYPERVISOR_hvm_op				34 +#define __HYPERVISOR_tmem_op				38 + +/* Architecture-specific hypercall definitions. */ +#define __HYPERVISOR_arch_0				48 +#define __HYPERVISOR_arch_1				49 +#define __HYPERVISOR_arch_2				50 +#define __HYPERVISOR_arch_3				51 +#define __HYPERVISOR_arch_4				52 +#define __HYPERVISOR_arch_5				53 +#define __HYPERVISOR_arch_6				54 +#define __HYPERVISOR_arch_7				55 + +#define N(x)	[__HYPERVISOR_##x] = "("#x")" +static const char *xen_hypercall_names[] = { +	N(set_trap_table), +	N(mmu_update), +	N(set_gdt), +	N(stack_switch), +	N(set_callbacks), +	N(fpu_taskswitch), +	N(sched_op_compat), +	N(dom0_op), +	N(set_debugreg), +	N(get_debugreg), +	N(update_descriptor), +	N(memory_op), +	N(multicall), +	N(update_va_mapping), +	N(set_timer_op), +	N(event_channel_op_compat), +	N(xen_version), +	N(console_io), +	N(physdev_op_compat), +	N(grant_table_op), +	N(vm_assist), +	N(update_va_mapping_otherdomain), +	N(iret), +	N(vcpu_op), +	N(set_segment_base), +	N(mmuext_op), +	N(acm_op), +	N(nmi_op), +	N(sched_op), +	N(callback_op), +	N(xenoprof_op), +	N(event_channel_op), +	N(physdev_op), +	N(hvm_op), + +/* Architecture-specific hypercall definitions. */ +	N(arch_0), +	N(arch_1), +	N(arch_2), +	N(arch_3), +	N(arch_4), +	N(arch_5), +	N(arch_6), +	N(arch_7), +}; +#undef N + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static const char *xen_hypercall_name(unsigned op) +{ +	if (op < ARRAY_SIZE(xen_hypercall_names) && +	    xen_hypercall_names[op] != NULL) +		return xen_hypercall_names[op]; + +	return ""; +} + +unsigned long long process_xen_hypercall_name(struct trace_seq *s, +					      unsigned long long *args) +{ +	unsigned int op = args[0]; + +	trace_seq_printf(s, "%s", xen_hypercall_name(op)); +	return 0; +} + +int PEVENT_PLUGIN_LOADER(struct pevent *pevent) +{ +	pevent_register_print_function(pevent, +				       process_xen_hypercall_name, +				       PEVENT_FUNC_ARG_STRING, +				       "xen_hypercall_name", +				       PEVENT_FUNC_ARG_INT, +				       PEVENT_FUNC_ARG_VOID); +	return 0; +} + +void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) +{ +	pevent_unregister_print_function(pevent, process_xen_hypercall_name, +					 "xen_hypercall_name"); +} diff --git a/tools/lib/traceevent/trace-seq.c b/tools/lib/traceevent/trace-seq.c new file mode 100644 index 00000000000..ec3bd16a548 --- /dev/null +++ b/tools/lib/traceevent/trace-seq.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2009 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> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include <asm/bug.h> +#include "event-parse.h" +#include "event-utils.h" + +/* + * The TRACE_SEQ_POISON is to catch the use of using + * a trace_seq structure after it was destroyed. + */ +#define TRACE_SEQ_POISON	((void *)0xdeadbeef) +#define TRACE_SEQ_CHECK(s)						\ +do {									\ +	if (WARN_ONCE((s)->buffer == TRACE_SEQ_POISON,			\ +		      "Usage of trace_seq after it was destroyed"))	\ +		(s)->state = TRACE_SEQ__BUFFER_POISONED;		\ +} while (0) + +#define TRACE_SEQ_CHECK_RET_N(s, n)		\ +do {						\ +	TRACE_SEQ_CHECK(s);			\ +	if ((s)->state != TRACE_SEQ__GOOD)	\ +		return n; 			\ +} while (0) + +#define TRACE_SEQ_CHECK_RET(s)   TRACE_SEQ_CHECK_RET_N(s, ) +#define TRACE_SEQ_CHECK_RET0(s)  TRACE_SEQ_CHECK_RET_N(s, 0) + +/** + * trace_seq_init - initialize the trace_seq structure + * @s: a pointer to the trace_seq structure to initialize + */ +void trace_seq_init(struct trace_seq *s) +{ +	s->len = 0; +	s->readpos = 0; +	s->buffer_size = TRACE_SEQ_BUF_SIZE; +	s->buffer = malloc(s->buffer_size); +	if (s->buffer != NULL) +		s->state = TRACE_SEQ__GOOD; +	else +		s->state = TRACE_SEQ__MEM_ALLOC_FAILED; +} + +/** + * trace_seq_reset - re-initialize the trace_seq structure + * @s: a pointer to the trace_seq structure to reset + */ +void trace_seq_reset(struct trace_seq *s) +{ +	if (!s) +		return; +	TRACE_SEQ_CHECK(s); +	s->len = 0; +	s->readpos = 0; +} + +/** + * trace_seq_destroy - free up memory of a trace_seq + * @s: a pointer to the trace_seq to free the buffer + * + * Only frees the buffer, not the trace_seq struct itself. + */ +void trace_seq_destroy(struct trace_seq *s) +{ +	if (!s) +		return; +	TRACE_SEQ_CHECK_RET(s); +	free(s->buffer); +	s->buffer = TRACE_SEQ_POISON; +} + +static void expand_buffer(struct trace_seq *s) +{ +	char *buf; + +	buf = realloc(s->buffer, s->buffer_size + TRACE_SEQ_BUF_SIZE); +	if (WARN_ONCE(!buf, "Can't allocate trace_seq buffer memory")) { +		s->state = TRACE_SEQ__MEM_ALLOC_FAILED; +		return; +	} + +	s->buffer = buf; +	s->buffer_size += TRACE_SEQ_BUF_SIZE; +} + +/** + * trace_seq_printf - sequence printing of trace information + * @s: trace sequence descriptor + * @fmt: printf format string + * + * It returns 0 if the trace oversizes the buffer's free + * space, 1 otherwise. + * + * The tracer may use either sequence operations or its own + * copy to user routines. To simplify formating of a trace + * trace_seq_printf is used to store strings into a special + * buffer (@s). Then the output may be either used by + * the sequencer or pulled into another buffer. + */ +int +trace_seq_printf(struct trace_seq *s, const char *fmt, ...) +{ +	va_list ap; +	int len; +	int ret; + + try_again: +	TRACE_SEQ_CHECK_RET0(s); + +	len = (s->buffer_size - 1) - s->len; + +	va_start(ap, fmt); +	ret = vsnprintf(s->buffer + s->len, len, fmt, ap); +	va_end(ap); + +	if (ret >= len) { +		expand_buffer(s); +		goto try_again; +	} + +	s->len += ret; + +	return 1; +} + +/** + * trace_seq_vprintf - sequence printing of trace information + * @s: trace sequence descriptor + * @fmt: printf format string + * + * The tracer may use either sequence operations or its own + * copy to user routines. To simplify formating of a trace + * trace_seq_printf is used to store strings into a special + * buffer (@s). Then the output may be either used by + * the sequencer or pulled into another buffer. + */ +int +trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args) +{ +	int len; +	int ret; + + try_again: +	TRACE_SEQ_CHECK_RET0(s); + +	len = (s->buffer_size - 1) - s->len; + +	ret = vsnprintf(s->buffer + s->len, len, fmt, args); + +	if (ret >= len) { +		expand_buffer(s); +		goto try_again; +	} + +	s->len += ret; + +	return len; +} + +/** + * trace_seq_puts - trace sequence printing of simple string + * @s: trace sequence descriptor + * @str: simple string to record + * + * The tracer may use either the sequence operations or its own + * copy to user routines. This function records a simple string + * into a special buffer (@s) for later retrieval by a sequencer + * or other mechanism. + */ +int trace_seq_puts(struct trace_seq *s, const char *str) +{ +	int len; + +	TRACE_SEQ_CHECK_RET0(s); + +	len = strlen(str); + +	while (len > ((s->buffer_size - 1) - s->len)) +		expand_buffer(s); + +	TRACE_SEQ_CHECK_RET0(s); + +	memcpy(s->buffer + s->len, str, len); +	s->len += len; + +	return len; +} + +int trace_seq_putc(struct trace_seq *s, unsigned char c) +{ +	TRACE_SEQ_CHECK_RET0(s); + +	while (s->len >= (s->buffer_size - 1)) +		expand_buffer(s); + +	TRACE_SEQ_CHECK_RET0(s); + +	s->buffer[s->len++] = c; + +	return 1; +} + +void trace_seq_terminate(struct trace_seq *s) +{ +	TRACE_SEQ_CHECK_RET(s); + +	/* There's always one character left on the buffer */ +	s->buffer[s->len] = 0; +} + +int trace_seq_do_printf(struct trace_seq *s) +{ +	TRACE_SEQ_CHECK(s); + +	switch (s->state) { +	case TRACE_SEQ__GOOD: +		return printf("%.*s", s->len, s->buffer); +	case TRACE_SEQ__BUFFER_POISONED: +		puts("Usage of trace_seq after it was destroyed"); +		break; +	case TRACE_SEQ__MEM_ALLOC_FAILED: +		puts("Can't allocate trace_seq buffer memory"); +		break; +	} +	return -1; +}  | 
