diff options
Diffstat (limited to '')
69 files changed, 19850 insertions, 0 deletions
diff --git a/tools/lib/traceevent/.gitignore b/tools/lib/traceevent/.gitignore new file mode 100644 index 000000000..7123c70b9 --- /dev/null +++ b/tools/lib/traceevent/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +TRACEEVENT-CFLAGS +libtraceevent-dynamic-list +libtraceevent.so.* diff --git a/tools/lib/traceevent/Build b/tools/lib/traceevent/Build new file mode 100644 index 000000000..f9a5d7957 --- /dev/null +++ b/tools/lib/traceevent/Build @@ -0,0 +1,8 @@ +libtraceevent-y += event-parse.o +libtraceevent-y += event-plugin.o +libtraceevent-y += trace-seq.o +libtraceevent-y += parse-filter.o +libtraceevent-y += parse-utils.o +libtraceevent-y += kbuffer-parse.o +libtraceevent-y += tep_strerror.o +libtraceevent-y += event-parse-api.o diff --git a/tools/lib/traceevent/Documentation/Makefile b/tools/lib/traceevent/Documentation/Makefile new file mode 100644 index 000000000..aa72ab96c --- /dev/null +++ b/tools/lib/traceevent/Documentation/Makefile @@ -0,0 +1,207 @@ +include ../../../scripts/Makefile.include +include ../../../scripts/utilities.mak + +# This Makefile and manpage XSL files were taken from tools/perf/Documentation +# and modified for libtraceevent. + +MAN3_TXT= \ + $(wildcard libtraceevent-*.txt) \ + libtraceevent.txt + +MAN_TXT = $(MAN3_TXT) +_MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) +_MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT)) +_DOC_MAN3=$(patsubst %.txt,%.3,$(MAN3_TXT)) + +MAN_XML=$(addprefix $(OUTPUT),$(_MAN_XML)) +MAN_HTML=$(addprefix $(OUTPUT),$(_MAN_HTML)) +DOC_MAN3=$(addprefix $(OUTPUT),$(_DOC_MAN3)) + +# Make the path relative to DESTDIR, not prefix +ifndef DESTDIR +prefix?=$(HOME) +endif +bindir?=$(prefix)/bin +htmldir?=$(prefix)/share/doc/libtraceevent-doc +pdfdir?=$(prefix)/share/doc/libtraceevent-doc +mandir?=$(prefix)/share/man +man3dir=$(mandir)/man3 + +ASCIIDOC=asciidoc +ASCIIDOC_EXTRA = --unsafe -f asciidoc.conf +ASCIIDOC_HTML = xhtml11 +MANPAGE_XSL = manpage-normal.xsl +XMLTO_EXTRA = +INSTALL?=install +RM ?= rm -f + +ifdef USE_ASCIIDOCTOR +ASCIIDOC = asciidoctor +ASCIIDOC_EXTRA = -a compat-mode +ASCIIDOC_EXTRA += -I. -rasciidoctor-extensions +ASCIIDOC_EXTRA += -a mansource="libtraceevent" -a manmanual="libtraceevent Manual" +ASCIIDOC_HTML = xhtml5 +endif + +XMLTO=xmlto + +_tmp_tool_path := $(call get-executable,$(ASCIIDOC)) +ifeq ($(_tmp_tool_path),) + missing_tools = $(ASCIIDOC) +endif + +ifndef USE_ASCIIDOCTOR +_tmp_tool_path := $(call get-executable,$(XMLTO)) +ifeq ($(_tmp_tool_path),) + missing_tools += $(XMLTO) +endif +endif + +# +# For asciidoc ... +# -7.1.2, no extra settings are needed. +# 8.0-, set ASCIIDOC8. +# + +# +# For docbook-xsl ... +# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0) +# 1.69.0, no extra settings are needed? +# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? +# 1.71.1, no extra settings are needed? +# 1.72.0, set DOCBOOK_XSL_172. +# 1.73.0-, set ASCIIDOC_NO_ROFF +# + +# +# If you had been using DOCBOOK_XSL_172 in an attempt to get rid +# of 'the ".ft C" problem' in your generated manpages, and you +# instead ended up with weird characters around callouts, try +# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8). +# + +ifdef ASCIIDOC8 +ASCIIDOC_EXTRA += -a asciidoc7compatible +endif +ifdef DOCBOOK_XSL_172 +ASCIIDOC_EXTRA += -a libtraceevent-asciidoc-no-roff +MANPAGE_XSL = manpage-1.72.xsl +else + ifdef ASCIIDOC_NO_ROFF + # docbook-xsl after 1.72 needs the regular XSL, but will not + # pass-thru raw roff codes from asciidoc.conf, so turn them off. + ASCIIDOC_EXTRA += -a libtraceevent-asciidoc-no-roff + endif +endif +ifdef MAN_BOLD_LITERAL +XMLTO_EXTRA += -m manpage-bold-literal.xsl +endif +ifdef DOCBOOK_SUPPRESS_SP +XMLTO_EXTRA += -m manpage-suppress-sp.xsl +endif + +SHELL_PATH ?= $(SHELL) +# Shell quote; +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) + +DESTDIR ?= +DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' + +export DESTDIR DESTDIR_SQ + +# +# Please note that there is a minor bug in asciidoc. +# The version after 6.0.3 _will_ include the patch found here: +# http://marc.theaimsgroup.com/?l=libtraceevent&m=111558757202243&w=2 +# +# Until that version is released you may have to apply the patch +# yourself - yes, all 6 characters of it! +# +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifneq ($(V),1) + QUIET_ASCIIDOC = @echo ' ASCIIDOC '$@; + QUIET_XMLTO = @echo ' XMLTO '$@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) \ + echo ' SUBDIR ' $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + export V +endif +endif + +all: html man + +man: man3 +man3: $(DOC_MAN3) + +html: $(MAN_HTML) + +$(MAN_HTML) $(DOC_MAN3): asciidoc.conf + +install: install-man + +check-man-tools: +ifdef missing_tools + $(error "You need to install $(missing_tools) for man pages") +endif + +do-install-man: man + $(call QUIET_INSTALL, Documentation-man) \ + $(INSTALL) -d -m 755 $(DESTDIR)$(man3dir); \ + $(INSTALL) -m 644 $(DOC_MAN3) $(DESTDIR)$(man3dir); + +install-man: check-man-tools man do-install-man + +uninstall: uninstall-man + +uninstall-man: + $(call QUIET_UNINST, Documentation-man) \ + $(Q)$(RM) $(addprefix $(DESTDIR)$(man3dir)/,$(DOC_MAN3)) + + +ifdef missing_tools + DO_INSTALL_MAN = $(warning Please install $(missing_tools) to have the man pages installed) +else + DO_INSTALL_MAN = do-install-man +endif + +CLEAN_FILES = \ + $(MAN_XML) $(addsuffix +,$(MAN_XML)) \ + $(MAN_HTML) $(addsuffix +,$(MAN_HTML)) \ + $(DOC_MAN3) *.3 + +clean: + $(call QUIET_CLEAN, Documentation) $(RM) $(CLEAN_FILES) + +ifdef USE_ASCIIDOCTOR +$(OUTPUT)%.3 : $(OUTPUT)%.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b manpage -d manpage \ + $(ASCIIDOC_EXTRA) -alibtraceevent_version=$(EVENT_PARSE_VERSION) -o $@+ $< && \ + mv $@+ $@ +endif + +$(OUTPUT)%.3 : $(OUTPUT)%.xml + $(QUIET_XMLTO)$(RM) $@ && \ + $(XMLTO) -o $(OUTPUT). -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< + +$(OUTPUT)%.xml : %.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b docbook -d manpage \ + $(ASCIIDOC_EXTRA) -alibtraceevent_version=$(EVENT_PARSE_VERSION) -o $@+ $< && \ + mv $@+ $@ + +$(MAN_HTML): $(OUTPUT)%.html : %.txt + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + $(ASCIIDOC) -b $(ASCIIDOC_HTML) -d manpage \ + $(ASCIIDOC_EXTRA) -aperf_version=$(EVENT_PARSE_VERSION) -o $@+ $< && \ + mv $@+ $@ diff --git a/tools/lib/traceevent/Documentation/asciidoc.conf b/tools/lib/traceevent/Documentation/asciidoc.conf new file mode 100644 index 000000000..07595717f --- /dev/null +++ b/tools/lib/traceevent/Documentation/asciidoc.conf @@ -0,0 +1,120 @@ +## linktep: macro +# +# Usage: linktep:command[manpage-section] +# +# Note, {0} is the manpage section, while {target} is the command. +# +# Show TEP link as: <command>(<section>); if section is defined, else just show +# the command. + +[macros] +(?su)[\\]?(?P<name>linktep):(?P<target>\S*?)\[(?P<attrlist>.*?)\]= + +[attributes] +asterisk=* +plus=+ +caret=^ +startsb=[ +endsb=] +tilde=~ + +ifdef::backend-docbook[] +[linktep-inlinemacro] +{0%{target}} +{0#<citerefentry>} +{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>} +{0#</citerefentry>} +endif::backend-docbook[] + +ifdef::backend-docbook[] +ifndef::tep-asciidoc-no-roff[] +# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. +# v1.72 breaks with this because it replaces dots not in roff requests. +[listingblock] +<example><title>{title}</title> +<literallayout> +ifdef::doctype-manpage[] + .ft C +endif::doctype-manpage[] +| +ifdef::doctype-manpage[] + .ft +endif::doctype-manpage[] +</literallayout> +{title#}</example> +endif::tep-asciidoc-no-roff[] + +ifdef::tep-asciidoc-no-roff[] +ifdef::doctype-manpage[] +# The following two small workarounds insert a simple paragraph after screen +[listingblock] +<example><title>{title}</title> +<literallayout> +| +</literallayout><simpara></simpara> +{title#}</example> + +[verseblock] +<formalpara{id? id="{id}"}><title>{title}</title><para> +{title%}<literallayout{id? id="{id}"}> +{title#}<literallayout> +| +</literallayout> +{title#}</para></formalpara> +{title%}<simpara></simpara> +endif::doctype-manpage[] +endif::tep-asciidoc-no-roff[] +endif::backend-docbook[] + +ifdef::doctype-manpage[] +ifdef::backend-docbook[] +[header] +template::[header-declarations] +<refentry> +<refmeta> +<refentrytitle>{mantitle}</refentrytitle> +<manvolnum>{manvolnum}</manvolnum> +<refmiscinfo class="source">libtraceevent</refmiscinfo> +<refmiscinfo class="version">{libtraceevent_version}</refmiscinfo> +<refmiscinfo class="manual">libtraceevent Manual</refmiscinfo> +</refmeta> +<refnamediv> + <refname>{manname1}</refname> + <refname>{manname2}</refname> + <refname>{manname3}</refname> + <refname>{manname4}</refname> + <refname>{manname5}</refname> + <refname>{manname6}</refname> + <refname>{manname7}</refname> + <refname>{manname8}</refname> + <refname>{manname9}</refname> + <refname>{manname10}</refname> + <refname>{manname11}</refname> + <refname>{manname12}</refname> + <refname>{manname13}</refname> + <refname>{manname14}</refname> + <refname>{manname15}</refname> + <refname>{manname16}</refname> + <refname>{manname17}</refname> + <refname>{manname18}</refname> + <refname>{manname19}</refname> + <refname>{manname20}</refname> + <refname>{manname21}</refname> + <refname>{manname22}</refname> + <refname>{manname23}</refname> + <refname>{manname24}</refname> + <refname>{manname25}</refname> + <refname>{manname26}</refname> + <refname>{manname27}</refname> + <refname>{manname28}</refname> + <refname>{manname29}</refname> + <refname>{manname30}</refname> + <refpurpose>{manpurpose}</refpurpose> +</refnamediv> +endif::backend-docbook[] +endif::doctype-manpage[] + +ifdef::backend-xhtml11[] +[linktep-inlinemacro] +<a href="{target}.html">{target}{0?({0})}</a> +endif::backend-xhtml11[] diff --git a/tools/lib/traceevent/Documentation/libtraceevent-commands.txt b/tools/lib/traceevent/Documentation/libtraceevent-commands.txt new file mode 100644 index 000000000..bec552001 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-commands.txt @@ -0,0 +1,153 @@ +libtraceevent(3) +================ + +NAME +---- +tep_register_comm, tep_override_comm, tep_pid_is_registered, +tep_data_comm_from_pid, tep_data_pid_from_comm, tep_cmdline_pid - +Manage pid to process name mappings. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_register_comm*(struct tep_handle pass:[*]_tep_, const char pass:[*]_comm_, int _pid_); +int *tep_override_comm*(struct tep_handle pass:[*]_tep_, const char pass:[*]_comm_, int _pid_); +bool *tep_is_pid_registered*(struct tep_handle pass:[*]_tep_, int _pid_); +const char pass:[*]*tep_data_comm_from_pid*(struct tep_handle pass:[*]_pevent_, int _pid_); +struct cmdline pass:[*]*tep_data_pid_from_comm*(struct tep_handle pass:[*]_pevent_, const char pass:[*]_comm_, struct cmdline pass:[*]_next_); +int *tep_cmdline_pid*(struct tep_handle pass:[*]_pevent_, struct cmdline pass:[*]_cmdline_); +-- + +DESCRIPTION +----------- +These functions can be used to handle the mapping between pid and process name. +The library builds a cache of these mappings, which is used to display the name +of the process, instead of its pid. This information can be retrieved from +tracefs/saved_cmdlines file. + +The _tep_register_comm()_ function registers a _pid_ / process name mapping. +If a command with the same _pid_ is already registered, an error is returned. +The _pid_ argument is the process ID, the _comm_ argument is the process name, +_tep_ is the event context. The _comm_ is duplicated internally. + +The _tep_override_comm()_ function registers a _pid_ / process name mapping. +If a process with the same pid is already registered, the process name string is +udapted with the new one. The _pid_ argument is the process ID, the _comm_ +argument is the process name, _tep_ is the event context. The _comm_ is +duplicated internally. + +The _tep_is_pid_registered()_ function checks if a pid has a process name +mapping registered. The _pid_ argument is the process ID, _tep_ is the event +context. + +The _tep_data_comm_from_pid()_ function returns the process name for a given +pid. The _pid_ argument is the process ID, _tep_ is the event context. +The returned string should not be freed, but will be freed when the _tep_ +handler is closed. + +The _tep_data_pid_from_comm()_ function returns a pid for a given process name. +The _comm_ argument is the process name, _tep_ is the event context. +The argument _next_ is the cmdline structure to search for the next pid. +As there may be more than one pid for a given process, the result of this call +can be passed back into a recurring call in the _next_ parameter, to search for +the next pid. If _next_ is NULL, it will return the first pid associated with +the _comm_. The function performs a linear search, so it may be slow. + +The _tep_cmdline_pid()_ function returns the pid associated with a given +_cmdline_. The _tep_ argument is the event context. + +RETURN VALUE +------------ +_tep_register_comm()_ function returns 0 on success. In case of an error -1 is +returned and errno is set to indicate the cause of the problem: ENOMEM, if there +is not enough memory to duplicate the _comm_ or EEXIST if a mapping for this +_pid_ is already registered. + +_tep_override_comm()_ function returns 0 on success. In case of an error -1 is +returned and errno is set to indicate the cause of the problem: ENOMEM, if there +is not enough memory to duplicate the _comm_. + +_tep_is_pid_registered()_ function returns true if the _pid_ has a process name +mapped to it, false otherwise. + +_tep_data_comm_from_pid()_ function returns the process name as string, or the +string "<...>" if there is no mapping for the given pid. + +_tep_data_pid_from_comm()_ function returns a pointer to a struct cmdline, that +holds a pid for a given process, or NULL if none is found. This result can be +passed back into a recurring call as the _next_ parameter of the function. + +_tep_cmdline_pid()_ functions returns the pid for the give cmdline. If _cmdline_ + is NULL, then -1 is returned. + +EXAMPLE +------- +The following example registers pid for command "ls", in context of event _tep_ +and performs various searches for pid / process name mappings: +[source,c] +-- +#include <event-parse.h> +... +int ret; +int ls_pid = 1021; +struct tep_handle *tep = tep_alloc(); +... + ret = tep_register_comm(tep, "ls", ls_pid); + if (ret != 0 && errno == EEXIST) + ret = tep_override_comm(tep, "ls", ls_pid); + if (ret != 0) { + /* Failed to register pid / command mapping */ + } +... + if (tep_is_pid_registered(tep, ls_pid) == 0) { + /* Command mapping for ls_pid is not registered */ + } +... + const char *comm = tep_data_comm_from_pid(tep, ls_pid); + if (comm) { + /* Found process name for ls_pid */ + } +... + int pid; + struct cmdline *cmd = tep_data_pid_from_comm(tep, "ls", NULL); + while (cmd) { + pid = tep_cmdline_pid(tep, cmd); + /* Found pid for process "ls" */ + cmd = tep_data_pid_from_comm(tep, "ls", cmd); + } +-- +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-cpus.txt b/tools/lib/traceevent/Documentation/libtraceevent-cpus.txt new file mode 100644 index 000000000..5ad70e43b --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-cpus.txt @@ -0,0 +1,77 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_cpus, tep_set_cpus - Get / set the number of CPUs, which have a tracing +buffer representing it. Note, the buffer may be empty. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_get_cpus*(struct tep_handle pass:[*]_tep_); +void *tep_set_cpus*(struct tep_handle pass:[*]_tep_, int _cpus_); +-- + +DESCRIPTION +----------- +The _tep_get_cpus()_ function gets the number of CPUs, which have a tracing +buffer representing it. The _tep_ argument is trace event parser context. + +The _tep_set_cpus()_ function sets the number of CPUs, which have a tracing +buffer representing it. The _tep_ argument is trace event parser context. +The _cpu_ argument is the number of CPUs with tracing data. + +RETURN VALUE +------------ +The _tep_get_cpus()_ functions returns the number of CPUs, which have tracing +data recorded. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... + tep_set_cpus(tep, 5); +... + printf("We have tracing data for %d CPUs", tep_get_cpus(tep)); +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-endian_read.txt b/tools/lib/traceevent/Documentation/libtraceevent-endian_read.txt new file mode 100644 index 000000000..e64851b6e --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-endian_read.txt @@ -0,0 +1,78 @@ +libtraceevent(3) +================ + +NAME +---- +tep_read_number - Reads a number from raw data. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +unsigned long long *tep_read_number*(struct tep_handle pass:[*]_tep_, const void pass:[*]_ptr_, int _size_); +-- + +DESCRIPTION +----------- +The _tep_read_number()_ function reads an integer from raw data, taking into +account the endianness of the raw data and the current host. The _tep_ argument +is the trace event parser context. The _ptr_ is a pointer to the raw data, where +the integer is, and the _size_ is the size of the integer. + +RETURN VALUE +------------ +The _tep_read_number()_ function returns the integer in the byte order of +the current host. In case of an error, 0 is returned. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +void process_record(struct tep_record *record) +{ + int offset = 24; + int data = tep_read_number(tep, record->data + offset, 4); + + /* Read the 4 bytes at the offset 24 of data as an integer */ +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-event_find.txt b/tools/lib/traceevent/Documentation/libtraceevent-event_find.txt new file mode 100644 index 000000000..7bc062c9f --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-event_find.txt @@ -0,0 +1,103 @@ +libtraceevent(3) +================ + +NAME +---- +tep_find_event,tep_find_event_by_name,tep_find_event_by_record - +Find events by given key. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_event pass:[*]*tep_find_event*(struct tep_handle pass:[*]_tep_, int _id_); +struct tep_event pass:[*]*tep_find_event_by_name*(struct tep_handle pass:[*]_tep_, const char pass:[*]_sys_, const char pass:[*]_name_); +struct tep_event pass:[*]*tep_find_event_by_record*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_record_); +-- + +DESCRIPTION +----------- +This set of functions can be used to search for an event, based on a given +criteria. All functions require a pointer to a _tep_, trace event parser +context. + +The _tep_find_event()_ function searches for an event by given event _id_. The +event ID is assigned dynamically and can be viewed in event's format file, +"ID" field. + +The tep_find_event_by_name()_ function searches for an event by given +event _name_, under the system _sys_. If the _sys_ is NULL (not specified), +the first event with _name_ is returned. + +The tep_find_event_by_record()_ function searches for an event from a given +_record_. + +RETURN VALUE +------------ +All these functions return a pointer to the found event, or NULL if there is no +such event. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +struct tep_event *event; + +event = tep_find_event(tep, 1857); +if (event == NULL) { + /* There is no event with ID 1857 */ +} + +event = tep_find_event_by_name(tep, "kvm", "kvm_exit"); +if (event == NULL) { + /* There is no kvm_exit event, from kvm system */ +} + +void event_from_record(struct tep_record *record) +{ + struct tep_event *event = tep_find_event_by_record(tep, record); + if (event == NULL) { + /* There is no event from given record */ + } +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-event_get.txt b/tools/lib/traceevent/Documentation/libtraceevent-event_get.txt new file mode 100644 index 000000000..6525092fc --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-event_get.txt @@ -0,0 +1,99 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_event, tep_get_first_event, tep_get_events_count - Access events. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_event pass:[*]*tep_get_event*(struct tep_handle pass:[*]_tep_, int _index_); +struct tep_event pass:[*]*tep_get_first_event*(struct tep_handle pass:[*]_tep_); +int *tep_get_events_count*(struct tep_handle pass:[*]_tep_); +-- + +DESCRIPTION +----------- +The _tep_get_event()_ function returns a pointer to event at the given _index_. +The _tep_ argument is trace event parser context, the _index_ is the index of +the requested event. + +The _tep_get_first_event()_ function returns a pointer to the first event. +As events are stored in an array, this function returns the pointer to the +beginning of the array. The _tep_ argument is trace event parser context. + +The _tep_get_events_count()_ function returns the number of the events +in the array. The _tep_ argument is trace event parser context. + +RETURN VALUE +------------ +The _tep_get_event()_ returns a pointer to the event located at _index_. +NULL is returned in case of error, in case there are no events or _index_ is +out of range. + +The _tep_get_first_event()_ returns a pointer to the first event. NULL is +returned in case of error, or in case there are no events. + +The _tep_get_events_count()_ returns the number of the events. 0 is +returned in case of error, or in case there are no events. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +int i,count = tep_get_events_count(tep); +struct tep_event *event, *events = tep_get_first_event(tep); + +if (events == NULL) { + /* There are no events */ +} else { + for (i = 0; i < count; i++) { + event = (events+i); + /* process events[i] */ + } + + /* Get the last event */ + event = tep_get_event(tep, count-1); +} +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-event_list.txt b/tools/lib/traceevent/Documentation/libtraceevent-event_list.txt new file mode 100644 index 000000000..fba350e5a --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-event_list.txt @@ -0,0 +1,122 @@ +libtraceevent(3) +================ + +NAME +---- +tep_list_events, tep_list_events_copy - +Get list of events, sorted by given criteria. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_event_sort_type* { + _TEP_EVENT_SORT_ID_, + _TEP_EVENT_SORT_NAME_, + _TEP_EVENT_SORT_SYSTEM_, +}; + +struct tep_event pass:[*]pass:[*]*tep_list_events*(struct tep_handle pass:[*]_tep_, enum tep_event_sort_type _sort_type_); +struct tep_event pass:[*]pass:[*]*tep_list_events_copy*(struct tep_handle pass:[*]_tep_, enum tep_event_sort_type _sort_type_); +-- + +DESCRIPTION +----------- +The _tep_list_events()_ function returns an array of pointers to the events, +sorted by the _sort_type_ criteria. The last element of the array is NULL. +The returned memory must not be freed, it is managed by the library. +The function is not thread safe. The _tep_ argument is trace event parser +context. The _sort_type_ argument is the required sort criteria: +[verse] +-- + _TEP_EVENT_SORT_ID_ - sort by the event ID. + _TEP_EVENT_SORT_NAME_ - sort by the event (name, system, id) triplet. + _TEP_EVENT_SORT_SYSTEM_ - sort by the event (system, name, id) triplet. +-- + +The _tep_list_events_copy()_ is a thread safe version of _tep_list_events()_. +It has the same behavior, but the returned array is allocated internally and +must be freed by the caller. Note that the content of the array must not be +freed (see the EXAMPLE below). + +RETURN VALUE +------------ +The _tep_list_events()_ function returns an array of pointers to events. +In case of an error, NULL is returned. The returned array must not be freed, +it is managed by the library. + +The _tep_list_events_copy()_ function returns an array of pointers to events. +In case of an error, NULL is returned. The returned array must be freed by +the caller. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +int i; +struct tep_event_format **events; + +i=0; +events = tep_list_events(tep, TEP_EVENT_SORT_ID); +if (events == NULL) { + /* Failed to get the events, sorted by ID */ +} else { + while(events[i]) { + /* walk through the list of the events, sorted by ID */ + i++; + } +} + +i=0; +events = tep_list_events_copy(tep, TEP_EVENT_SORT_NAME); +if (events == NULL) { + /* Failed to get the events, sorted by name */ +} else { + while(events[i]) { + /* walk through the list of the events, sorted by name */ + i++; + } + free(events); +} + +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-event_print.txt b/tools/lib/traceevent/Documentation/libtraceevent-event_print.txt new file mode 100644 index 000000000..2c6a61811 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-event_print.txt @@ -0,0 +1,130 @@ +libtraceevent(3) +================ + +NAME +---- +tep_print_event - Writes event information into a trace sequence. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* +*#include <trace-seq.h>* + +void *tep_print_event*(struct tep_handle pass:[*]_tep_, struct trace_seqpass:[*]_s_, struct tep_record pass:[*]_record_, const char pass:[*]_fmt_, _..._) +-- + +DESCRIPTION +----------- + +The _tep_print_event()_ function parses the event information of the given +_record_ and writes it into the trace sequence _s_, according to the format +string _fmt_. The desired information is specified after the format string. +The _fmt_ is printf-like format string, following arguments are supported: +[verse] +-- + TEP_PRINT_PID, "%d" - PID of the event. + TEP_PRINT_CPU, "%d" - Event CPU. + TEP_PRINT_COMM, "%s" - Event command string. + TEP_PRINT_NAME, "%s" - Event name. + TEP_PRINT_LATENCY, "%s" - Latency of the event. It prints 4 or more + fields - interrupt state, scheduling state, + current context, and preemption count. + Field 1 is the interrupt enabled state: + d : Interrupts are disabled + . : Interrupts are enabled + X : The architecture does not support this + information + Field 2 is the "need resched" state. + N : The task is set to call the scheduler when + possible, as another higher priority task + may need to be scheduled in. + . : The task is not set to call the scheduler. + Field 3 is the context state. + . : Normal context + s : Soft interrupt context + h : Hard interrupt context + H : Hard interrupt context which triggered + during soft interrupt context. + z : NMI context + Z : NMI context which triggered during hard + interrupt context + Field 4 is the preemption count. + . : The preempt count is zero. + On preemptible kernels (where the task can be scheduled + out in arbitrary locations while in kernel context), the + preempt count, when non zero, will prevent the kernel + from scheduling out the current task. The preempt count + number is displayed when it is not zero. + Depending on the kernel, it may show other fields + (lock depth, or migration disabled, which are unique to + specialized kernels). + TEP_PRINT_TIME, %d - event time stamp. A divisor and precision can be + specified as part of this format string: + "%precision.divisord". Example: + "%3.1000d" - divide the time by 1000 and print the first + 3 digits before the dot. Thus, the time stamp + "123456000" will be printed as "123.456" + TEP_PRINT_INFO, "%s" - event information. + TEP_PRINT_INFO_RAW, "%s" - event information, in raw format. + +-- +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct trace_seq seq; +trace_seq_init(&seq); +struct tep_handle *tep = tep_alloc(); +... +void print_my_event(struct tep_record *record) +{ + trace_seq_reset(&seq); + tep_print_event(tep, s, record, "%16s-%-5d [%03d] %s %6.1000d %s %s", + TEP_PRINT_COMM, TEP_PRINT_PID, TEP_PRINT_CPU, + TEP_PRINT_LATENCY, TEP_PRINT_TIME, TEP_PRINT_NAME, + TEP_PRINT_INFO); +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences related APIs. + Trace sequences are used to allow a function to call several other functions + to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-field_find.txt b/tools/lib/traceevent/Documentation/libtraceevent-field_find.txt new file mode 100644 index 000000000..0896af5b9 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-field_find.txt @@ -0,0 +1,118 @@ +libtraceevent(3) +================ + +NAME +---- +tep_find_common_field, tep_find_field, tep_find_any_field - +Search for a field in an event. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_format_field pass:[*]*tep_find_common_field*(struct tep_event pass:[*]_event_, const char pass:[*]_name_); +struct tep_format_field pass:[*]*tep_find_field*(struct tep_event_ormat pass:[*]_event_, const char pass:[*]_name_); +struct tep_format_field pass:[*]*tep_find_any_field*(struct tep_event pass:[*]_event_, const char pass:[*]_name_); +-- + +DESCRIPTION +----------- +These functions search for a field with given name in an event. The field +returned can be used to find the field content from within a data record. + +The _tep_find_common_field()_ function searches for a common field with _name_ +in the _event_. + +The _tep_find_field()_ function searches for an event specific field with +_name_ in the _event_. + +The _tep_find_any_field()_ function searches for any field with _name_ in the +_event_. + +RETURN VALUE +------------ +The _tep_find_common_field(), _tep_find_field()_ and _tep_find_any_field()_ +functions return a pointer to the found field, or NULL in case there is no field +with the requested name. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +void get_htimer_info(struct tep_handle *tep, struct tep_record *record) +{ + struct tep_format_field *field; + struct tep_event *event; + long long softexpires; + int mode; + int pid; + + event = tep_find_event_by_name(tep, "timer", "hrtimer_start"); + + field = tep_find_common_field(event, "common_pid"); + if (field == NULL) { + /* Cannot find "common_pid" field in the event */ + } else { + /* Get pid from the data record */ + pid = tep_read_number(tep, record->data + field->offset, + field->size); + } + + field = tep_find_field(event, "softexpires"); + if (field == NULL) { + /* Cannot find "softexpires" event specific field in the event */ + } else { + /* Get softexpires parameter from the data record */ + softexpires = tep_read_number(tep, record->data + field->offset, + field->size); + } + + field = tep_find_any_field(event, "mode"); + if (field == NULL) { + /* Cannot find "mode" field in the event */ + } else + { + /* Get mode parameter from the data record */ + mode = tep_read_number(tep, record->data + field->offset, + field->size); + } +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-field_get_val.txt b/tools/lib/traceevent/Documentation/libtraceevent-field_get_val.txt new file mode 100644 index 000000000..6324f0d48 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-field_get_val.txt @@ -0,0 +1,122 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_any_field_val, tep_get_common_field_val, tep_get_field_val, +tep_get_field_raw - Get value of a field. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* +*#include <trace-seq.h>* + +int *tep_get_any_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); +int *tep_get_common_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); +int *tep_get_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); +void pass:[*]*tep_get_field_raw*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int pass:[*]_len_, int _err_); +-- + +DESCRIPTION +----------- +These functions can be used to find a field and retrieve its value. + +The _tep_get_any_field_val()_ function searches in the _record_ for a field +with _name_, part of the _event_. If the field is found, its value is stored in +_val_. If there is an error and _err_ is not zero, then an error string is +written into _s_. + +The _tep_get_common_field_val()_ function does the same as +_tep_get_any_field_val()_, but searches only in the common fields. This works +for any event as all events include the common fields. + +The _tep_get_field_val()_ function does the same as _tep_get_any_field_val()_, +but searches only in the event specific fields. + +The _tep_get_field_raw()_ function searches in the _record_ for a field with +_name_, part of the _event_. If the field is found, a pointer to where the field +exists in the record's raw data is returned. The size of the data is stored in +_len_. If there is an error and _err_ is not zero, then an error string is +written into _s_. + +RETURN VALUE +------------ +The _tep_get_any_field_val()_, _tep_get_common_field_val()_ and +_tep_get_field_val()_ functions return 0 on success, or -1 in case of an error. + +The _tep_get_field_raw()_ function returns a pointer to field's raw data, and +places the length of this data in _len_. In case of an error NULL is returned. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct tep_handle *tep = tep_alloc(); +... +struct tep_event *event = tep_find_event_by_name(tep, "kvm", "kvm_exit"); +... +void process_record(struct tep_record *record) +{ + int len; + char *comm; + struct tep_event_format *event; + unsigned long long val; + + event = tep_find_event_by_record(pevent, record); + if (event != NULL) { + if (tep_get_common_field_val(NULL, event, "common_type", + record, &val, 0) == 0) { + /* Got the value of common type field */ + } + if (tep_get_field_val(NULL, event, "pid", record, &val, 0) == 0) { + /* Got the value of pid specific field */ + } + comm = tep_get_field_raw(NULL, event, "comm", record, &len, 0); + if (comm != NULL) { + /* Got a pointer to the comm event specific field */ + } + } +} +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences + related APIs. Trace sequences are used to allow a function to call + several other functions to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-field_print.txt b/tools/lib/traceevent/Documentation/libtraceevent-field_print.txt new file mode 100644 index 000000000..9a9df98ac --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-field_print.txt @@ -0,0 +1,126 @@ +libtraceevent(3) +================ + +NAME +---- +tep_print_field, tep_print_fields, tep_print_num_field, tep_print_func_field - +Print the field content. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* +*#include <trace-seq.h>* + +void *tep_print_field*(struct trace_seq pass:[*]_s_, void pass:[*]_data_, struct tep_format_field pass:[*]_field_); +void *tep_print_fields*(struct trace_seq pass:[*]_s_, void pass:[*]_data_, int _size_, struct tep_event pass:[*]_event_); +int *tep_print_num_field*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int _err_); +int *tep_print_func_field*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int _err_); +-- + +DESCRIPTION +----------- +These functions print recorded field's data, according to the field's type. + +The _tep_print_field()_ function extracts from the recorded raw _data_ value of +the _field_ and prints it into _s_, according to the field type. + +The _tep_print_fields()_ prints each field name followed by the record's field +value according to the field's type: +[verse] +-- +"field1_name=field1_value field2_name=field2_value ..." +-- +It iterates all fields of the _event_, and calls _tep_print_field()_ for each of +them. + +The _tep_print_num_field()_ function prints a numeric field with given format +string. A search is performed in the _event_ for a field with _name_. If such +field is found, its value is extracted from the _record_ and is printed in the +_s_, according to the given format string _fmt_. If the argument _err_ is +non-zero, and an error occures - it is printed in the _s_. + +The _tep_print_func_field()_ function prints a function field with given format +string. A search is performed in the _event_ for a field with _name_. If such +field is found, its value is extracted from the _record_. The value is assumed +to be a function address, and a search is perform to find the name of this +function. The function name (if found) and its address are printed in the _s_, +according to the given format string _fmt_. If the argument _err_ is non-zero, +and an error occures - it is printed in _s_. + +RETURN VALUE +------------ +The _tep_print_num_field()_ and _tep_print_func_field()_ functions return 1 +on success, -1 in case of an error or 0 if the print buffer _s_ is full. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct tep_handle *tep = tep_alloc(); +... +struct trace_seq seq; +trace_seq_init(&seq); +struct tep_event *event = tep_find_event_by_name(tep, "timer", "hrtimer_start"); +... +void process_record(struct tep_record *record) +{ + struct tep_format_field *field_pid = tep_find_common_field(event, "common_pid"); + + trace_seq_reset(&seq); + + /* Print the value of "common_pid" */ + tep_print_field(&seq, record->data, field_pid); + + /* Print all fields of the "hrtimer_start" event */ + tep_print_fields(&seq, record->data, record->size, event); + + /* Print the value of "expires" field with custom format string */ + tep_print_num_field(&seq, " timer expires in %llu ", event, "expires", record, 0); + + /* Print the address and the name of "function" field with custom format string */ + tep_print_func_field(&seq, " timer function is %s ", event, "function", record, 0); + } + ... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences related APIs. + Trace sequences are used to allow a function to call several other functions + to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-field_read.txt b/tools/lib/traceevent/Documentation/libtraceevent-field_read.txt new file mode 100644 index 000000000..64e9e25d3 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-field_read.txt @@ -0,0 +1,81 @@ +libtraceevent(3) +================ + +NAME +---- +tep_read_number_field - Reads a number from raw data. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_read_number_field*(struct tep_format_field pass:[*]_field_, const void pass:[*]_data_, unsigned long long pass:[*]_value_); +-- + +DESCRIPTION +----------- +The _tep_read_number_field()_ function reads the value of the _field_ from the +raw _data_ and stores it in the _value_. The function sets the _value_ according +to the endianness of the raw data and the current machine and stores it in +_value_. + +RETURN VALUE +------------ +The _tep_read_number_field()_ function retunrs 0 in case of success, or -1 in +case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +struct tep_event *event = tep_find_event_by_name(tep, "timer", "hrtimer_start"); +... +void process_record(struct tep_record *record) +{ + unsigned long long pid; + struct tep_format_field *field_pid = tep_find_common_field(event, "common_pid"); + + if (tep_read_number_field(field_pid, record->data, &pid) != 0) { + /* Failed to get "common_pid" value */ + } +} +... +-- +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-fields.txt b/tools/lib/traceevent/Documentation/libtraceevent-fields.txt new file mode 100644 index 000000000..1ccb531d5 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-fields.txt @@ -0,0 +1,105 @@ +libtraceevent(3) +================ + +NAME +---- +tep_event_common_fields, tep_event_fields - Get a list of fields for an event. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_format_field pass:[*]pass:[*]*tep_event_common_fields*(struct tep_event pass:[*]_event_); +struct tep_format_field pass:[*]pass:[*]*tep_event_fields*(struct tep_event pass:[*]_event_); +-- + +DESCRIPTION +----------- +The _tep_event_common_fields()_ function returns an array of pointers to common +fields for the _event_. The array is allocated in the function and must be freed +by free(). The last element of the array is NULL. + +The _tep_event_fields()_ function returns an array of pointers to event specific +fields for the _event_. The array is allocated in the function and must be freed +by free(). The last element of the array is NULL. + +RETURN VALUE +------------ +Both _tep_event_common_fields()_ and _tep_event_fields()_ functions return +an array of pointers to tep_format_field structures in case of success, or +NULL in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +int i; +struct tep_format_field **fields; +struct tep_event *event = tep_find_event_by_name(tep, "kvm", "kvm_exit"); +if (event != NULL) { + fields = tep_event_common_fields(event); + if (fields != NULL) { + i = 0; + while (fields[i]) { + /* + walk through the list of the common fields + of the kvm_exit event + */ + i++; + } + free(fields); + } + fields = tep_event_fields(event); + if (fields != NULL) { + i = 0; + while (fields[i]) { + /* + walk through the list of the event specific + fields of the kvm_exit event + */ + i++; + } + free(fields); + } +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-file_endian.txt b/tools/lib/traceevent/Documentation/libtraceevent-file_endian.txt new file mode 100644 index 000000000..f401ad311 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-file_endian.txt @@ -0,0 +1,91 @@ +libtraceevent(3) +================ + +NAME +---- +tep_is_file_bigendian, tep_set_file_bigendian - Get / set the endianness of the +raw data being accessed by the tep handler. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_endian* { + TEP_LITTLE_ENDIAN = 0, + TEP_BIG_ENDIAN +}; + +bool *tep_is_file_bigendian*(struct tep_handle pass:[*]_tep_); +void *tep_set_file_bigendian*(struct tep_handle pass:[*]_tep_, enum tep_endian _endian_); + +-- +DESCRIPTION +----------- +The _tep_is_file_bigendian()_ function gets the endianness of the raw data, +being accessed by the tep handler. The _tep_ argument is trace event parser +context. + +The _tep_set_file_bigendian()_ function sets the endianness of raw data being +accessed by the tep handler. The _tep_ argument is trace event parser context. +[verse] +-- +The _endian_ argument is the endianness: + _TEP_LITTLE_ENDIAN_ - the raw data is in little endian format, + _TEP_BIG_ENDIAN_ - the raw data is in big endian format. +-- +RETURN VALUE +------------ +The _tep_is_file_bigendian()_ function returns true if the data is in bigendian +format, false otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... + tep_set_file_bigendian(tep, TEP_LITTLE_ENDIAN); +... + if (tep_is_file_bigendian(tep)) { + /* The raw data is in big endian */ + } else { + /* The raw data is in little endian */ + } +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-filter.txt b/tools/lib/traceevent/Documentation/libtraceevent-filter.txt new file mode 100644 index 000000000..4a9962d8c --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-filter.txt @@ -0,0 +1,209 @@ +libtraceevent(3) +================ + +NAME +---- +tep_filter_alloc, tep_filter_free, tep_filter_reset, tep_filter_make_string, +tep_filter_copy, tep_filter_compare, tep_filter_match, tep_event_filtered, +tep_filter_remove_event, tep_filter_strerror, tep_filter_add_filter_str - +Event filter related APIs. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_event_filter pass:[*]*tep_filter_alloc*(struct tep_handle pass:[*]_tep_); +void *tep_filter_free*(struct tep_event_filter pass:[*]_filter_); +void *tep_filter_reset*(struct tep_event_filter pass:[*]_filter_); +enum tep_errno *tep_filter_add_filter_str*(struct tep_event_filter pass:[*]_filter_, const char pass:[*]_filter_str_); +int *tep_event_filtered*(struct tep_event_filter pass:[*]_filter_, int _event_id_); +int *tep_filter_remove_event*(struct tep_event_filter pass:[*]_filter_, int _event_id_); +enum tep_errno *tep_filter_match*(struct tep_event_filter pass:[*]_filter_, struct tep_record pass:[*]_record_); +int *tep_filter_copy*(struct tep_event_filter pass:[*]_dest_, struct tep_event_filter pass:[*]_source_); +int *tep_filter_compare*(struct tep_event_filter pass:[*]_filter1_, struct tep_event_filter pass:[*]_filter2_); +char pass:[*]*tep_filter_make_string*(struct tep_event_filter pass:[*]_filter_, int _event_id_); +int *tep_filter_strerror*(struct tep_event_filter pass:[*]_filter_, enum tep_errno _err_, char pass:[*]buf, size_t _buflen_); +-- + +DESCRIPTION +----------- +Filters can be attached to traced events. They can be used to filter out various +events when outputting them. Each event can be filtered based on its parameters, +described in the event's format file. This set of functions can be used to +create, delete, modify and attach event filters. + +The _tep_filter_alloc()_ function creates a new event filter. The _tep_ argument +is the trace event parser context. + +The _tep_filter_free()_ function frees an event filter and all resources that it +had used. + +The _tep_filter_reset()_ function removes all rules from an event filter and +resets it. + +The _tep_filter_add_filter_str()_ function adds a new rule to the _filter_. The +_filter_str_ argument is the filter string, that contains the rule. + +The _tep_event_filtered()_ function checks if the event with _event_id_ has +_filter_. + +The _tep_filter_remove_event()_ function removes a _filter_ for an event with +_event_id_. + +The _tep_filter_match()_ function tests if a _record_ matches given _filter_. + +The _tep_filter_copy()_ function copies a _source_ filter into a _dest_ filter. + +The _tep_filter_compare()_ function compares two filers - _filter1_ and _filter2_. + +The _tep_filter_make_string()_ function constructs a string, displaying +the _filter_ contents for given _event_id_. + +The _tep_filter_strerror()_ function copies the _filter_ error buffer into the +given _buf_ with the size _buflen_. If the error buffer is empty, in the _buf_ +is copied a string, describing the error _err_. + +RETURN VALUE +------------ +The _tep_filter_alloc()_ function returns a pointer to the newly created event +filter, or NULL in case of an error. + +The _tep_filter_add_filter_str()_ function returns 0 if the rule was +successfully added or a negative error code. Use _tep_filter_strerror()_ to see +actual error message in case of an error. + +The _tep_event_filtered()_ function returns 1 if the filter is found for given +event, or 0 otherwise. + +The _tep_filter_remove_event()_ function returns 1 if the vent was removed, or +0 if the event was not found. + +The _tep_filter_match()_ function returns _tep_errno_, according to the result: +[verse] +-- +_pass:[TEP_ERRNO__FILTER_MATCH]_ - filter found for event, the record matches. +_pass:[TEP_ERRNO__FILTER_MISS]_ - filter found for event, the record does not match. +_pass:[TEP_ERRNO__FILTER_NOT_FOUND]_ - no filter found for record's event. +_pass:[TEP_ERRNO__NO_FILTER]_ - no rules in the filter. +-- +or any other _tep_errno_, if an error occurred during the test. + +The _tep_filter_copy()_ function returns 0 on success or -1 if not all rules + were copied. + +The _tep_filter_compare()_ function returns 1 if the two filters hold the same +content, or 0 if they do not. + +The _tep_filter_make_string()_ function returns a string, which must be freed +with free(), or NULL in case of an error. + +The _tep_filter_strerror()_ function returns 0 if message was filled +successfully, or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +char errstr[200]; +int ret; + +struct tep_event_filter *filter = tep_filter_alloc(tep); +struct tep_event_filter *filter1 = tep_filter_alloc(tep); +ret = tep_filter_add_filter_str(filter, "sched/sched_wakeup:target_cpu==1"); +if(ret < 0) { + tep_filter_strerror(filter, ret, errstr, sizeof(errstr)); + /* Failed to add a new rule to the filter, the error string is in errstr */ +} +if (tep_filter_copy(filter1, filter) != 0) { + /* Failed to copy filter in filter1 */ +} +... +if (tep_filter_compare(filter, filter1) != 1) { + /* Both filters are different */ +} +... +void process_record(struct tep_handle *tep, struct tep_record *record) +{ + struct tep_event *event; + char *fstring; + + event = tep_find_event_by_record(tep, record); + + if (tep_event_filtered(filter, event->id) == 1) { + /* The event has filter */ + fstring = tep_filter_make_string(filter, event->id); + if (fstring != NULL) { + /* The filter for the event is in fstring */ + free(fstring); + } + } + + switch (tep_filter_match(filter, record)) { + case TEP_ERRNO__FILTER_MATCH: + /* The filter matches the record */ + break; + case TEP_ERRNO__FILTER_MISS: + /* The filter does not match the record */ + break; + case TEP_ERRNO__FILTER_NOT_FOUND: + /* No filter found for record's event */ + break; + case TEP_ERRNO__NO_FILTER: + /* There are no rules in the filter */ + break + default: + /* An error occurred during the test */ + break; + } + + if (tep_filter_remove_event(filter, event->id) == 1) { + /* The event was removed from the filter */ + } +} + +... +tep_filter_reset(filter); +... +tep_filter_free(filter); +tep_filter_free(filter1); +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-func_apis.txt b/tools/lib/traceevent/Documentation/libtraceevent-func_apis.txt new file mode 100644 index 000000000..f6aca0df2 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-func_apis.txt @@ -0,0 +1,183 @@ +libtraceevent(3) +================ + +NAME +---- +tep_find_function, tep_find_function_address, tep_set_function_resolver, +tep_reset_function_resolver, tep_register_function, tep_register_print_string - +function related tep APIs + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +typedef char pass:[*](*tep_func_resolver_t*)(void pass:[*]_priv_, unsigned long long pass:[*]_addrp_, char pass:[**]_modp_); +int *tep_set_function_resolver*(struct tep_handle pass:[*]_tep_, tep_func_resolver_t pass:[*]_func_, void pass:[*]_priv_); +void *tep_reset_function_resolver*(struct tep_handle pass:[*]_tep_); +const char pass:[*]*tep_find_function*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); +unsigned long long *tep_find_function_address*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); +int *tep_register_function*(struct tep_handle pass:[*]_tep_, char pass:[*]_name_, unsigned long long _addr_, char pass:[*]_mod_); +int *tep_register_print_string*(struct tep_handle pass:[*]_tep_, const char pass:[*]_fmt_, unsigned long long _addr_); +-- + +DESCRIPTION +----------- +Some tools may have already a way to resolve the kernel functions. These APIs +allow them to keep using it instead of duplicating all the entries inside. + +The _tep_func_resolver_t_ type is the prototype of the alternative kernel +functions resolver. This function receives a pointer to its custom context +(set with the _tep_set_function_resolver()_ call ) and the address of a kernel +function, which has to be resolved. In case of success, it should return +the name of the function and its module (if any) in _modp_. + +The _tep_set_function_resolver()_ function registers _func_ as an alternative +kernel functions resolver. The _tep_ argument is trace event parser context. +The _priv_ argument is a custom context of the _func_ function. The function +resolver is used by the APIs _tep_find_function()_, +_tep_find_function_address()_, and _tep_print_func_field()_ to resolve +a function address to a function name. + +The _tep_reset_function_resolver()_ function resets the kernel functions +resolver to the default function. The _tep_ argument is trace event parser +context. + + +These APIs can be used to find function name and start address, by given +address. The given address does not have to be exact, it will select +the function that would contain it. + +The _tep_find_function()_ function returns the function name, which contains the +given address _addr_. The _tep_ argument is the trace event parser context. + +The _tep_find_function_address()_ function returns the function start address, +by given address _addr_. The _addr_ does not have to be exact, it will select +the function that would contain it. The _tep_ argument is the trace event +parser context. + +The _tep_register_function()_ function registers a function name mapped to an +address and (optional) module. This mapping is used in case the function tracer +or events have "%pS" parameter in its format string. It is common to pass in +the kallsyms function names with their corresponding addresses with this +function. The _tep_ argument is the trace event parser context. The _name_ is +the name of the function, the string is copied internally. The _addr_ is the +start address of the function. The _mod_ is the kernel module the function may +be in (NULL for none). + +The _tep_register_print_string()_ function registers a string by the address +it was stored in the kernel. Some strings internal to the kernel with static +address are passed to certain events. The "%s" in the event's format field +which has an address needs to know what string would be at that address. The +tep_register_print_string() supplies the parsing with the mapping between kernel +addresses and those strings. The _tep_ argument is the trace event parser +context. The _fmt_ is the string to register, it is copied internally. +The _addr_ is the address the string was located at. + + +RETURN VALUE +------------ +The _tep_set_function_resolver()_ function returns 0 in case of success, or -1 +in case of an error. + +The _tep_find_function()_ function returns the function name, or NULL in case +it cannot be found. + +The _tep_find_function_address()_ function returns the function start address, +or 0 in case it cannot be found. + +The _tep_register_function()_ function returns 0 in case of success. In case of +an error -1 is returned, and errno is set to the appropriate error number. + +The _tep_register_print_string()_ function returns 0 in case of success. In case +of an error -1 is returned, and errno is set to the appropriate error number. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +char *my_resolve_kernel_addr(void *context, + unsigned long long *addrp, char **modp) +{ + struct db *function_database = context; + struct symbol *sym = sql_lookup(function_database, *addrp); + + if (!sym) + return NULL; + + *modp = sym->module_name; + return sym->name; +} + +void show_function( unsigned long long addr) +{ + unsigned long long fstart; + const char *fname; + + if (tep_set_function_resolver(tep, my_resolve_kernel_addr, + function_database) != 0) { + /* failed to register my_resolve_kernel_addr */ + } + + /* These APIs use my_resolve_kernel_addr() to resolve the addr */ + fname = tep_find_function(tep, addr); + fstart = tep_find_function_address(tep, addr); + + /* + addr is in function named fname, starting at fstart address, + at offset (addr - fstart) + */ + + tep_reset_function_resolver(tep); + +} +... + if (tep_register_function(tep, "kvm_exit", + (unsigned long long) 0x12345678, "kvm") != 0) { + /* Failed to register kvm_exit address mapping */ + } +... + if (tep_register_print_string(tep, "print string", + (unsigned long long) 0x87654321, NULL) != 0) { + /* Failed to register "print string" address mapping */ + } +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-func_find.txt b/tools/lib/traceevent/Documentation/libtraceevent-func_find.txt new file mode 100644 index 000000000..04840e244 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-func_find.txt @@ -0,0 +1,88 @@ +libtraceevent(3) +================ + +NAME +---- +tep_find_function,tep_find_function_address - Find function name / start address. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +const char pass:[*]*tep_find_function*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); +unsigned long long *tep_find_function_address*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); +-- + +DESCRIPTION +----------- +These functions can be used to find function name and start address, by given +address. The given address does not have to be exact, it will select the function +that would contain it. + +The _tep_find_function()_ function returns the function name, which contains the +given address _addr_. The _tep_ argument is the trace event parser context. + +The _tep_find_function_address()_ function returns the function start address, +by given address _addr_. The _addr_ does not have to be exact, it will select the +function that would contain it. The _tep_ argument is the trace event parser context. + +RETURN VALUE +------------ +The _tep_find_function()_ function returns the function name, or NULL in case +it cannot be found. + +The _tep_find_function_address()_ function returns the function start address, +or 0 in case it cannot be found. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +void show_function( unsigned long long addr) +{ + const char *fname = tep_find_function(tep, addr); + unsigned long long fstart = tep_find_function_address(tep, addr); + + /* addr is in function named fname, starting at fstart address, at offset (addr - fstart) */ +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-handle.txt b/tools/lib/traceevent/Documentation/libtraceevent-handle.txt new file mode 100644 index 000000000..45b20172e --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-handle.txt @@ -0,0 +1,101 @@ +libtraceevent(3) +================ + +NAME +---- +tep_alloc, tep_free,tep_ref, tep_unref,tep_get_ref - Create, destroy, manage +references of trace event parser context. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_handle pass:[*]*tep_alloc*(void); +void *tep_free*(struct tep_handle pass:[*]_tep_); +void *tep_ref*(struct tep_handle pass:[*]_tep_); +void *tep_unref*(struct tep_handle pass:[*]_tep_); +int *tep_get_ref*(struct tep_handle pass:[*]_tep_); +-- + +DESCRIPTION +----------- +These are the main functions to create and destroy tep_handle - the main +structure, representing the trace event parser context. This context is used as +the input parameter of most library APIs. + +The _tep_alloc()_ function allocates and initializes the tep context. + +The _tep_free()_ function will decrement the reference of the _tep_ handler. +When there is no more references, then it will free the handler, as well +as clean up all its resources that it had used. The argument _tep_ is +the pointer to the trace event parser context. + +The _tep_ref()_ function adds a reference to the _tep_ handler. + +The _tep_unref()_ function removes a reference from the _tep_ handler. When +the last reference is removed, the _tep_ is destroyed, and all resources that +it had used are cleaned up. + +The _tep_ref_get()_ functions gets the current references of the _tep_ handler. + +RETURN VALUE +------------ +_tep_alloc()_ returns a pointer to a newly created tep_handle structure. +NULL is returned in case there is not enough free memory to allocate it. + +_tep_ref_get()_ returns the current references of _tep_. +If _tep_ is NULL, 0 is returned. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> + +... +struct tep_handle *tep = tep_alloc(); +... +int ref = tep_get_ref(tep); +tep_ref(tep); +if ( (ref+1) != tep_get_ref(tep)) { + /* Something wrong happened, the counter is not incremented by 1 */ +} +tep_unref(tep); +... +tep_free(tep); +... +-- +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-header_page.txt b/tools/lib/traceevent/Documentation/libtraceevent-header_page.txt new file mode 100644 index 000000000..615d117dc --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-header_page.txt @@ -0,0 +1,102 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_header_page_size, tep_get_header_timestamp_size, tep_is_old_format - +Get the data stored in the header page, in kernel context. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_get_header_page_size*(struct tep_handle pass:[*]_tep_); +int *tep_get_header_timestamp_size*(struct tep_handle pass:[*]_tep_); +bool *tep_is_old_format*(struct tep_handle pass:[*]_tep_); +-- +DESCRIPTION +----------- +These functions retrieve information from kernel context, stored in tracefs +events/header_page. Old kernels do not have header page info, so default values +from user space context are used. + +The _tep_get_header_page_size()_ function returns the size of a long integer, +in kernel context. The _tep_ argument is trace event parser context. +This information is retrieved from tracefs events/header_page, "commit" field. + +The _tep_get_header_timestamp_size()_ function returns the size of timestamps, +in kernel context. The _tep_ argument is trace event parser context. This +information is retrieved from tracefs events/header_page, "timestamp" field. + +The _tep_is_old_format()_ function returns true if the kernel predates +the addition of events/header_page, otherwise it returns false. + +RETURN VALUE +------------ +The _tep_get_header_page_size()_ function returns the size of a long integer, +in bytes. + +The _tep_get_header_timestamp_size()_ function returns the size of timestamps, +in bytes. + +The _tep_is_old_format()_ function returns true, if an old kernel is used to +generate the tracing data, which has no event/header_page. If the kernel is new, +or _tep_ is NULL, false is returned. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... + int longsize; + int timesize; + bool old; + + longsize = tep_get_header_page_size(tep); + timesize = tep_get_header_timestamp_size(tep); + old = tep_is_old_format(tep); + + printf ("%s kernel is used to generate the tracing data.\n", + old?"Old":"New"); + printf("The size of a long integer is %d bytes.\n", longsize); + printf("The timestamps size is %d bytes.\n", timesize); +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-host_endian.txt b/tools/lib/traceevent/Documentation/libtraceevent-host_endian.txt new file mode 100644 index 000000000..d5d375eb8 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-host_endian.txt @@ -0,0 +1,104 @@ +libtraceevent(3) +================ + +NAME +---- +tep_is_bigendian, tep_is_local_bigendian, tep_set_local_bigendian - Get / set +the endianness of the local machine. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_endian* { + TEP_LITTLE_ENDIAN = 0, + TEP_BIG_ENDIAN +}; + +int *tep_is_bigendian*(void); +bool *tep_is_local_bigendian*(struct tep_handle pass:[*]_tep_); +void *tep_set_local_bigendian*(struct tep_handle pass:[*]_tep_, enum tep_endian _endian_); +-- + +DESCRIPTION +----------- + +The _tep_is_bigendian()_ gets the endianness of the machine, executing +the function. + +The _tep_is_local_bigendian()_ function gets the endianness of the local +machine, saved in the _tep_ handler. The _tep_ argument is the trace event +parser context. This API is a bit faster than _tep_is_bigendian()_, as it +returns cached endianness of the local machine instead of checking it each time. + +The _tep_set_local_bigendian()_ function sets the endianness of the local +machine in the _tep_ handler. The _tep_ argument is trace event parser context. +The _endian_ argument is the endianness: +[verse] +-- + _TEP_LITTLE_ENDIAN_ - the machine is little endian, + _TEP_BIG_ENDIAN_ - the machine is big endian. +-- + +RETURN VALUE +------------ +The _tep_is_bigendian()_ function returns non zero if the endianness of the +machine, executing the code, is big endian and zero otherwise. + +The _tep_is_local_bigendian()_ function returns true, if the endianness of the +local machine, saved in the _tep_ handler, is big endian, or false otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... + if (tep_is_bigendian()) + tep_set_local_bigendian(tep, TEP_BIG_ENDIAN); + else + tep_set_local_bigendian(tep, TEP_LITTLE_ENDIAN); +... + if (tep_is_local_bigendian(tep)) + printf("This machine you are running on is bigendian\n"); + else + printf("This machine you are running on is little endian\n"); + +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-long_size.txt b/tools/lib/traceevent/Documentation/libtraceevent-long_size.txt new file mode 100644 index 000000000..01d78ea25 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-long_size.txt @@ -0,0 +1,78 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_long_size, tep_set_long_size - Get / set the size of a long integer on +the machine, where the trace is generated, in bytes + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_get_long_size*(strucqt tep_handle pass:[*]_tep_); +void *tep_set_long_size*(struct tep_handle pass:[*]_tep_, int _long_size_); +-- + +DESCRIPTION +----------- +The _tep_get_long_size()_ function returns the size of a long integer on the machine, +where the trace is generated. The _tep_ argument is trace event parser context. + +The _tep_set_long_size()_ function sets the size of a long integer on the machine, +where the trace is generated. The _tep_ argument is trace event parser context. +The _long_size_ is the size of a long integer, in bytes. + +RETURN VALUE +------------ +The _tep_get_long_size()_ function returns the size of a long integer on the machine, +where the trace is generated, in bytes. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +tep_set_long_size(tep, 4); +... +int long_size = tep_get_long_size(tep); +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-page_size.txt b/tools/lib/traceevent/Documentation/libtraceevent-page_size.txt new file mode 100644 index 000000000..452c0cfa1 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-page_size.txt @@ -0,0 +1,82 @@ +libtraceevent(3) +================ + +NAME +---- +tep_get_page_size, tep_set_page_size - Get / set the size of a memory page on +the machine, where the trace is generated + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_get_page_size*(struct tep_handle pass:[*]_tep_); +void *tep_set_page_size*(struct tep_handle pass:[*]_tep_, int _page_size_); +-- + +DESCRIPTION +----------- +The _tep_get_page_size()_ function returns the size of a memory page on +the machine, where the trace is generated. The _tep_ argument is trace +event parser context. + +The _tep_set_page_size()_ function stores in the _tep_ context the size of a +memory page on the machine, where the trace is generated. +The _tep_ argument is trace event parser context. +The _page_size_ argument is the size of a memory page, in bytes. + +RETURN VALUE +------------ +The _tep_get_page_size()_ function returns size of the memory page, in bytes. + +EXAMPLE +------- +[source,c] +-- +#include <unistd.h> +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... + int page_size = getpagesize(); + + tep_set_page_size(tep, page_size); + + printf("The page size for this machine is %d\n", tep_get_page_size(tep)); + +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-parse_event.txt b/tools/lib/traceevent/Documentation/libtraceevent-parse_event.txt new file mode 100644 index 000000000..f248114ca --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-parse_event.txt @@ -0,0 +1,90 @@ +libtraceevent(3) +================ + +NAME +---- +tep_parse_event, tep_parse_format - Parse the event format information + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum tep_errno *tep_parse_event*(struct tep_handle pass:[*]_tep_, const char pass:[*]_buf_, unsigned long _size_, const char pass:[*]_sys_); +enum tep_errno *tep_parse_format*(struct tep_handle pass:[*]_tep_, struct tep_event pass:[*]pass:[*]_eventp_, const char pass:[*]_buf_, unsigned long _size_, const char pass:[*]_sys_); +-- + +DESCRIPTION +----------- +The _tep_parse_event()_ function parses the event format and creates an event +structure to quickly parse raw data for a given event. The _tep_ argument is +the trace event parser context. The created event structure is stored in the +_tep_ context. The _buf_ argument is a buffer with _size_, where the event +format data is. The event format data can be taken from +tracefs/events/.../.../format files. The _sys_ argument is the system of +the event. + +The _tep_parse_format()_ function does the same as _tep_parse_event()_. The only +difference is in the extra _eventp_ argument, where the newly created event +structure is returned. + +RETURN VALUE +------------ +Both _tep_parse_event()_ and _tep_parse_format()_ functions return 0 on success, +or TEP_ERRNO__... in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +char *buf; +int size; +struct tep_event *event = NULL; +buf = read_file("/sys/kernel/tracing/events/ftrace/print/format", &size); +if (tep_parse_event(tep, buf, size, "ftrace") != 0) { + /* Failed to parse the ftrace print format */ +} + +if (tep_parse_format(tep, &event, buf, size, "ftrace") != 0) { + /* Failed to parse the ftrace print format */ +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-parse_head.txt b/tools/lib/traceevent/Documentation/libtraceevent-parse_head.txt new file mode 100644 index 000000000..c90f16c7d --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-parse_head.txt @@ -0,0 +1,82 @@ +libtraceevent(3) +================ + +NAME +---- +tep_parse_header_page - Parses the data stored in the header page. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_parse_header_page*(struct tep_handle pass:[*]_tep_, char pass:[*]_buf_, unsigned long _size_, int _long_size_); +-- + +DESCRIPTION +----------- +The _tep_parse_header_page()_ function parses the header page data from _buf_, +and initializes the _tep_, trace event parser context, with it. The buffer +_buf_ is with _size_, and is supposed to be copied from +tracefs/events/header_page. + +Some old kernels do not have header page info, in this case the +_tep_parse_header_page()_ function can be called with _size_ equal to 0. The +_tep_ context is initialized with default values. The _long_size_ can be used in +this use case, to set the size of a long integer to be used. + +RETURN VALUE +------------ +The _tep_parse_header_page()_ function returns 0 in case of success, or -1 +in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +char *buf; +int size; +buf = read_file("/sys/kernel/tracing/events/header_page", &size); +if (tep_parse_header_page(tep, buf, size, sizeof(unsigned long)) != 0) { + /* Failed to parse the header page */ +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-plugins.txt b/tools/lib/traceevent/Documentation/libtraceevent-plugins.txt new file mode 100644 index 000000000..4d6394397 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-plugins.txt @@ -0,0 +1,122 @@ +libtraceevent(3) +================ + +NAME +---- +tep_load_plugins, tep_unload_plugins, tep_load_plugins_hook - Load / unload traceevent plugins. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +struct tep_plugin_list pass:[*]*tep_load_plugins*(struct tep_handle pass:[*]_tep_); +void *tep_unload_plugins*(struct tep_plugin_list pass:[*]_plugin_list_, struct tep_handle pass:[*]_tep_); +void *tep_load_plugins_hook*(struct tep_handle pass:[*]_tep_, const char pass:[*]_suffix_, + void (pass:[*]_load_plugin_)(struct tep_handle pass:[*]tep, + const char pass:[*]path, + const char pass:[*]name, + void pass:[*]data), + void pass:[*]_data_); +-- + +DESCRIPTION +----------- +The _tep_load_plugins()_ function loads all plugins, located in the plugin +directories. The _tep_ argument is trace event parser context. +The plugin directories are : +[verse] +-- + - Directories, specified in _tep_->plugins_dir with priority TEP_PLUGIN_FIRST + - System's plugin directory, defined at the library compile time. It + depends on the library installation prefix and usually is + _(install_preffix)/lib/traceevent/plugins_ + - Directory, defined by the environment variable _TRACEEVENT_PLUGIN_DIR_ + - User's plugin directory, located at _~/.local/lib/traceevent/plugins_ + - Directories, specified in _tep_->plugins_dir with priority TEP_PLUGIN_LAST +-- +Loading of plugins can be controlled by the _tep_flags_, using the +_tep_set_flag()_ API: +[verse] +-- + _TEP_DISABLE_SYS_PLUGINS_ - do not load plugins, located in + the system's plugin directory. + _TEP_DISABLE_PLUGINS_ - do not load any plugins. +-- +The _tep_set_flag()_ API needs to be called before _tep_load_plugins()_, if +loading of all plugins is not the desired case. + +The _tep_unload_plugins()_ function unloads the plugins, previously loaded by +_tep_load_plugins()_. The _tep_ argument is trace event parser context. The +_plugin_list_ is the list of loaded plugins, returned by +the _tep_load_plugins()_ function. + +The _tep_load_plugins_hook_ function walks through all directories with plugins +and calls user specified _load_plugin()_ hook for each plugin file. Only files +with given _suffix_ are considered to be plugins. The _data_ is a user specified +context, passed to _load_plugin()_. Directories and the walk order are the same +as in _tep_load_plugins()_ API. + +RETURN VALUE +------------ +The _tep_load_plugins()_ function returns a list of successfully loaded plugins, +or NULL in case no plugins are loaded. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +struct tep_plugin_list *plugins = tep_load_plugins(tep); +if (plugins == NULL) { + /* no plugins are loaded */ +} +... +tep_unload_plugins(plugins, tep); +... +void print_plugin(struct tep_handle *tep, const char *path, + const char *name, void *data) +{ + pritnf("Found libtraceevent plugin %s/%s\n", path, name); +} +... +tep_load_plugins_hook(tep, ".so", print_plugin, NULL); +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_, _tep_set_flag(3)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-record_parse.txt b/tools/lib/traceevent/Documentation/libtraceevent-record_parse.txt new file mode 100644 index 000000000..e9a69116c --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-record_parse.txt @@ -0,0 +1,137 @@ +libtraceevent(3) +================ + +NAME +---- +tep_data_type, tep_data_pid,tep_data_preempt_count, tep_data_flags - +Extract common fields from a record. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *trace_flag_type* { + _TRACE_FLAG_IRQS_OFF_, + _TRACE_FLAG_IRQS_NOSUPPORT_, + _TRACE_FLAG_NEED_RESCHED_, + _TRACE_FLAG_HARDIRQ_, + _TRACE_FLAG_SOFTIRQ_, +}; + +int *tep_data_type*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); +int *tep_data_pid*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); +int *tep_data_preempt_count*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); +int *tep_data_flags*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); +-- + +DESCRIPTION +----------- +This set of functions can be used to extract common fields from a record. + +The _tep_data_type()_ function gets the event id from the record _rec_. +It reads the "common_type" field. The _tep_ argument is the trace event parser +context. + +The _tep_data_pid()_ function gets the process id from the record _rec_. +It reads the "common_pid" field. The _tep_ argument is the trace event parser +context. + +The _tep_data_preempt_count()_ function gets the preemption count from the +record _rec_. It reads the "common_preempt_count" field. The _tep_ argument is +the trace event parser context. + +The _tep_data_flags()_ function gets the latency flags from the record _rec_. +It reads the "common_flags" field. The _tep_ argument is the trace event parser +context. Supported latency flags are: +[verse] +-- + _TRACE_FLAG_IRQS_OFF_, Interrupts are disabled. + _TRACE_FLAG_IRQS_NOSUPPORT_, Reading IRQ flag is not supported by the architecture. + _TRACE_FLAG_NEED_RESCHED_, Task needs rescheduling. + _TRACE_FLAG_HARDIRQ_, Hard IRQ is running. + _TRACE_FLAG_SOFTIRQ_, Soft IRQ is running. +-- + +RETURN VALUE +------------ +The _tep_data_type()_ function returns an integer, representing the event id. + +The _tep_data_pid()_ function returns an integer, representing the process id + +The _tep_data_preempt_count()_ function returns an integer, representing the +preemption count. + +The _tep_data_flags()_ function returns an integer, representing the latency +flags. Look at the _trace_flag_type_ enum for supported flags. + +All these functions in case of an error return a negative integer. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +void process_record(struct tep_record *record) +{ + int data; + + data = tep_data_type(tep, record); + if (data >= 0) { + /* Got the ID of the event */ + } + + data = tep_data_pid(tep, record); + if (data >= 0) { + /* Got the process ID */ + } + + data = tep_data_preempt_count(tep, record); + if (data >= 0) { + /* Got the preemption count */ + } + + data = tep_data_flags(tep, record); + if (data >= 0) { + /* Got the latency flags */ + } +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-reg_event_handler.txt b/tools/lib/traceevent/Documentation/libtraceevent-reg_event_handler.txt new file mode 100644 index 000000000..53d37d72a --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-reg_event_handler.txt @@ -0,0 +1,156 @@ +libtraceevent(3) +================ + +NAME +---- +tep_register_event_handler, tep_unregister_event_handler - Register / +unregisters a callback function to parse an event information. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_reg_handler* { + _TEP_REGISTER_SUCCESS_, + _TEP_REGISTER_SUCCESS_OVERWRITE_, +}; + +int *tep_register_event_handler*(struct tep_handle pass:[*]_tep_, int _id_, const char pass:[*]_sys_name_, const char pass:[*]_event_name_, tep_event_handler_func _func_, void pass:[*]_context_); +int *tep_unregister_event_handler*(struct tep_handle pass:[*]tep, int id, const char pass:[*]sys_name, const char pass:[*]event_name, tep_event_handler_func func, void pass:[*]_context_); + +typedef int (*pass:[*]tep_event_handler_func*)(struct trace_seq pass:[*]s, struct tep_record pass:[*]record, struct tep_event pass:[*]event, void pass:[*]context); +-- + +DESCRIPTION +----------- +The _tep_register_event_handler()_ function registers a handler function, +which is going to be called to parse the information for a given event. +The _tep_ argument is the trace event parser context. The _id_ argument is +the id of the event. The _sys_name_ argument is the name of the system, +the event belongs to. The _event_name_ argument is the name of the event. +If _id_ is >= 0, it is used to find the event, otherwise _sys_name_ and +_event_name_ are used. The _func_ is a pointer to the function, which is going +to be called to parse the event information. The _context_ argument is a pointer +to the context data, which will be passed to the _func_. If a handler function +for the same event is already registered, it will be overridden with the new +one. This mechanism allows a developer to override the parsing of a given event. +If for some reason the default print format is not sufficient, the developer +can register a function for an event to be used to parse the data instead. + +The _tep_unregister_event_handler()_ function unregisters the handler function, +previously registered with _tep_register_event_handler()_. The _tep_ argument +is the trace event parser context. The _id_, _sys_name_, _event_name_, _func_, +and _context_ are the same arguments, as when the callback function _func_ was +registered. + +The _tep_event_handler_func_ is the type of the custom event handler +function. The _s_ argument is the trace sequence, it can be used to create a +custom string, describing the event. A _record_ to get the event from is passed +as input parameter and also the _event_ - the handle to the record's event. The +_context_ is custom context, set when the custom event handler is registered. + +RETURN VALUE +------------ +The _tep_register_event_handler()_ function returns _TEP_REGISTER_SUCCESS_ +if the new handler is registered successfully or +_TEP_REGISTER_SUCCESS_OVERWRITE_ if an existing handler is overwritten. +If there is not enough memory to complete the registration, +TEP_ERRNO__MEM_ALLOC_FAILED is returned. + +The _tep_unregister_event_handler()_ function returns 0 if _func_ was removed +successful or, -1 if the event was not found. + +The _tep_event_handler_func_ should return -1 in case of an error, +or 0 otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct tep_handle *tep = tep_alloc(); +... +int timer_expire_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + trace_seq_printf(s, "hrtimer="); + + if (tep_print_num_field(s, "0x%llx", event, "timer", record, 0) == -1) + tep_print_num_field(s, "0x%llx", event, "hrtimer", record, 1); + + trace_seq_printf(s, " now="); + + tep_print_num_field(s, "%llu", event, "now", record, 1); + + tep_print_func_field(s, " function=%s", event, "function", record, 0); + + return 0; +} +... + int ret; + + ret = tep_register_event_handler(tep, -1, "timer", "hrtimer_expire_entry", + timer_expire_handler, NULL); + if (ret < 0) { + char buf[32]; + + tep_strerror(tep, ret, buf, 32) + printf("Failed to register handler for hrtimer_expire_entry: %s\n", buf); + } else { + switch (ret) { + case TEP_REGISTER_SUCCESS: + printf ("Registered handler for hrtimer_expire_entry\n"); + break; + case TEP_REGISTER_SUCCESS_OVERWRITE: + printf ("Overwrote handler for hrtimer_expire_entry\n"); + break; + } + } +... + ret = tep_unregister_event_handler(tep, -1, "timer", "hrtimer_expire_entry", + timer_expire_handler, NULL); + if ( ret ) + printf ("Failed to unregister handler for hrtimer_expire_entry\n"); + +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences + related APIs. Trace sequences are used to allow a function to call + several other functions to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-reg_print_func.txt b/tools/lib/traceevent/Documentation/libtraceevent-reg_print_func.txt new file mode 100644 index 000000000..708dce91e --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-reg_print_func.txt @@ -0,0 +1,155 @@ +libtraceevent(3) +================ + +NAME +---- +tep_register_print_function,tep_unregister_print_function - +Registers / Unregisters a helper function. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_func_arg_type* { + TEP_FUNC_ARG_VOID, + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_LONG, + TEP_FUNC_ARG_STRING, + TEP_FUNC_ARG_PTR, + TEP_FUNC_ARG_MAX_TYPES +}; + +typedef unsigned long long (*pass:[*]tep_func_handler*)(struct trace_seq pass:[*]s, unsigned long long pass:[*]args); + +int *tep_register_print_function*(struct tep_handle pass:[*]_tep_, tep_func_handler _func_, enum tep_func_arg_type _ret_type_, char pass:[*]_name_, _..._); +int *tep_unregister_print_function*(struct tep_handle pass:[*]_tep_, tep_func_handler _func_, char pass:[*]_name_); +-- + +DESCRIPTION +----------- +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 _tep_register_print_function()_ registers such helper function. The _tep_ +argument is the trace event parser context. The _func_ argument is a pointer +to the helper function. The _ret_type_ argument is the return type of the +helper function, value from the _tep_func_arg_type_ enum. The _name_ is the name +of the helper function, as seen in the print format arguments. The _..._ is a +variable list of _tep_func_arg_type_ enums, the _func_ function arguments. +This list must end with _TEP_FUNC_ARG_VOID_. See 'EXAMPLE' section. + +The _tep_unregister_print_function()_ unregisters a helper function, previously +registered with _tep_register_print_function()_. The _tep_ argument is the +trace event parser context. The _func_ and _name_ arguments are the same, used +when the helper function was registered. + +The _tep_func_handler_ is the type of the helper function. The _s_ argument is +the trace sequence, it can be used to create a custom string. +The _args_ is a list of arguments, defined when the helper function was +registered. + +RETURN VALUE +------------ +The _tep_register_print_function()_ function returns 0 in case of success. +In case of an error, TEP_ERRNO_... code is returned. + +The _tep_unregister_print_function()_ returns 0 in case of success, or -1 in +case of an error. + +EXAMPLE +------- +Some events have internal functions calls, that appear in the print format +output. For example "tracefs/events/i915/g4x_wm/format" has: +[source,c] +-- +print fmt: "pipe %c, frame=%u, scanline=%u, wm %d/%d/%d, sr %s/%d/%d/%d, hpll %s/%d/%d/%d, fbc %s", + ((REC->pipe) + 'A'), REC->frame, REC->scanline, REC->primary, + REC->sprite, REC->cursor, yesno(REC->cxsr), REC->sr_plane, + REC->sr_cursor, REC->sr_fbc, yesno(REC->hpll), REC->hpll_plane, + REC->hpll_cursor, REC->hpll_fbc, yesno(REC->fbc) +-- +Notice the call to function _yesno()_ in the print arguments. In the kernel +context, this function has the following implementation: +[source,c] +-- +static const char *yesno(int x) +{ + static const char *yes = "yes"; + static const char *no = "no"; + + return x ? yes : no; +} +-- +The user space event parser has no idea how to handle this _yesno()_ function. +The _tep_register_print_function()_ API can be used to register a user space +helper function, mapped to the kernel's _yesno()_: +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct tep_handle *tep = tep_alloc(); +... +static const char *yes_no_helper(int x) +{ + return x ? "yes" : "no"; +} +... + if ( tep_register_print_function(tep, + yes_no_helper, + TEP_FUNC_ARG_STRING, + "yesno", + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_VOID) != 0) { + /* Failed to register yes_no_helper function */ + } + +/* + Now, when the event parser encounters this yesno() function, it will know + how to handle it. +*/ +... + if (tep_unregister_print_function(tep, yes_no_helper, "yesno") != 0) { + /* Failed to unregister yes_no_helper function */ + } +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences + related APIs. Trace sequences are used to allow a function to call + several other functions to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-set_flag.txt b/tools/lib/traceevent/Documentation/libtraceevent-set_flag.txt new file mode 100644 index 000000000..b0599780b --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-set_flag.txt @@ -0,0 +1,104 @@ +libtraceevent(3) +================ + +NAME +---- +tep_set_flag, tep_clear_flag, tep_test_flag - +Manage flags of trace event parser context. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +enum *tep_flag* { + _TEP_NSEC_OUTPUT_, + _TEP_DISABLE_SYS_PLUGINS_, + _TEP_DISABLE_PLUGINS_ +}; +void *tep_set_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flag_); +void *tep_clear_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flag_); +bool *tep_test_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flag_); +-- + +DESCRIPTION +----------- +Trace event parser context flags are defined in *enum tep_flag*: +[verse] +-- +_TEP_NSEC_OUTPUT_ - print event's timestamp in nano seconds, instead of micro seconds. +_TEP_DISABLE_SYS_PLUGINS_ - disable plugins, located in system's plugin + directory. This directory is defined at library compile + time, and usually depends on library installation + prefix: (install_preffix)/lib/traceevent/plugins +_TEP_DISABLE_PLUGINS_ - disable all library plugins: + - in system's plugin directory + - in directory, defined by the environment variable _TRACEEVENT_PLUGIN_DIR_ + - in user's home directory, _~/.traceevent/plugins_ +-- +Note: plugin related flags must me set before calling _tep_load_plugins()_ API. + +The _tep_set_flag()_ function sets _flag_ to _tep_ context. + +The _tep_clear_flag()_ function clears _flag_ from _tep_ context. + +The _tep_test_flag()_ function tests if _flag_ is set to _tep_ context. + +RETURN VALUE +------------ +_tep_test_flag()_ function returns true if _flag_ is set, false otherwise. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +/* Print timestamps in nanoseconds */ +tep_set_flag(tep, TEP_NSEC_OUTPUT); +... +if (tep_test_flag(tep, TEP_NSEC_OUTPUT)) { + /* print timestamps in nanoseconds */ +} else { + /* print timestamps in microseconds */ +} +... +/* Print timestamps in microseconds */ +tep_clear_flag(tep, TEP_NSEC_OUTPUT); +... +-- +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-strerror.txt b/tools/lib/traceevent/Documentation/libtraceevent-strerror.txt new file mode 100644 index 000000000..ee4062a00 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-strerror.txt @@ -0,0 +1,85 @@ +libtraceevent(3) +================ + +NAME +---- +tep_strerror - Returns a string describing regular errno and tep error number. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +int *tep_strerror*(struct tep_handle pass:[*]_tep_, enum tep_errno _errnum_, char pass:[*]_buf_, size_t _buflen_); + +-- +DESCRIPTION +----------- +The _tep_strerror()_ function converts tep error number into a human +readable string. +The _tep_ argument is trace event parser context. The _errnum_ is a regular +errno, defined in errno.h, or a tep error number. The string, describing this +error number is copied in the _buf_ argument. The _buflen_ argument is +the size of the _buf_. + +It as a thread safe wrapper around strerror_r(). The library function has two +different behaviors - POSIX and GNU specific. The _tep_strerror()_ API always +behaves as the POSIX version - the error string is copied in the user supplied +buffer. + +RETURN VALUE +------------ +The _tep_strerror()_ function returns 0, if a valid _errnum_ is passed and the +string is copied into _buf_. If _errnum_ is not a valid error number, +-1 is returned and _buf_ is not modified. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +... +struct tep_handle *tep = tep_alloc(); +... +char buf[32]; +char *pool = calloc(1, 128); +if (tep == NULL) { + tep_strerror(tep, TEP_ERRNO__MEM_ALLOC_FAILED, buf, 32); + printf ("The pool is not initialized, %s", buf); +} +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent-tseq.txt b/tools/lib/traceevent/Documentation/libtraceevent-tseq.txt new file mode 100644 index 000000000..8ac6aa174 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent-tseq.txt @@ -0,0 +1,158 @@ +libtraceevent(3) +================ + +NAME +---- +trace_seq_init, trace_seq_destroy, trace_seq_reset, trace_seq_terminate, +trace_seq_putc, trace_seq_puts, trace_seq_printf, trace_seq_vprintf, +trace_seq_do_fprintf, trace_seq_do_printf - +Initialize / destroy a trace sequence. + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* +*#include <trace-seq.h>* + +void *trace_seq_init*(struct trace_seq pass:[*]_s_); +void *trace_seq_destroy*(struct trace_seq pass:[*]_s_); +void *trace_seq_reset*(struct trace_seq pass:[*]_s_); +void *trace_seq_terminate*(struct trace_seq pass:[*]_s_); +int *trace_seq_putc*(struct trace_seq pass:[*]_s_, unsigned char _c_); +int *trace_seq_puts*(struct trace_seq pass:[*]_s_, const char pass:[*]_str_); +int *trace_seq_printf*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, _..._); +int *trace_seq_vprintf*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, va_list _args_); +int *trace_seq_do_printf*(struct trace_seq pass:[*]_s_); +int *trace_seq_do_fprintf*(struct trace_seq pass:[*]_s_, FILE pass:[*]_fp_); +-- + +DESCRIPTION +----------- +Trace sequences are used to allow a function to call several other functions +to create a string of data to use. + +The _trace_seq_init()_ function initializes the trace sequence _s_. + +The _trace_seq_destroy()_ function destroys the trace sequence _s_ and frees +all its resources that it had used. + +The _trace_seq_reset()_ function re-initializes the trace sequence _s_. All +characters already written in _s_ will be deleted. + +The _trace_seq_terminate()_ function terminates the trace sequence _s_. It puts +the null character pass:['\0'] at the end of the buffer. + +The _trace_seq_putc()_ function puts a single character _c_ in the trace +sequence _s_. + +The _trace_seq_puts()_ function puts a NULL terminated string _str_ in the +trace sequence _s_. + +The _trace_seq_printf()_ function puts a formated string _fmt _with +variable arguments _..._ in the trace sequence _s_. + +The _trace_seq_vprintf()_ function puts a formated string _fmt _with +list of arguments _args_ in the trace sequence _s_. + +The _trace_seq_do_printf()_ function prints the buffer of trace sequence _s_ to +the standard output stdout. + +The _trace_seq_do_fprintf()_ function prints the buffer of trace sequence _s_ +to the given file _fp_. + +RETURN VALUE +------------ +Both _trace_seq_putc()_ and _trace_seq_puts()_ functions return the number of +characters put in the trace sequence, or 0 in case of an error + +Both _trace_seq_printf()_ and _trace_seq_vprintf()_ functions return 0 if the +trace oversizes the buffer's free space, the number of characters printed, or +a negative value in case of an error. + +Both _trace_seq_do_printf()_ and _trace_seq_do_fprintf()_ functions return the +number of printed characters, or -1 in case of an error. + +EXAMPLE +------- +[source,c] +-- +#include <event-parse.h> +#include <trace-seq.h> +... +struct trace_seq seq; +trace_seq_init(&seq); +... +void foo_seq_print(struct trace_seq *tseq, char *format, ...) +{ + va_list ap; + va_start(ap, format); + if (trace_seq_vprintf(tseq, format, ap) <= 0) { + /* Failed to print in the trace sequence */ + } + va_end(ap); +} + +trace_seq_reset(&seq); + +char *str = " MAN page example"; +if (trace_seq_puts(&seq, str) != strlen(str)) { + /* Failed to put str in the trace sequence */ +} +if (trace_seq_putc(&seq, ':') != 1) { + /* Failed to put ':' in the trace sequence */ +} +if (trace_seq_printf(&seq, " trace sequence: %d", 1) <= 0) { + /* Failed to print in the trace sequence */ +} +foo_seq_print( &seq, " %d\n", 2); + +trace_seq_terminate(&seq); +... + +if (trace_seq_do_printf(&seq) < 0 ) { + /* Failed to print the sequence buffer to the standard output */ +} +FILE *fp = fopen("trace.txt", "w"); +if (trace_seq_do_fprintf(&seq, fp) < 0 ) [ + /* Failed to print the sequence buffer to the trace.txt file */ +} + +trace_seq_destroy(&seq); +... +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences related APIs. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_libtraceevent(3)_, _trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/libtraceevent.txt b/tools/lib/traceevent/Documentation/libtraceevent.txt new file mode 100644 index 000000000..d530a7ce8 --- /dev/null +++ b/tools/lib/traceevent/Documentation/libtraceevent.txt @@ -0,0 +1,192 @@ +libtraceevent(3) +================ + +NAME +---- +libtraceevent - Linux kernel trace event library + +SYNOPSIS +-------- +[verse] +-- +*#include <event-parse.h>* + +Management of tep handler data structure and access of its members: + struct tep_handle pass:[*]*tep_alloc*(void); + void *tep_free*(struct tep_handle pass:[*]_tep_); + void *tep_ref*(struct tep_handle pass:[*]_tep_); + void *tep_unref*(struct tep_handle pass:[*]_tep_); + int *tep_get_ref*(struct tep_handle pass:[*]_tep_); + void *tep_set_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flag_); + void *tep_clear_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flag_); + bool *tep_test_flag*(struct tep_handle pass:[*]_tep_, enum tep_flag _flags_); + int *tep_get_cpus*(struct tep_handle pass:[*]_tep_); + void *tep_set_cpus*(struct tep_handle pass:[*]_tep_, int _cpus_); + int *tep_get_long_size*(strucqt tep_handle pass:[*]_tep_); + void *tep_set_long_size*(struct tep_handle pass:[*]_tep_, int _long_size_); + int *tep_get_page_size*(struct tep_handle pass:[*]_tep_); + void *tep_set_page_size*(struct tep_handle pass:[*]_tep_, int _page_size_); + int *tep_get_header_page_size*(struct tep_handle pass:[*]_tep_); + int *tep_get_header_timestamp_size*(struct tep_handle pass:[*]_tep_); + bool *tep_is_old_format*(struct tep_handle pass:[*]_tep_); + int *tep_strerror*(struct tep_handle pass:[*]_tep_, enum tep_errno _errnum_, char pass:[*]_buf_, size_t _buflen_); + +Register / unregister APIs: + int *tep_register_function*(struct tep_handle pass:[*]_tep_, char pass:[*]_name_, unsigned long long _addr_, char pass:[*]_mod_); + int *tep_register_event_handler*(struct tep_handle pass:[*]_tep_, int _id_, const char pass:[*]_sys_name_, const char pass:[*]_event_name_, tep_event_handler_func _func_, void pass:[*]_context_); + int *tep_unregister_event_handler*(struct tep_handle pass:[*]tep, int id, const char pass:[*]sys_name, const char pass:[*]event_name, tep_event_handler_func func, void pass:[*]_context_); + int *tep_register_print_string*(struct tep_handle pass:[*]_tep_, const char pass:[*]_fmt_, unsigned long long _addr_); + int *tep_register_print_function*(struct tep_handle pass:[*]_tep_, tep_func_handler _func_, enum tep_func_arg_type _ret_type_, char pass:[*]_name_, _..._); + int *tep_unregister_print_function*(struct tep_handle pass:[*]_tep_, tep_func_handler _func_, char pass:[*]_name_); + +Plugins management: + struct tep_plugin_list pass:[*]*tep_load_plugins*(struct tep_handle pass:[*]_tep_); + void *tep_unload_plugins*(struct tep_plugin_list pass:[*]_plugin_list_, struct tep_handle pass:[*]_tep_); + char pass:[*]pass:[*]*tep_plugin_list_options*(void); + void *tep_plugin_free_options_list*(char pass:[*]pass:[*]_list_); + int *tep_plugin_add_options*(const char pass:[*]_name_, struct tep_plugin_option pass:[*]_options_); + void *tep_plugin_remove_options*(struct tep_plugin_option pass:[*]_options_); + void *tep_print_plugins*(struct trace_seq pass:[*]_s_, const char pass:[*]_prefix_, const char pass:[*]_suffix_, const struct tep_plugin_list pass:[*]_list_); + +Event related APIs: + struct tep_event pass:[*]*tep_get_event*(struct tep_handle pass:[*]_tep_, int _index_); + struct tep_event pass:[*]*tep_get_first_event*(struct tep_handle pass:[*]_tep_); + int *tep_get_events_count*(struct tep_handle pass:[*]_tep_); + struct tep_event pass:[*]pass:[*]*tep_list_events*(struct tep_handle pass:[*]_tep_, enum tep_event_sort_type _sort_type_); + struct tep_event pass:[*]pass:[*]*tep_list_events_copy*(struct tep_handle pass:[*]_tep_, enum tep_event_sort_type _sort_type_); + void *tep_print_event*(struct tep_handle pass:[*]_tep_, struct trace_seq pass:[*]_s_, struct tep_record pass:[*]_record_, const char pass:[*]_fmt_, _..._); + +Event finding: + struct tep_event pass:[*]*tep_find_event*(struct tep_handle pass:[*]_tep_, int _id_); + struct tep_event pass:[*]*tep_find_event_by_name*(struct tep_handle pass:[*]_tep_, const char pass:[*]_sys_, const char pass:[*]_name_); + struct tep_event pass:[*]*tep_find_event_by_record*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_record_); + +Parsing of event files: + int *tep_parse_header_page*(struct tep_handle pass:[*]_tep_, char pass:[*]_buf_, unsigned long _size_, int _long_size_); + enum tep_errno *tep_parse_event*(struct tep_handle pass:[*]_tep_, const char pass:[*]_buf_, unsigned long _size_, const char pass:[*]_sys_); + enum tep_errno *tep_parse_format*(struct tep_handle pass:[*]_tep_, struct tep_event pass:[*]pass:[*]_eventp_, const char pass:[*]_buf_, unsigned long _size_, const char pass:[*]_sys_); + +APIs related to fields from event's format files: + struct tep_format_field pass:[*]pass:[*]*tep_event_common_fields*(struct tep_event pass:[*]_event_); + struct tep_format_field pass:[*]pass:[*]*tep_event_fields*(struct tep_event pass:[*]_event_); + void pass:[*]*tep_get_field_raw*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int pass:[*]_len_, int _err_); + int *tep_get_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); + int *tep_get_common_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); + int *tep_get_any_field_val*(struct trace_seq pass:[*]_s_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, unsigned long long pass:[*]_val_, int _err_); + int *tep_read_number_field*(struct tep_format_field pass:[*]_field_, const void pass:[*]_data_, unsigned long long pass:[*]_value_); + +Event fields printing: + void *tep_print_field*(struct trace_seq pass:[*]_s_, void pass:[*]_data_, struct tep_format_field pass:[*]_field_); + void *tep_print_fields*(struct trace_seq pass:[*]_s_, void pass:[*]_data_, int _size_, struct tep_event pass:[*]_event_); + int *tep_print_num_field*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int _err_); + int *tep_print_func_field*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, struct tep_event pass:[*]_event_, const char pass:[*]_name_, struct tep_record pass:[*]_record_, int _err_); + +Event fields finding: + struct tep_format_field pass:[*]*tep_find_common_field*(struct tep_event pass:[*]_event_, const char pass:[*]_name_); + struct tep_format_field pass:[*]*tep_find_field*(struct tep_event_ormat pass:[*]_event_, const char pass:[*]_name_); + struct tep_format_field pass:[*]*tep_find_any_field*(struct tep_event pass:[*]_event_, const char pass:[*]_name_); + +Functions resolver: + int *tep_set_function_resolver*(struct tep_handle pass:[*]_tep_, tep_func_resolver_t pass:[*]_func_, void pass:[*]_priv_); + void *tep_reset_function_resolver*(struct tep_handle pass:[*]_tep_); + const char pass:[*]*tep_find_function*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); + unsigned long long *tep_find_function_address*(struct tep_handle pass:[*]_tep_, unsigned long long _addr_); + +Filter management: + struct tep_event_filter pass:[*]*tep_filter_alloc*(struct tep_handle pass:[*]_tep_); + enum tep_errno *tep_filter_add_filter_str*(struct tep_event_filter pass:[*]_filter_, const char pass:[*]_filter_str_); + enum tep_errno *tep_filter_match*(struct tep_event_filter pass:[*]_filter_, struct tep_record pass:[*]_record_); + int *tep_filter_strerror*(struct tep_event_filter pass:[*]_filter_, enum tep_errno _err_, char pass:[*]buf, size_t _buflen_); + int *tep_event_filtered*(struct tep_event_filter pass:[*]_filter_, int _event_id_); + void *tep_filter_reset*(struct tep_event_filter pass:[*]_filter_); + void *tep_filter_free*(struct tep_event_filter pass:[*]_filter_); + char pass:[*]*tep_filter_make_string*(struct tep_event_filter pass:[*]_filter_, int _event_id_); + int *tep_filter_remove_event*(struct tep_event_filter pass:[*]_filter_, int _event_id_); + int *tep_filter_copy*(struct tep_event_filter pass:[*]_dest_, struct tep_event_filter pass:[*]_source_); + int *tep_filter_compare*(struct tep_event_filter pass:[*]_filter1_, struct tep_event_filter pass:[*]_filter2_); + +Parsing various data from the records: + int *tep_data_type*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); + int *tep_data_pid*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); + int *tep_data_preempt_count*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); + int *tep_data_flags*(struct tep_handle pass:[*]_tep_, struct tep_record pass:[*]_rec_); + +Command and task related APIs: + const char pass:[*]*tep_data_comm_from_pid*(struct tep_handle pass:[*]_tep_, int _pid_); + struct cmdline pass:[*]*tep_data_pid_from_comm*(struct tep_handle pass:[*]_tep_, const char pass:[*]_comm_, struct cmdline pass:[*]_next_); + int *tep_register_comm*(struct tep_handle pass:[*]_tep_, const char pass:[*]_comm_, int _pid_); + int *tep_override_comm*(struct tep_handle pass:[*]_tep_, const char pass:[*]_comm_, int _pid_); + bool *tep_is_pid_registered*(struct tep_handle pass:[*]_tep_, int _pid_); + int *tep_cmdline_pid*(struct tep_handle pass:[*]_tep_, struct cmdline pass:[*]_cmdline_); + +Endian related APIs: + int *tep_is_bigendian*(void); + unsigned long long *tep_read_number*(struct tep_handle pass:[*]_tep_, const void pass:[*]_ptr_, int _size_); + bool *tep_is_file_bigendian*(struct tep_handle pass:[*]_tep_); + void *tep_set_file_bigendian*(struct tep_handle pass:[*]_tep_, enum tep_endian _endian_); + bool *tep_is_local_bigendian*(struct tep_handle pass:[*]_tep_); + void *tep_set_local_bigendian*(struct tep_handle pass:[*]_tep_, enum tep_endian _endian_); + +Trace sequences: +*#include <trace-seq.h>* + void *trace_seq_init*(struct trace_seq pass:[*]_s_); + void *trace_seq_reset*(struct trace_seq pass:[*]_s_); + void *trace_seq_destroy*(struct trace_seq pass:[*]_s_); + int *trace_seq_printf*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, ...); + int *trace_seq_vprintf*(struct trace_seq pass:[*]_s_, const char pass:[*]_fmt_, va_list _args_); + int *trace_seq_puts*(struct trace_seq pass:[*]_s_, const char pass:[*]_str_); + int *trace_seq_putc*(struct trace_seq pass:[*]_s_, unsigned char _c_); + void *trace_seq_terminate*(struct trace_seq pass:[*]_s_); + int *trace_seq_do_fprintf*(struct trace_seq pass:[*]_s_, FILE pass:[*]_fp_); + int *trace_seq_do_printf*(struct trace_seq pass:[*]_s_); +-- + +DESCRIPTION +----------- +The libtraceevent(3) library provides APIs to access kernel tracepoint events, +located in the tracefs file system under the events directory. + +ENVIRONMENT +----------- +[verse] +-- +TRACEEVENT_PLUGIN_DIR + Additional plugin directory. All shared object files, located in this directory will be loaded as traceevent plugins. +-- + +FILES +----- +[verse] +-- +*event-parse.h* + Header file to include in order to have access to the library APIs. +*trace-seq.h* + Header file to include in order to have access to trace sequences related APIs. + Trace sequences are used to allow a function to call several other functions + to create a string of data to use. +*-ltraceevent* + Linker switch to add when building a program that uses the library. +-- + +SEE ALSO +-------- +_trace-cmd(1)_ + +AUTHOR +------ +[verse] +-- +*Steven Rostedt* <rostedt@goodmis.org>, author of *libtraceevent*. +*Tzvetomir Stoyanov* <tz.stoyanov@gmail.com>, author of this man page. +-- +REPORTING BUGS +-------------- +Report bugs to <linux-trace-devel@vger.kernel.org> + +LICENSE +------- +libtraceevent is Free Software licensed under the GNU LGPL 2.1 + +RESOURCES +--------- +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git diff --git a/tools/lib/traceevent/Documentation/manpage-1.72.xsl b/tools/lib/traceevent/Documentation/manpage-1.72.xsl new file mode 100644 index 000000000..b4d315cb8 --- /dev/null +++ b/tools/lib/traceevent/Documentation/manpage-1.72.xsl @@ -0,0 +1,14 @@ +<!-- manpage-1.72.xsl: + special settings for manpages rendered from asciidoc+docbook + handles peculiarities in docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:import href="manpage-base.xsl"/> + +<!-- these are the special values for the roff control characters + needed for docbook-xsl 1.72.0 --> +<xsl:param name="git.docbook.backslash">▓</xsl:param> +<xsl:param name="git.docbook.dot" >⌂</xsl:param> + +</xsl:stylesheet> diff --git a/tools/lib/traceevent/Documentation/manpage-base.xsl b/tools/lib/traceevent/Documentation/manpage-base.xsl new file mode 100644 index 000000000..a264fa616 --- /dev/null +++ b/tools/lib/traceevent/Documentation/manpage-base.xsl @@ -0,0 +1,35 @@ +<!-- manpage-base.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- these params silence some output from xmlto --> +<xsl:param name="man.output.quietly" select="1"/> +<xsl:param name="refentry.meta.get.quietly" select="1"/> + +<!-- convert asciidoc callouts to man page format; + git.docbook.backslash and git.docbook.dot params + must be supplied by another XSL file or other means --> +<xsl:template match="co"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB(', + substring-after(@id,'-'),')', + $git.docbook.backslash,'fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB', + substring-after(@arearefs,'-'), + '. ',$git.docbook.backslash,'fR')"/> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>br </xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/tools/lib/traceevent/Documentation/manpage-bold-literal.xsl b/tools/lib/traceevent/Documentation/manpage-bold-literal.xsl new file mode 100644 index 000000000..608eb5df6 --- /dev/null +++ b/tools/lib/traceevent/Documentation/manpage-bold-literal.xsl @@ -0,0 +1,17 @@ +<!-- manpage-bold-literal.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- render literal text as bold (instead of plain or monospace); + this makes literal text easier to distinguish in manpages + viewed on a tty --> +<xsl:template match="literal"> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fB</xsl:text> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fR</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/tools/lib/traceevent/Documentation/manpage-normal.xsl b/tools/lib/traceevent/Documentation/manpage-normal.xsl new file mode 100644 index 000000000..a48f5b11f --- /dev/null +++ b/tools/lib/traceevent/Documentation/manpage-normal.xsl @@ -0,0 +1,13 @@ +<!-- manpage-normal.xsl: + special settings for manpages rendered from asciidoc+docbook + handles anything we want to keep away from docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:import href="manpage-base.xsl"/> + +<!-- these are the normal values for the roff control characters --> +<xsl:param name="git.docbook.backslash">\</xsl:param> +<xsl:param name="git.docbook.dot" >.</xsl:param> + +</xsl:stylesheet> diff --git a/tools/lib/traceevent/Documentation/manpage-suppress-sp.xsl b/tools/lib/traceevent/Documentation/manpage-suppress-sp.xsl new file mode 100644 index 000000000..a63c7632a --- /dev/null +++ b/tools/lib/traceevent/Documentation/manpage-suppress-sp.xsl @@ -0,0 +1,21 @@ +<!-- manpage-suppress-sp.xsl: + special settings for manpages rendered from asciidoc+docbook + handles erroneous, inline .sp in manpage output of some + versions of docbook-xsl --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- attempt to work around spurious .sp at the tail of the line + that some versions of docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +</xsl:stylesheet> diff --git a/tools/lib/traceevent/Makefile b/tools/lib/traceevent/Makefile new file mode 100644 index 000000000..c874c017c --- /dev/null +++ b/tools/lib/traceevent/Makefile @@ -0,0 +1,300 @@ +# SPDX-License-Identifier: GPL-2.0 +# 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) +$(call allow-override,NM,$(CROSS_COMPILE)nm) +$(call allow-override,PKG_CONFIG,pkg-config) + +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))' + +LP64 := $(shell echo __LP64__ | ${CC} ${CFLAGS} -E -x c - | tail -n 1) +ifeq ($(LP64), 1) + libdir_relative_temp = lib64 +else + libdir_relative_temp = lib +endif + +libdir_relative ?= $(libdir_relative_temp) +prefix ?= /usr/local +libdir = $(prefix)/$(libdir_relative) +man_dir = $(prefix)/share/man +man_dir_SQ = '$(subst ','\'',$(man_dir))' +pkgconfig_dir ?= $(word 1,$(shell $(PKG_CONFIG) \ + --variable pc_path pkg-config | tr ":" " ")) +includedir_relative = traceevent +includedir = $(prefix)/include/$(includedir_relative) +includedir_SQ = '$(subst ','\'',$(includedir))' + +export man_dir man_dir_SQ INSTALL +export DESTDIR DESTDIR_SQ +export EVENT_PARSE_VERSION + +include ../../scripts/Makefile.include + +# copy a bit from Linux kbuild + +ifeq ("$(origin V)", "command line") + VERBOSE = $(V) +endif +ifndef VERBOSE + VERBOSE = 0 +endif + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +#$(info Determined 'srctree' to be $(srctree)) +endif + +export prefix libdir src obj + +# Shell quotes +libdir_SQ = $(subst ','\'',$(libdir)) +libdir_relative_SQ = $(subst ','\'',$(libdir_relative)) + +CONFIG_INCLUDES = +CONFIG_LIBS = +CONFIG_FLAGS = + +VERSION = $(EP_VERSION) +PATCHLEVEL = $(EP_PATCHLEVEL) +EXTRAVERSION = $(EP_EXTRAVERSION) + +OBJ = $@ +N = + +EVENT_PARSE_VERSION = $(EP_VERSION).$(EP_PATCHLEVEL).$(EP_EXTRAVERSION) + +LIB_TARGET = libtraceevent.a libtraceevent.so.$(EVENT_PARSE_VERSION) +LIB_INSTALL = libtraceevent.a libtraceevent.so* +LIB_INSTALL := $(addprefix $(OUTPUT),$(LIB_INSTALL)) + +INCLUDES = -I. -I $(srctree)/tools/include $(CONFIG_INCLUDES) + +# Set compile option CFLAGS +ifdef EXTRA_CFLAGS + CFLAGS := $(EXTRA_CFLAGS) +else + CFLAGS := -g -Wall +endif + +# Append required CFLAGS +override CFLAGS += -fPIC +override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ) +override CFLAGS += $(udis86-flags) -D_GNU_SOURCE + +ifeq ($(VERBOSE),1) + Q = +else + Q = @ +endif + +# Disable command line variables (CFLAGS) override from top +# level Makefile (perf), otherwise build Makefile will get +# the same command line setup. +MAKEOVERRIDES= + +export srctree OUTPUT CC LD CFLAGS V +build := -f $(srctree)/tools/build/Makefile.build dir=. obj + +TE_IN := $(OUTPUT)libtraceevent-in.o +LIB_TARGET := $(addprefix $(OUTPUT),$(LIB_TARGET)) + +CMD_TARGETS = $(LIB_TARGET) + +TARGETS = $(CMD_TARGETS) + +all: all_cmd plugins + +all_cmd: $(CMD_TARGETS) + +$(TE_IN): force + $(Q)$(MAKE) $(build)=libtraceevent + +$(OUTPUT)libtraceevent.so.$(EVENT_PARSE_VERSION): $(TE_IN) + $(QUIET_LINK)$(CC) --shared $(LDFLAGS) $^ -Wl,-soname,libtraceevent.so.$(EP_VERSION) -o $@ + @ln -sf $(@F) $(OUTPUT)libtraceevent.so + @ln -sf $(@F) $(OUTPUT)libtraceevent.so.$(EP_VERSION) + +$(OUTPUT)libtraceevent.a: $(TE_IN) + $(QUIET_LINK)$(RM) $@; $(AR) rcs $@ $^ + +$(OUTPUT)%.so: $(OUTPUT)%-in.o + $(QUIET_LINK)$(CC) $(CFLAGS) -shared $(LDFLAGS) -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 + +tags: force + $(RM) tags + find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \ + --regex-c++='/_PE\(([^,)]*).*/TEP_ERRNO__\1/' + +TAGS: force + $(RM) TAGS + find . -name '*.[ch]' | xargs etags \ + --regex='/_PE(\([^,)]*\).*/TEP_ERRNO__\1/' + +define do_install_mkdir + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ + fi +endef + +define do_install + $(call do_install_mkdir,$2); \ + $(INSTALL) $(if $3,-m $3,) $1 '$(DESTDIR_SQ)$2' +endef + +PKG_CONFIG_SOURCE_FILE = libtraceevent.pc +PKG_CONFIG_FILE := $(addprefix $(OUTPUT),$(PKG_CONFIG_SOURCE_FILE)) +define do_install_pkgconfig_file + if [ -n "${pkgconfig_dir}" ]; then \ + cp -f ${PKG_CONFIG_SOURCE_FILE}.template ${PKG_CONFIG_FILE}; \ + sed -i "s|INSTALL_PREFIX|${1}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|LIB_VERSION|${EVENT_PARSE_VERSION}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|LIB_DIR|${libdir}|g" ${PKG_CONFIG_FILE}; \ + sed -i "s|HEADER_DIR|$(includedir)|g" ${PKG_CONFIG_FILE}; \ + $(call do_install,$(PKG_CONFIG_FILE),$(pkgconfig_dir),644); \ + else \ + (echo Failed to locate pkg-config directory) 1>&2; \ + fi +endef + +install_lib: all_cmd install_plugins install_headers install_pkgconfig + $(call QUIET_INSTALL, $(LIB_TARGET)) \ + $(call do_install_mkdir,$(libdir_SQ)); \ + cp -fpR $(LIB_INSTALL) $(DESTDIR)$(libdir_SQ) + +install_pkgconfig: + $(call QUIET_INSTALL, $(PKG_CONFIG_FILE)) \ + $(call do_install_pkgconfig_file,$(prefix)) + +install_headers: + $(call QUIET_INSTALL, headers) \ + $(call do_install,event-parse.h,$(includedir_SQ),644); \ + $(call do_install,event-utils.h,$(includedir_SQ),644); \ + $(call do_install,trace-seq.h,$(includedir_SQ),644); \ + $(call do_install,kbuffer.h,$(includedir_SQ),644) + +install: install_lib + +clean: clean_plugins + $(call QUIET_CLEAN, libtraceevent) \ + $(RM) *.o *~ $(TARGETS) *.a *.so $(VERSION_FILES) .*.d .*.cmd; \ + $(RM) TRACEEVENT-CFLAGS tags TAGS; \ + $(RM) $(PKG_CONFIG_FILE) + +PHONY += doc +doc: + $(call descend,Documentation) + +PHONY += doc-clean +doc-clean: + $(call descend,Documentation,clean) + +PHONY += doc-install +doc-install: + $(call descend,Documentation,install) + +PHONY += doc-uninstall +doc-uninstall: + $(call descend,Documentation,uninstall) + +PHONY += help +help: + @echo 'Possible targets:' + @echo'' + @echo ' all - default, compile the library and the'\ + 'plugins' + @echo ' plugins - compile the plugins' + @echo ' install - install the library, the plugins,'\ + 'the header and pkgconfig files' + @echo ' clean - clean the library and the plugins object files' + @echo ' doc - compile the documentation files - man'\ + 'and html pages, in the Documentation directory' + @echo ' doc-clean - clean the documentation files' + @echo ' doc-install - install the man pages' + @echo ' doc-uninstall - uninstall the man pages' + @echo'' + +PHONY += plugins +plugins: + $(call descend,plugins) + +PHONY += install_plugins +install_plugins: + $(call descend,plugins,install) + +PHONY += clean_plugins +clean_plugins: + $(call descend,plugins,clean) + +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/traceevent/event-parse-api.c b/tools/lib/traceevent/event-parse-api.c new file mode 100644 index 000000000..f8361e45d --- /dev/null +++ b/tools/lib/traceevent/event-parse-api.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ + +#include "event-parse.h" +#include "event-parse-local.h" +#include "event-utils.h" + +/** + * tep_get_event - returns the event with the given index + * @tep: a handle to the tep_handle + * @index: index of the requested event, in the range 0 .. nr_events + * + * This returns pointer to the element of the events array with the given index + * If @tep is NULL, or @index is not in the range 0 .. nr_events, NULL is returned. + */ +struct tep_event *tep_get_event(struct tep_handle *tep, int index) +{ + if (tep && tep->events && index < tep->nr_events) + return tep->events[index]; + + return NULL; +} + +/** + * tep_get_first_event - returns the first event in the events array + * @tep: a handle to the tep_handle + * + * This returns pointer to the first element of the events array + * If @tep is NULL, NULL is returned. + */ +struct tep_event *tep_get_first_event(struct tep_handle *tep) +{ + return tep_get_event(tep, 0); +} + +/** + * tep_get_events_count - get the number of defined events + * @tep: a handle to the tep_handle + * + * This returns number of elements in event array + * If @tep is NULL, 0 is returned. + */ +int tep_get_events_count(struct tep_handle *tep) +{ + if (tep) + return tep->nr_events; + return 0; +} + +/** + * tep_set_flag - set event parser flag + * @tep: a handle to the tep_handle + * @flag: flag, or combination of flags to be set + * can be any combination from enum tep_flag + * + * This sets a flag or combination of flags from enum tep_flag + */ +void tep_set_flag(struct tep_handle *tep, int flag) +{ + if (tep) + tep->flags |= flag; +} + +/** + * tep_clear_flag - clear event parser flag + * @tep: a handle to the tep_handle + * @flag: flag to be cleared + * + * This clears a tep flag + */ +void tep_clear_flag(struct tep_handle *tep, enum tep_flag flag) +{ + if (tep) + tep->flags &= ~flag; +} + +/** + * tep_test_flag - check the state of event parser flag + * @tep: a handle to the tep_handle + * @flag: flag to be checked + * + * This returns the state of the requested tep flag. + * Returns: true if the flag is set, false otherwise. + */ +bool tep_test_flag(struct tep_handle *tep, enum tep_flag flag) +{ + if (tep) + return tep->flags & flag; + return false; +} + +__hidden unsigned short data2host2(struct tep_handle *tep, unsigned short data) +{ + unsigned short swap; + + if (!tep || tep->host_bigendian == tep->file_bigendian) + return data; + + swap = ((data & 0xffULL) << 8) | + ((data & (0xffULL << 8)) >> 8); + + return swap; +} + +__hidden unsigned int data2host4(struct tep_handle *tep, unsigned int data) +{ + unsigned int swap; + + if (!tep || tep->host_bigendian == tep->file_bigendian) + return data; + + swap = ((data & 0xffULL) << 24) | + ((data & (0xffULL << 8)) << 8) | + ((data & (0xffULL << 16)) >> 8) | + ((data & (0xffULL << 24)) >> 24); + + return swap; +} + +__hidden unsigned long long +data2host8(struct tep_handle *tep, unsigned long long data) +{ + unsigned long long swap; + + if (!tep || tep->host_bigendian == tep->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; +} + +/** + * tep_get_header_page_size - get size of the header page + * @tep: a handle to the tep_handle + * + * This returns size of the header page + * If @tep is NULL, 0 is returned. + */ +int tep_get_header_page_size(struct tep_handle *tep) +{ + if (tep) + return tep->header_page_size_size; + return 0; +} + +/** + * tep_get_header_timestamp_size - get size of the timestamp in the header page + * @tep: a handle to the tep_handle + * + * This returns size of the timestamp in the header page + * If @tep is NULL, 0 is returned. + */ +int tep_get_header_timestamp_size(struct tep_handle *tep) +{ + if (tep) + return tep->header_page_ts_size; + return 0; +} + +/** + * tep_get_cpus - get the number of CPUs + * @tep: a handle to the tep_handle + * + * This returns the number of CPUs + * If @tep is NULL, 0 is returned. + */ +int tep_get_cpus(struct tep_handle *tep) +{ + if (tep) + return tep->cpus; + return 0; +} + +/** + * tep_set_cpus - set the number of CPUs + * @tep: a handle to the tep_handle + * + * This sets the number of CPUs + */ +void tep_set_cpus(struct tep_handle *tep, int cpus) +{ + if (tep) + tep->cpus = cpus; +} + +/** + * tep_get_long_size - get the size of a long integer on the traced machine + * @tep: a handle to the tep_handle + * + * This returns the size of a long integer on the traced machine + * If @tep is NULL, 0 is returned. + */ +int tep_get_long_size(struct tep_handle *tep) +{ + if (tep) + return tep->long_size; + return 0; +} + +/** + * tep_set_long_size - set the size of a long integer on the traced machine + * @tep: a handle to the tep_handle + * @size: size, in bytes, of a long integer + * + * This sets the size of a long integer on the traced machine + */ +void tep_set_long_size(struct tep_handle *tep, int long_size) +{ + if (tep) + tep->long_size = long_size; +} + +/** + * tep_get_page_size - get the size of a memory page on the traced machine + * @tep: a handle to the tep_handle + * + * This returns the size of a memory page on the traced machine + * If @tep is NULL, 0 is returned. + */ +int tep_get_page_size(struct tep_handle *tep) +{ + if (tep) + return tep->page_size; + return 0; +} + +/** + * tep_set_page_size - set the size of a memory page on the traced machine + * @tep: a handle to the tep_handle + * @_page_size: size of a memory page, in bytes + * + * This sets the size of a memory page on the traced machine + */ +void tep_set_page_size(struct tep_handle *tep, int _page_size) +{ + if (tep) + tep->page_size = _page_size; +} + +/** + * tep_is_file_bigendian - return the endian of the file + * @tep: a handle to the tep_handle + * + * This returns true if the file is in big endian order + * If @tep is NULL, false is returned. + */ +bool tep_is_file_bigendian(struct tep_handle *tep) +{ + if (tep) + return (tep->file_bigendian == TEP_BIG_ENDIAN); + return false; +} + +/** + * tep_set_file_bigendian - set if the file is in big endian order + * @tep: a handle to the tep_handle + * @endian: non zero, if the file is in big endian order + * + * This sets if the file is in big endian order + */ +void tep_set_file_bigendian(struct tep_handle *tep, enum tep_endian endian) +{ + if (tep) + tep->file_bigendian = endian; +} + +/** + * tep_is_local_bigendian - return the endian of the saved local machine + * @tep: a handle to the tep_handle + * + * This returns true if the saved local machine in @tep is big endian. + * If @tep is NULL, false is returned. + */ +bool tep_is_local_bigendian(struct tep_handle *tep) +{ + if (tep) + return (tep->host_bigendian == TEP_BIG_ENDIAN); + return 0; +} + +/** + * tep_set_local_bigendian - set the stored local machine endian order + * @tep: a handle to the tep_handle + * @endian: non zero, if the local host has big endian order + * + * This sets the endian order for the local machine. + */ +void tep_set_local_bigendian(struct tep_handle *tep, enum tep_endian endian) +{ + if (tep) + tep->host_bigendian = endian; +} + +/** + * tep_is_old_format - get if an old kernel is used + * @tep: a handle to the tep_handle + * + * This returns true, if an old kernel is used to generate the tracing events or + * false if a new kernel is used. Old kernels did not have header page info. + * If @tep is NULL, false is returned. + */ +bool tep_is_old_format(struct tep_handle *tep) +{ + if (tep) + return tep->old_format; + return false; +} + +/** + * tep_set_test_filters - set a flag to test a filter string + * @tep: a handle to the tep_handle + * @test_filters: the new value of the test_filters flag + * + * This sets a flag to test a filter string. If this flag is set, when + * tep_filter_add_filter_str() API as called,it will print the filter string + * instead of adding it. + */ +void tep_set_test_filters(struct tep_handle *tep, int test_filters) +{ + if (tep) + tep->test_filters = test_filters; +} diff --git a/tools/lib/traceevent/event-parse-local.h b/tools/lib/traceevent/event-parse-local.h new file mode 100644 index 000000000..fd4bbcfbb --- /dev/null +++ b/tools/lib/traceevent/event-parse-local.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ + +#ifndef _PARSE_EVENTS_INT_H +#define _PARSE_EVENTS_INT_H + +struct tep_cmdline; +struct cmdline_list; +struct func_map; +struct func_list; +struct event_handler; +struct func_resolver; +struct tep_plugins_dir; + +#define __hidden __attribute__((visibility ("hidden"))) + +struct tep_handle { + 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; + + enum tep_endian file_bigendian; + enum tep_endian host_bigendian; + + int old_format; + + int cpus; + int long_size; + int page_size; + + struct tep_cmdline *cmdlines; + struct cmdline_list *cmdlist; + int cmdline_count; + + struct func_map *func_map; + struct func_resolver *func_resolver; + struct func_list *funclist; + unsigned int func_count; + + struct printk_map *printk_map; + struct printk_list *printklist; + unsigned int printk_count; + + struct tep_event **events; + int nr_events; + struct tep_event **sort_events; + enum tep_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 test_filters; + + int flags; + + struct tep_format_field *bprint_ip_field; + struct tep_format_field *bprint_fmt_field; + struct tep_format_field *bprint_buf_field; + + struct event_handler *handlers; + struct tep_function_handler *func_handlers; + + /* cache */ + struct tep_event *last_event; + + struct tep_plugins_dir *plugins_dir; +}; + +enum tep_print_parse_type { + PRINT_FMT_STRING, + PRINT_FMT_ARG_DIGIT, + PRINT_FMT_ARG_POINTER, + PRINT_FMT_ARG_STRING, +}; + +struct tep_print_parse { + struct tep_print_parse *next; + + char *format; + int ls; + enum tep_print_parse_type type; + struct tep_print_arg *arg; + struct tep_print_arg *len_as_arg; +}; + +void free_tep_event(struct tep_event *event); +void free_tep_format_field(struct tep_format_field *field); +void free_tep_plugin_paths(struct tep_handle *tep); + +unsigned short data2host2(struct tep_handle *tep, unsigned short data); +unsigned int data2host4(struct tep_handle *tep, unsigned int data); +unsigned long long data2host8(struct tep_handle *tep, unsigned long long data); + +/* access to the internal parser */ +int peek_char(void); +void init_input_buf(const char *buf, unsigned long long size); +unsigned long long get_input_buf_ptr(void); +const char *get_input_buf(void); +enum tep_event_type read_token(char **tok); +void free_token(char *tok); + +#endif /* _PARSE_EVENTS_INT_H */ diff --git a/tools/lib/traceevent/event-parse.c b/tools/lib/traceevent/event-parse.c new file mode 100644 index 000000000..fe58843d0 --- /dev/null +++ b/tools/lib/traceevent/event-parse.c @@ -0,0 +1,7603 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * + * 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 <inttypes.h> +#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 <linux/time64.h> + +#include <netinet/in.h> +#include "event-parse.h" + +#include "event-parse-local.h" +#include "event-utils.h" +#include "trace-seq.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) + +/** + * init_input_buf - init buffer for parsing + * @buf: buffer to parse + * @size: the size of the buffer + * + * Initializes the internal buffer that tep_read_token() will parse. + */ +__hidden void init_input_buf(const char *buf, unsigned long long size) +{ + input_buf = buf; + input_buf_siz = size; + input_buf_ptr = 0; +} + +__hidden const char *get_input_buf(void) +{ + return input_buf; +} + +__hidden unsigned long long 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; + tep_event_handler_func func; + void *context; +}; + +struct func_params { + struct func_params *next; + enum tep_func_arg_type type; +}; + +struct tep_function_handler { + struct tep_function_handler *next; + enum tep_func_arg_type ret_type; + char *name; + tep_func_handler func; + struct func_params *params; + int nr_args; +}; + +static unsigned long long +process_defined_func(struct trace_seq *s, void *data, int size, + struct tep_event *event, struct tep_print_arg *arg); + +static void free_func_handle(struct tep_function_handler *func); + +void breakpoint(void) +{ + static int x; + x++; +} + +static struct tep_print_arg *alloc_arg(void) +{ + return calloc(1, sizeof(struct tep_print_arg)); +} + +struct tep_cmdline { + char *comm; + int pid; +}; + +static int cmdline_cmp(const void *a, const void *b) +{ + const struct tep_cmdline *ca = a; + const struct tep_cmdline *cb = b; + + if (ca->pid < cb->pid) + return -1; + if (ca->pid > cb->pid) + return 1; + + return 0; +} + +/* Looking for where to place the key */ +static int cmdline_slot_cmp(const void *a, const void *b) +{ + const struct tep_cmdline *ca = a; + const struct tep_cmdline *cb = b; + const struct tep_cmdline *cb1 = cb + 1; + + if (ca->pid < cb->pid) + return -1; + + if (ca->pid > cb->pid) { + if (ca->pid <= cb1->pid) + return 0; + return 1; + } + + return 0; +} + +struct cmdline_list { + struct cmdline_list *next; + char *comm; + int pid; +}; + +static int cmdline_init(struct tep_handle *tep) +{ + struct cmdline_list *cmdlist = tep->cmdlist; + struct cmdline_list *item; + struct tep_cmdline *cmdlines; + int i; + + cmdlines = malloc(sizeof(*cmdlines) * tep->cmdline_count); + if (!cmdlines) + return -1; + + i = 0; + while (cmdlist) { + cmdlines[i].pid = cmdlist->pid; + cmdlines[i].comm = cmdlist->comm; + i++; + item = cmdlist; + cmdlist = cmdlist->next; + free(item); + } + + qsort(cmdlines, tep->cmdline_count, sizeof(*cmdlines), cmdline_cmp); + + tep->cmdlines = cmdlines; + tep->cmdlist = NULL; + + return 0; +} + +static const char *find_cmdline(struct tep_handle *tep, int pid) +{ + const struct tep_cmdline *comm; + struct tep_cmdline key; + + if (!pid) + return "<idle>"; + + if (!tep->cmdlines && cmdline_init(tep)) + return "<not enough memory for cmdlines!>"; + + key.pid = pid; + + comm = bsearch(&key, tep->cmdlines, tep->cmdline_count, + sizeof(*tep->cmdlines), cmdline_cmp); + + if (comm) + return comm->comm; + return "<...>"; +} + +/** + * tep_is_pid_registered - return if a pid has a cmdline registered + * @tep: a handle to the trace event parser context + * @pid: The pid to check if it has a cmdline registered with. + * + * Returns true if the pid has a cmdline mapped to it + * false otherwise. + */ +bool tep_is_pid_registered(struct tep_handle *tep, int pid) +{ + const struct tep_cmdline *comm; + struct tep_cmdline key; + + if (!pid) + return true; + + if (!tep->cmdlines && cmdline_init(tep)) + return false; + + key.pid = pid; + + comm = bsearch(&key, tep->cmdlines, tep->cmdline_count, + sizeof(*tep->cmdlines), cmdline_cmp); + + if (comm) + return true; + return false; +} + +/* + * If the command lines have been converted to an array, then + * we must add this pid. This is much slower than when cmdlines + * are added before the array is initialized. + */ +static int add_new_comm(struct tep_handle *tep, + const char *comm, int pid, bool override) +{ + struct tep_cmdline *cmdlines = tep->cmdlines; + struct tep_cmdline *cmdline; + struct tep_cmdline key; + char *new_comm; + int cnt; + + if (!pid) + return 0; + + /* avoid duplicates */ + key.pid = pid; + + cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count, + sizeof(*tep->cmdlines), cmdline_cmp); + if (cmdline) { + if (!override) { + errno = EEXIST; + return -1; + } + new_comm = strdup(comm); + if (!new_comm) { + errno = ENOMEM; + return -1; + } + free(cmdline->comm); + cmdline->comm = new_comm; + + return 0; + } + + cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (tep->cmdline_count + 1)); + if (!cmdlines) { + errno = ENOMEM; + return -1; + } + tep->cmdlines = cmdlines; + + key.comm = strdup(comm); + if (!key.comm) { + errno = ENOMEM; + return -1; + } + + if (!tep->cmdline_count) { + /* no entries yet */ + tep->cmdlines[0] = key; + tep->cmdline_count++; + return 0; + } + + /* Now find where we want to store the new cmdline */ + cmdline = bsearch(&key, tep->cmdlines, tep->cmdline_count - 1, + sizeof(*tep->cmdlines), cmdline_slot_cmp); + + cnt = tep->cmdline_count; + if (cmdline) { + /* cmdline points to the one before the spot we want */ + cmdline++; + cnt -= cmdline - tep->cmdlines; + + } else { + /* The new entry is either before or after the list */ + if (key.pid > tep->cmdlines[tep->cmdline_count - 1].pid) { + tep->cmdlines[tep->cmdline_count++] = key; + return 0; + } + cmdline = &tep->cmdlines[0]; + } + memmove(cmdline + 1, cmdline, (cnt * sizeof(*cmdline))); + *cmdline = key; + + tep->cmdline_count++; + + return 0; +} + +static int _tep_register_comm(struct tep_handle *tep, + const char *comm, int pid, bool override) +{ + struct cmdline_list *item; + + if (tep->cmdlines) + return add_new_comm(tep, comm, pid, override); + + item = malloc(sizeof(*item)); + if (!item) + return -1; + + if (comm) + item->comm = strdup(comm); + else + item->comm = strdup("<...>"); + if (!item->comm) { + free(item); + return -1; + } + item->pid = pid; + item->next = tep->cmdlist; + + tep->cmdlist = item; + tep->cmdline_count++; + + return 0; +} + +/** + * tep_register_comm - register a pid / comm mapping + * @tep: a handle to the trace event parser context + * @comm: the command line to register + * @pid: the pid to map the command line to + * + * This adds a mapping to search for command line names with + * a given pid. The comm is duplicated. If a command with the same pid + * already exist, -1 is returned and errno is set to EEXIST + */ +int tep_register_comm(struct tep_handle *tep, const char *comm, int pid) +{ + return _tep_register_comm(tep, comm, pid, false); +} + +/** + * tep_override_comm - register a pid / comm mapping + * @tep: a handle to the trace event parser context + * @comm: the command line to register + * @pid: the pid to map the command line to + * + * This adds a mapping to search for command line names with + * a given pid. The comm is duplicated. If a command with the same pid + * already exist, the command string is udapted with the new one + */ +int tep_override_comm(struct tep_handle *tep, const char *comm, int pid) +{ + if (!tep->cmdlines && cmdline_init(tep)) { + errno = ENOMEM; + return -1; + } + return _tep_register_comm(tep, comm, pid, true); +} + +struct func_map { + unsigned long long addr; + char *func; + char *mod; +}; + +struct func_list { + struct func_list *next; + unsigned long long addr; + char *func; + char *mod; +}; + +static int func_cmp(const void *a, const void *b) +{ + const struct func_map *fa = a; + const struct func_map *fb = b; + + if (fa->addr < fb->addr) + return -1; + if (fa->addr > fb->addr) + return 1; + + return 0; +} + +/* + * We are searching for a record in between, not an exact + * match. + */ +static int func_bcmp(const void *a, const void *b) +{ + const struct func_map *fa = a; + const struct func_map *fb = b; + + if ((fa->addr == fb->addr) || + + (fa->addr > fb->addr && + fa->addr < (fb+1)->addr)) + return 0; + + if (fa->addr < fb->addr) + return -1; + + return 1; +} + +static int func_map_init(struct tep_handle *tep) +{ + struct func_list *funclist; + struct func_list *item; + struct func_map *func_map; + int i; + + func_map = malloc(sizeof(*func_map) * (tep->func_count + 1)); + if (!func_map) + return -1; + + funclist = tep->funclist; + + i = 0; + while (funclist) { + func_map[i].func = funclist->func; + func_map[i].addr = funclist->addr; + func_map[i].mod = funclist->mod; + i++; + item = funclist; + funclist = funclist->next; + free(item); + } + + qsort(func_map, tep->func_count, sizeof(*func_map), func_cmp); + + /* + * Add a special record at the end. + */ + func_map[tep->func_count].func = NULL; + func_map[tep->func_count].addr = 0; + func_map[tep->func_count].mod = NULL; + + tep->func_map = func_map; + tep->funclist = NULL; + + return 0; +} + +static struct func_map * +__find_func(struct tep_handle *tep, unsigned long long addr) +{ + struct func_map *func; + struct func_map key; + + if (!tep->func_map) + func_map_init(tep); + + key.addr = addr; + + func = bsearch(&key, tep->func_map, tep->func_count, + sizeof(*tep->func_map), func_bcmp); + + return func; +} + +struct func_resolver { + tep_func_resolver_t *func; + void *priv; + struct func_map map; +}; + +/** + * tep_set_function_resolver - set an alternative function resolver + * @tep: a handle to the trace event parser context + * @resolver: function to be used + * @priv: resolver function private state. + * + * Some tools may have already a way to resolve kernel functions, allow them to + * keep using it instead of duplicating all the entries inside tep->funclist. + */ +int tep_set_function_resolver(struct tep_handle *tep, + tep_func_resolver_t *func, void *priv) +{ + struct func_resolver *resolver = malloc(sizeof(*resolver)); + + if (resolver == NULL) + return -1; + + resolver->func = func; + resolver->priv = priv; + + free(tep->func_resolver); + tep->func_resolver = resolver; + + return 0; +} + +/** + * tep_reset_function_resolver - reset alternative function resolver + * @tep: a handle to the trace event parser context + * + * Stop using whatever alternative resolver was set, use the default + * one instead. + */ +void tep_reset_function_resolver(struct tep_handle *tep) +{ + free(tep->func_resolver); + tep->func_resolver = NULL; +} + +static struct func_map * +find_func(struct tep_handle *tep, unsigned long long addr) +{ + struct func_map *map; + + if (!tep->func_resolver) + return __find_func(tep, addr); + + map = &tep->func_resolver->map; + map->mod = NULL; + map->addr = addr; + map->func = tep->func_resolver->func(tep->func_resolver->priv, + &map->addr, &map->mod); + if (map->func == NULL) + return NULL; + + return map; +} + +/** + * tep_find_function - find a function by a given address + * @tep: a handle to the trace event parser context + * @addr: the address to find the function with + * + * Returns a pointer to the function stored that has the given + * address. Note, the address does not have to be exact, it + * will select the function that would contain the address. + */ +const char *tep_find_function(struct tep_handle *tep, unsigned long long addr) +{ + struct func_map *map; + + map = find_func(tep, addr); + if (!map) + return NULL; + + return map->func; +} + +/** + * tep_find_function_address - find a function address by a given address + * @tep: a handle to the trace event parser context + * @addr: the address to find the function with + * + * Returns the address the function starts at. This can be used in + * conjunction with tep_find_function to print both the function + * name and the function offset. + */ +unsigned long long +tep_find_function_address(struct tep_handle *tep, unsigned long long addr) +{ + struct func_map *map; + + map = find_func(tep, addr); + if (!map) + return 0; + + return map->addr; +} + +/** + * tep_register_function - register a function with a given address + * @tep: a handle to the trace event parser context + * @function: the function name to register + * @addr: the address the function starts at + * @mod: the kernel module the function may be in (NULL for none) + * + * This registers a function name with an address and module. + * The @func passed in is duplicated. + */ +int tep_register_function(struct tep_handle *tep, char *func, + unsigned long long addr, char *mod) +{ + struct func_list *item = malloc(sizeof(*item)); + + if (!item) + return -1; + + item->next = tep->funclist; + item->func = strdup(func); + if (!item->func) + goto out_free; + + if (mod) { + item->mod = strdup(mod); + if (!item->mod) + goto out_free_func; + } else + item->mod = NULL; + item->addr = addr; + + tep->funclist = item; + tep->func_count++; + + return 0; + +out_free_func: + free(item->func); + item->func = NULL; +out_free: + free(item); + errno = ENOMEM; + return -1; +} + +/** + * tep_print_funcs - print out the stored functions + * @tep: a handle to the trace event parser context + * + * This prints out the stored functions. + */ +void tep_print_funcs(struct tep_handle *tep) +{ + int i; + + if (!tep->func_map) + func_map_init(tep); + + for (i = 0; i < (int)tep->func_count; i++) { + printf("%016llx %s", + tep->func_map[i].addr, + tep->func_map[i].func); + if (tep->func_map[i].mod) + printf(" [%s]\n", tep->func_map[i].mod); + else + printf("\n"); + } +} + +struct printk_map { + unsigned long long addr; + char *printk; +}; + +struct printk_list { + struct printk_list *next; + unsigned long long addr; + char *printk; +}; + +static int printk_cmp(const void *a, const void *b) +{ + const struct printk_map *pa = a; + const struct printk_map *pb = b; + + if (pa->addr < pb->addr) + return -1; + if (pa->addr > pb->addr) + return 1; + + return 0; +} + +static int printk_map_init(struct tep_handle *tep) +{ + struct printk_list *printklist; + struct printk_list *item; + struct printk_map *printk_map; + int i; + + printk_map = malloc(sizeof(*printk_map) * (tep->printk_count + 1)); + if (!printk_map) + return -1; + + printklist = tep->printklist; + + i = 0; + while (printklist) { + printk_map[i].printk = printklist->printk; + printk_map[i].addr = printklist->addr; + i++; + item = printklist; + printklist = printklist->next; + free(item); + } + + qsort(printk_map, tep->printk_count, sizeof(*printk_map), printk_cmp); + + tep->printk_map = printk_map; + tep->printklist = NULL; + + return 0; +} + +static struct printk_map * +find_printk(struct tep_handle *tep, unsigned long long addr) +{ + struct printk_map *printk; + struct printk_map key; + + if (!tep->printk_map && printk_map_init(tep)) + return NULL; + + key.addr = addr; + + printk = bsearch(&key, tep->printk_map, tep->printk_count, + sizeof(*tep->printk_map), printk_cmp); + + return printk; +} + +/** + * tep_register_print_string - register a string by its address + * @tep: a handle to the trace event parser context + * @fmt: the string format to register + * @addr: the address the string was located at + * + * This registers a string by the address it was stored in the kernel. + * The @fmt passed in is duplicated. + */ +int tep_register_print_string(struct tep_handle *tep, const char *fmt, + unsigned long long addr) +{ + struct printk_list *item = malloc(sizeof(*item)); + char *p; + + if (!item) + return -1; + + item->next = tep->printklist; + item->addr = addr; + + /* Strip off quotes and '\n' from the end */ + if (fmt[0] == '"') + fmt++; + item->printk = strdup(fmt); + if (!item->printk) + goto out_free; + + p = item->printk + strlen(item->printk) - 1; + if (*p == '"') + *p = 0; + + p -= 2; + if (strcmp(p, "\\n") == 0) + *p = 0; + + tep->printklist = item; + tep->printk_count++; + + return 0; + +out_free: + free(item); + errno = ENOMEM; + return -1; +} + +/** + * tep_print_printk - print out the stored strings + * @tep: a handle to the trace event parser context + * + * This prints the string formats that were stored. + */ +void tep_print_printk(struct tep_handle *tep) +{ + int i; + + if (!tep->printk_map) + printk_map_init(tep); + + for (i = 0; i < (int)tep->printk_count; i++) { + printf("%016llx %s\n", + tep->printk_map[i].addr, + tep->printk_map[i].printk); + } +} + +static struct tep_event *alloc_event(void) +{ + return calloc(1, sizeof(struct tep_event)); +} + +static int add_event(struct tep_handle *tep, struct tep_event *event) +{ + int i; + struct tep_event **events = realloc(tep->events, sizeof(event) * + (tep->nr_events + 1)); + if (!events) + return -1; + + tep->events = events; + + for (i = 0; i < tep->nr_events; i++) { + if (tep->events[i]->id > event->id) + break; + } + if (i < tep->nr_events) + memmove(&tep->events[i + 1], + &tep->events[i], + sizeof(event) * (tep->nr_events - i)); + + tep->events[i] = event; + tep->nr_events++; + + event->tep = tep; + + return 0; +} + +static int event_item_type(enum tep_event_type type) +{ + switch (type) { + case TEP_EVENT_ITEM ... TEP_EVENT_SQUOTE: + return 1; + case TEP_EVENT_ERROR ... TEP_EVENT_DELIM: + default: + return 0; + } +} + +static void free_flag_sym(struct tep_print_flag_sym *fsym) +{ + struct tep_print_flag_sym *next; + + while (fsym) { + next = fsym->next; + free(fsym->value); + free(fsym->str); + free(fsym); + fsym = next; + } +} + +static void free_arg(struct tep_print_arg *arg) +{ + struct tep_print_arg *farg; + + if (!arg) + return; + + switch (arg->type) { + case TEP_PRINT_ATOM: + free(arg->atom.atom); + break; + case TEP_PRINT_FIELD: + free(arg->field.name); + break; + case TEP_PRINT_FLAGS: + free_arg(arg->flags.field); + free(arg->flags.delim); + free_flag_sym(arg->flags.flags); + break; + case TEP_PRINT_SYMBOL: + free_arg(arg->symbol.field); + free_flag_sym(arg->symbol.symbols); + break; + case TEP_PRINT_HEX: + case TEP_PRINT_HEX_STR: + free_arg(arg->hex.field); + free_arg(arg->hex.size); + break; + case TEP_PRINT_INT_ARRAY: + free_arg(arg->int_array.field); + free_arg(arg->int_array.count); + free_arg(arg->int_array.el_size); + break; + case TEP_PRINT_TYPE: + free(arg->typecast.type); + free_arg(arg->typecast.item); + break; + case TEP_PRINT_STRING: + case TEP_PRINT_BSTRING: + free(arg->string.string); + break; + case TEP_PRINT_BITMASK: + free(arg->bitmask.bitmask); + break; + case TEP_PRINT_DYNAMIC_ARRAY: + case TEP_PRINT_DYNAMIC_ARRAY_LEN: + free(arg->dynarray.index); + break; + case TEP_PRINT_OP: + free(arg->op.op); + free_arg(arg->op.left); + free_arg(arg->op.right); + break; + case TEP_PRINT_FUNC: + while (arg->func.args) { + farg = arg->func.args; + arg->func.args = farg->next; + free_arg(farg); + } + break; + + case TEP_PRINT_NULL: + default: + break; + } + + free(arg); +} + +static enum tep_event_type get_type(int ch) +{ + if (ch == '\n') + return TEP_EVENT_NEWLINE; + if (isspace(ch)) + return TEP_EVENT_SPACE; + if (isalnum(ch) || ch == '_') + return TEP_EVENT_ITEM; + if (ch == '\'') + return TEP_EVENT_SQUOTE; + if (ch == '"') + return TEP_EVENT_DQUOTE; + if (!isprint(ch)) + return TEP_EVENT_NONE; + if (ch == '(' || ch == ')' || ch == ',') + return TEP_EVENT_DELIM; + + return TEP_EVENT_OP; +} + +static int __read_char(void) +{ + if (input_buf_ptr >= input_buf_siz) + return -1; + + return input_buf[input_buf_ptr++]; +} + +/** + * peek_char - peek at the next character that will be read + * + * Returns the next character read, or -1 if end of buffer. + */ +__hidden int peek_char(void) +{ + if (input_buf_ptr >= input_buf_siz) + return -1; + + return input_buf[input_buf_ptr]; +} + +static int extend_token(char **tok, char *buf, int size) +{ + char *newtok = realloc(*tok, size); + + if (!newtok) { + free(*tok); + *tok = NULL; + return -1; + } + + if (!*tok) + strcpy(newtok, buf); + else + strcat(newtok, buf); + *tok = newtok; + + return 0; +} + +static enum tep_event_type force_token(const char *str, char **tok); + +static enum tep_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 tep_event_type type; + + *tok = NULL; + + + ch = __read_char(); + if (ch < 0) + return TEP_EVENT_NONE; + + type = get_type(ch); + if (type == TEP_EVENT_NONE) + return type; + + buf[i++] = ch; + + switch (type) { + case TEP_EVENT_NEWLINE: + case TEP_EVENT_DELIM: + if (asprintf(tok, "%c", ch) < 0) + return TEP_EVENT_ERROR; + + return type; + + case TEP_EVENT_OP: + switch (ch) { + case '-': + next_ch = peek_char(); + 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 TEP_EVENT_DQUOTE: + case TEP_EVENT_SQUOTE: + /* don't keep quotes */ + i--; + quote_ch = ch; + last_ch = 0; + concat: + do { + if (i == (BUFSIZ - 1)) { + buf[i] = 0; + tok_size += BUFSIZ; + + if (extend_token(tok, buf, tok_size) < 0) + return TEP_EVENT_NONE; + i = 0; + } + last_ch = ch; + ch = __read_char(); + 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 == TEP_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 TEP_EVENT_ERROR ... TEP_EVENT_SPACE: + case TEP_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 TEP_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 TEP_EVENT_NONE; + + if (type == TEP_EVENT_ITEM) { + /* + * Older versions of the kernel has a bug that + * creates invalid symbols and will break the mac80211 + * parsing. This is a work around to that bug. + * + * See Linux kernel commit: + * 811cb50baf63461ce0bdb234927046131fc7fa8b + */ + if (strcmp(*tok, "LOCAL_PR_FMT") == 0) { + free(*tok); + *tok = NULL; + return force_token("\"%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 tep_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 tep_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; +} + +/** + * free_token - free a token returned by tep_read_token + * @token: the token to free + */ +__hidden void free_token(char *tok) +{ + if (tok) + free(tok); +} + +/** + * read_token - access to utilities to use the tep parser + * @tok: The token to return + * + * This will parse tokens from the string given by + * tep_init_data(). + * + * Returns the token type. + */ +__hidden enum tep_event_type read_token(char **tok) +{ + enum tep_event_type type; + + for (;;) { + type = __read_token(tok); + if (type != TEP_EVENT_SPACE) + return type; + + free_token(*tok); + } + + /* not reached */ + *tok = NULL; + return TEP_EVENT_NONE; +} + +/* no newline */ +static enum tep_event_type read_token_item(char **tok) +{ + enum tep_event_type type; + + for (;;) { + type = __read_token(tok); + if (type != TEP_EVENT_SPACE && type != TEP_EVENT_NEWLINE) + return type; + free_token(*tok); + *tok = NULL; + } + + /* not reached */ + *tok = NULL; + return TEP_EVENT_NONE; +} + +static int test_type(enum tep_event_type type, enum tep_event_type expect) +{ + if (type != expect) { + do_warning("Error: expected type %d but read %d", + expect, type); + return -1; + } + return 0; +} + +static int test_type_token(enum tep_event_type type, const char *token, + enum tep_event_type expect, const char *expect_tok) +{ + if (type != expect) { + do_warning("Error: expected type %d 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 tep_event_type expect, char **tok, int newline_ok) +{ + enum tep_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 tep_event_type expect, char **tok) +{ + return __read_expect_type(expect, tok, 1); +} + +static int __read_expected(enum tep_event_type expect, const char *str, + int newline_ok) +{ + enum tep_event_type type; + char *token; + int ret; + + if (newline_ok) + type = read_token(&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 tep_event_type expect, const char *str) +{ + return __read_expected(expect, str, 1); +} + +static int read_expected_item(enum tep_event_type expect, const char *str) +{ + return __read_expected(expect, str, 0); +} + +static char *event_read_name(void) +{ + char *token; + + if (read_expected(TEP_EVENT_ITEM, "name") < 0) + return NULL; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + return NULL; + + if (read_expect_type(TEP_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(TEP_EVENT_ITEM, "ID") < 0) + return -1; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + return -1; + + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + + id = strtoul(token, NULL, 0); + free_token(token); + return id; + + fail: + free_token(token); + return -1; +} + +static int field_is_string(struct tep_format_field *field) +{ + if ((field->flags & TEP_FIELD_IS_ARRAY) && + (strstr(field->type, "char") || strstr(field->type, "u8") || + strstr(field->type, "s8"))) + return 1; + + return 0; +} + +static int field_is_dynamic(struct tep_format_field *field) +{ + if (strncmp(field->type, "__data_loc", 10) == 0) + return 1; + + return 0; +} + +static int field_is_long(struct tep_format_field *field) +{ + /* includes long long */ + if (strstr(field->type, "long")) + return 1; + + return 0; +} + +static unsigned int type_size(const char *name) +{ + /* This covers all TEP_FIELD_IS_STRING types. */ + static struct { + const char *type; + unsigned int size; + } table[] = { + { "u8", 1 }, + { "u16", 2 }, + { "u32", 4 }, + { "u64", 8 }, + { "s8", 1 }, + { "s16", 2 }, + { "s32", 4 }, + { "s64", 8 }, + { "char", 1 }, + { }, + }; + int i; + + for (i = 0; table[i].type; i++) { + if (!strcmp(table[i].type, name)) + return table[i].size; + } + + return 0; +} + +static int append(char **buf, const char *delim, const char *str) +{ + char *new_buf; + + new_buf = realloc(*buf, strlen(*buf) + strlen(delim) + strlen(str) + 1); + if (!new_buf) + return -1; + strcat(new_buf, delim); + strcat(new_buf, str); + *buf = new_buf; + return 0; +} + +static int event_read_fields(struct tep_event *event, struct tep_format_field **fields) +{ + struct tep_format_field *field = NULL; + enum tep_event_type type; + char *token; + char *last_token; + char *delim = " "; + int count = 0; + int ret; + + do { + unsigned int size_dynamic = 0; + + type = read_token(&token); + if (type == TEP_EVENT_NEWLINE) { + free_token(token); + return count; + } + + count++; + + if (test_type_token(type, token, TEP_EVENT_ITEM, "field")) + goto fail; + free_token(token); + + type = read_token(&token); + /* + * The ftrace fields may still use the "special" name. + * Just ignore it. + */ + if (event->flags & TEP_EVENT_FL_ISFTRACE && + type == TEP_EVENT_ITEM && strcmp(token, "special") == 0) { + free_token(token); + type = read_token(&token); + } + + if (test_type_token(type, token, TEP_EVENT_OP, ":") < 0) + goto fail; + + free_token(token); + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + + last_token = token; + + field = calloc(1, sizeof(*field)); + if (!field) + goto fail; + + field->event = event; + + /* read the rest of the type */ + for (;;) { + type = read_token(&token); + if (type == TEP_EVENT_ITEM || + (type == TEP_EVENT_OP && strcmp(token, "*") == 0) || + /* + * Some of the ftrace fields are broken and have + * an illegal "." in them. + */ + (event->flags & TEP_EVENT_FL_ISFTRACE && + type == TEP_EVENT_OP && strcmp(token, ".") == 0)) { + + if (strcmp(token, "*") == 0) + field->flags |= TEP_FIELD_IS_POINTER; + + if (field->type) { + ret = append(&field->type, delim, last_token); + free(last_token); + if (ret < 0) + goto fail; + } else + field->type = last_token; + last_token = token; + delim = " "; + continue; + } + + /* Handle __attribute__((user)) */ + if ((type == TEP_EVENT_DELIM) && + strcmp("__attribute__", last_token) == 0 && + token[0] == '(') { + int depth = 1; + int ret; + + ret = append(&field->type, " ", last_token); + ret |= append(&field->type, "", "("); + if (ret < 0) + goto fail; + + delim = " "; + while ((type = read_token(&token)) != TEP_EVENT_NONE) { + if (type == TEP_EVENT_DELIM) { + if (token[0] == '(') + depth++; + else if (token[0] == ')') + depth--; + if (!depth) + break; + ret = append(&field->type, "", token); + delim = ""; + } else { + ret = append(&field->type, delim, token); + delim = " "; + } + if (ret < 0) + goto fail; + free(last_token); + last_token = token; + } + continue; + } + break; + } + + if (!field->type) { + do_warning_event(event, "%s: no type found", __func__); + goto fail; + } + field->name = field->alias = last_token; + + if (test_type(type, TEP_EVENT_OP)) + goto fail; + + if (strcmp(token, "[") == 0) { + enum tep_event_type last_type = type; + char *brackets = token; + + field->flags |= TEP_FIELD_IS_ARRAY; + + type = read_token(&token); + + if (type == TEP_EVENT_ITEM) + field->arraylen = strtoul(token, NULL, 0); + else + field->arraylen = 0; + + while (strcmp(token, "]") != 0) { + const char *delim; + + if (last_type == TEP_EVENT_ITEM && + type == TEP_EVENT_ITEM) + delim = " "; + else + delim = ""; + + last_type = type; + + ret = append(&brackets, delim, token); + if (ret < 0) { + free(brackets); + goto fail; + } + /* We only care about the last token */ + field->arraylen = strtoul(token, NULL, 0); + free_token(token); + type = read_token(&token); + if (type == TEP_EVENT_NONE) { + free(brackets); + do_warning_event(event, "failed to find token"); + goto fail; + } + } + + free_token(token); + + ret = append(&brackets, "", "]"); + if (ret < 0) { + free(brackets); + goto fail; + } + + /* 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 == TEP_EVENT_ITEM) { + ret = append(&field->type, " ", field->name); + if (ret < 0) { + free(brackets); + goto fail; + } + ret = append(&field->type, "", brackets); + + size_dynamic = type_size(field->name); + free_token(field->name); + field->name = field->alias = token; + type = read_token(&token); + } else { + ret = append(&field->type, "", brackets); + if (ret < 0) { + free(brackets); + goto fail; + } + } + free(brackets); + } + + if (field_is_string(field)) + field->flags |= TEP_FIELD_IS_STRING; + if (field_is_dynamic(field)) + field->flags |= TEP_FIELD_IS_DYNAMIC; + if (field_is_long(field)) + field->flags |= TEP_FIELD_IS_LONG; + + if (test_type_token(type, token, TEP_EVENT_OP, ";")) + goto fail; + free_token(token); + + if (read_expected(TEP_EVENT_ITEM, "offset") < 0) + goto fail_expect; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + goto fail_expect; + + if (read_expect_type(TEP_EVENT_ITEM, &token)) + goto fail; + field->offset = strtoul(token, NULL, 0); + free_token(token); + + if (read_expected(TEP_EVENT_OP, ";") < 0) + goto fail_expect; + + if (read_expected(TEP_EVENT_ITEM, "size") < 0) + goto fail_expect; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + goto fail_expect; + + if (read_expect_type(TEP_EVENT_ITEM, &token)) + goto fail; + field->size = strtoul(token, NULL, 0); + free_token(token); + + if (read_expected(TEP_EVENT_OP, ";") < 0) + goto fail_expect; + + type = read_token(&token); + if (type != TEP_EVENT_NEWLINE) { + /* newer versions of the kernel have a "signed" type */ + if (test_type_token(type, token, TEP_EVENT_ITEM, "signed")) + goto fail; + + free_token(token); + + if (read_expected(TEP_EVENT_OP, ":") < 0) + goto fail_expect; + + if (read_expect_type(TEP_EVENT_ITEM, &token)) + goto fail; + + if (strtoul(token, NULL, 0)) + field->flags |= TEP_FIELD_IS_SIGNED; + + free_token(token); + if (read_expected(TEP_EVENT_OP, ";") < 0) + goto fail_expect; + + if (read_expect_type(TEP_EVENT_NEWLINE, &token)) + goto fail; + } + + free_token(token); + + if (field->flags & TEP_FIELD_IS_ARRAY) { + if (field->arraylen) + field->elementsize = field->size / field->arraylen; + else if (field->flags & TEP_FIELD_IS_DYNAMIC) + field->elementsize = size_dynamic; + else if (field->flags & TEP_FIELD_IS_STRING) + field->elementsize = 1; + else if (field->flags & TEP_FIELD_IS_LONG) + field->elementsize = event->tep ? + event->tep->long_size : + sizeof(long); + } else + field->elementsize = field->size; + + *fields = field; + fields = &field->next; + + } while (1); + + return 0; + +fail: + free_token(token); +fail_expect: + if (field) { + free(field->type); + free(field->name); + free(field); + } + return -1; +} + +static int event_read_format(struct tep_event *event) +{ + char *token; + int ret; + + if (read_expected_item(TEP_EVENT_ITEM, "format") < 0) + return -1; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + return -1; + + if (read_expect_type(TEP_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 tep_event_type +process_arg_token(struct tep_event *event, struct tep_print_arg *arg, + char **tok, enum tep_event_type type); + +static enum tep_event_type +process_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + enum tep_event_type type; + char *token; + + type = read_token(&token); + *tok = token; + + return process_arg_token(event, arg, tok, type); +} + +static enum tep_event_type +process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok); + +/* + * For __print_symbolic() and __print_flags, we need to completely + * evaluate the first argument, which defines what to print next. + */ +static enum tep_event_type +process_field_arg(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + enum tep_event_type type; + + type = process_arg(event, arg, tok); + + while (type == TEP_EVENT_OP) { + type = process_op(event, arg, tok); + } + + return type; +} + +static enum tep_event_type +process_cond(struct tep_event *event, struct tep_print_arg *top, char **tok) +{ + struct tep_print_arg *arg, *left, *right; + enum tep_event_type type; + char *token = NULL; + + arg = alloc_arg(); + left = alloc_arg(); + right = alloc_arg(); + + if (!arg || !left || !right) { + do_warning_event(event, "%s: not enough memory!", __func__); + /* arg will be freed at out_free */ + free_arg(left); + free_arg(right); + goto out_free; + } + + arg->type = TEP_PRINT_OP; + arg->op.left = left; + arg->op.right = right; + + *tok = NULL; + type = process_arg(event, left, &token); + + again: + if (type == TEP_EVENT_ERROR) + goto out_free; + + /* Handle other operations in the arguments */ + if (type == TEP_EVENT_OP && strcmp(token, ":") != 0) { + type = process_op(event, left, &token); + goto again; + } + + if (test_type_token(type, token, TEP_EVENT_OP, ":")) + goto out_free; + + arg->op.op = token; + + type = process_arg(event, right, &token); + + top->op.right = arg; + + *tok = token; + return type; + +out_free: + /* Top may point to itself */ + top->op.right = NULL; + free_token(token); + free_arg(arg); + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_array(struct tep_event *event, struct tep_print_arg *top, char **tok) +{ + struct tep_print_arg *arg; + enum tep_event_type type; + char *token = NULL; + + arg = alloc_arg(); + if (!arg) { + do_warning_event(event, "%s: not enough memory!", __func__); + /* '*tok' is set to top->op.op. No need to free. */ + *tok = NULL; + return TEP_EVENT_ERROR; + } + + *tok = NULL; + type = process_arg(event, arg, &token); + if (test_type_token(type, token, TEP_EVENT_OP, "]")) + goto out_free; + + top->op.right = arg; + + free_token(token); + type = read_token_item(&token); + *tok = token; + + return type; + +out_free: + free_token(token); + free_arg(arg); + return TEP_EVENT_ERROR; +} + +static int get_op_prio(char *op) +{ + if (!op[1]) { + switch (op[0]) { + case '~': + case '!': + return 4; + case '*': + case '/': + case '%': + return 6; + case '+': + case '-': + return 7; + /* '>>' and '<<' are 8 */ + case '<': + case '>': + return 9; + /* '==' and '!=' are 10 */ + case '&': + return 11; + case '^': + return 12; + case '|': + return 13; + case '?': + return 16; + default: + do_warning("unknown op '%c'", op[0]); + return -1; + } + } else { + if (strcmp(op, "++") == 0 || + strcmp(op, "--") == 0) { + return 3; + } else if (strcmp(op, ">>") == 0 || + strcmp(op, "<<") == 0) { + return 8; + } else if (strcmp(op, ">=") == 0 || + strcmp(op, "<=") == 0) { + return 9; + } else if (strcmp(op, "==") == 0 || + strcmp(op, "!=") == 0) { + return 10; + } else if (strcmp(op, "&&") == 0) { + return 14; + } else if (strcmp(op, "||") == 0) { + return 15; + } else { + do_warning("unknown op '%s'", op); + return -1; + } + } +} + +static int set_op_prio(struct tep_print_arg *arg) +{ + + /* single ops are the greatest */ + if (!arg->op.left || arg->op.left->type == TEP_PRINT_NULL) + arg->op.prio = 0; + else + arg->op.prio = get_op_prio(arg->op.op); + + return arg->op.prio; +} + +/* Note, *tok does not get freed, but will most likely be saved */ +static enum tep_event_type +process_op(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + struct tep_print_arg *left, *right = NULL; + enum tep_event_type type; + char *token; + + /* the op is passed in via tok */ + token = *tok; + + if (arg->type == TEP_PRINT_OP && !arg->op.left) { + /* handle single op */ + 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 = TEP_PRINT_NULL; + arg->op.left = left; + + right = alloc_arg(); + if (!right) + goto out_warn_free; + + arg->op.right = right; + + /* do not free the token, it belongs to an op */ + *tok = NULL; + type = process_arg(event, right, tok); + + } else if (strcmp(token, "?") == 0) { + + left = alloc_arg(); + if (!left) + goto out_warn_free; + + /* copy the top arg to the left */ + *left = *arg; + + arg->type = TEP_PRINT_OP; + arg->op.op = token; + arg->op.left = left; + arg->op.prio = 0; + + /* it will set arg->op.right */ + type = process_cond(event, arg, tok); + + } else if (strcmp(token, ">>") == 0 || + strcmp(token, "<<") == 0 || + strcmp(token, "&") == 0 || + strcmp(token, "|") == 0 || + strcmp(token, "&&") == 0 || + strcmp(token, "||") == 0 || + strcmp(token, "-") == 0 || + strcmp(token, "+") == 0 || + strcmp(token, "*") == 0 || + strcmp(token, "^") == 0 || + strcmp(token, "/") == 0 || + strcmp(token, "%") == 0 || + strcmp(token, "<") == 0 || + strcmp(token, ">") == 0 || + strcmp(token, "<=") == 0 || + strcmp(token, ">=") == 0 || + strcmp(token, "==") == 0 || + strcmp(token, "!=") == 0) { + + left = alloc_arg(); + if (!left) + goto out_warn_free; + + /* copy the top arg to the left */ + *left = *arg; + + arg->type = TEP_PRINT_OP; + arg->op.op = token; + arg->op.left = left; + arg->op.right = NULL; + + if (set_op_prio(arg) == -1) { + event->flags |= TEP_EVENT_FL_FAILED; + /* arg->op.op (= token) will be freed at out_free */ + arg->op.op = NULL; + goto out_free; + } + + type = read_token_item(&token); + *tok = token; + + /* could just be a type pointer */ + if ((strcmp(arg->op.op, "*") == 0) && + type == TEP_EVENT_DELIM && (strcmp(token, ")") == 0)) { + int ret; + + if (left->type != TEP_PRINT_ATOM) { + do_warning_event(event, "bad pointer type"); + goto out_free; + } + ret = append(&left->atom.atom, " ", "*"); + if (ret < 0) + goto out_warn_free; + + free(arg->op.op); + *arg = *left; + free(left); + + return type; + } + + right = alloc_arg(); + if (!right) + goto out_warn_free; + + type = process_arg_token(event, right, tok, type); + if (type == TEP_EVENT_ERROR) { + free_arg(right); + /* token was freed in process_arg_token() via *tok */ + token = NULL; + goto out_free; + } + + if (right->type == TEP_PRINT_OP && + get_op_prio(arg->op.op) < get_op_prio(right->op.op)) { + struct tep_print_arg tmp; + + /* rotate ops according to the priority */ + arg->op.right = right->op.left; + + tmp = *arg; + *arg = *right; + *right = tmp; + + arg->op.left = right; + } else { + arg->op.right = right; + } + + } else if (strcmp(token, "[") == 0) { + + left = alloc_arg(); + if (!left) + goto out_warn_free; + + *left = *arg; + + arg->type = TEP_PRINT_OP; + arg->op.op = token; + arg->op.left = left; + + arg->op.prio = 0; + + /* it will set arg->op.right */ + type = process_array(event, arg, tok); + + } else { + do_warning_event(event, "unknown op '%s'", token); + event->flags |= TEP_EVENT_FL_FAILED; + /* the arg is now the left side */ + goto out_free; + } + + if (type == TEP_EVENT_OP && strcmp(*tok, ":") != 0) { + int prio; + + /* higher prios need to be closer to the root */ + prio = get_op_prio(*tok); + + if (prio > arg->op.prio) + return process_op(event, arg, tok); + + return process_op(event, right, tok); + } + + return type; + +out_warn_free: + do_warning_event(event, "%s: not enough memory!", __func__); +out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_entry(struct tep_event *event __maybe_unused, struct tep_print_arg *arg, + char **tok) +{ + enum tep_event_type type; + char *field; + char *token; + + if (read_expected(TEP_EVENT_OP, "->") < 0) + goto out_err; + + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto out_free; + field = token; + + arg->type = TEP_PRINT_FIELD; + arg->field.name = field; + + if (is_flag_field) { + arg->field.field = tep_find_any_field(event, arg->field.name); + arg->field.field->flags |= TEP_FIELD_IS_FLAG; + is_flag_field = 0; + } else if (is_symbolic_field) { + arg->field.field = tep_find_any_field(event, arg->field.name); + arg->field.field->flags |= TEP_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 TEP_EVENT_ERROR; +} + +static int alloc_and_process_delim(struct tep_event *event, char *next_token, + struct tep_print_arg **print_arg) +{ + struct tep_print_arg *field; + enum tep_event_type type; + char *token; + int ret = 0; + + field = alloc_arg(); + if (!field) { + do_warning_event(event, "%s: not enough memory!", __func__); + errno = ENOMEM; + return -1; + } + + type = process_arg(event, field, &token); + + if (test_type_token(type, token, TEP_EVENT_DELIM, next_token)) { + errno = EINVAL; + ret = -1; + free_arg(field); + goto out_free_token; + } + + *print_arg = field; + +out_free_token: + free_token(token); + + return ret; +} + +static char *arg_eval (struct tep_print_arg *arg); + +static unsigned long long +eval_type_str(unsigned long long val, const char *type, int pointer) +{ + int sign = 0; + char *ref; + int len; + + len = strlen(type); + + if (pointer) { + + if (type[len-1] != '*') { + do_warning("pointer expected with non pointer type"); + return val; + } + + ref = malloc(len); + if (!ref) { + do_warning("%s: not enough memory!", __func__); + return val; + } + memcpy(ref, type, len); + + /* chop off the " *" */ + ref[len - 2] = 0; + + val = eval_type_str(val, ref, 0); + free(ref); + return val; + } + + /* check if this is a pointer */ + if (type[len - 1] == '*') + return val; + + /* Try to figure out the arg size*/ + if (strncmp(type, "struct", 6) == 0) + /* all bets off */ + return val; + + if (strcmp(type, "u8") == 0) + return val & 0xff; + + if (strcmp(type, "u16") == 0) + return val & 0xffff; + + if (strcmp(type, "u32") == 0) + return val & 0xffffffff; + + if (strcmp(type, "u64") == 0 || + strcmp(type, "s64") == 0) + return val; + + if (strcmp(type, "s8") == 0) + return (unsigned long long)(char)val & 0xff; + + if (strcmp(type, "s16") == 0) + return (unsigned long long)(short)val & 0xffff; + + if (strcmp(type, "s32") == 0) + return (unsigned long long)(int)val & 0xffffffff; + + if (strncmp(type, "unsigned ", 9) == 0) { + sign = 0; + type += 9; + } + + if (strcmp(type, "char") == 0) { + if (sign) + return (unsigned long long)(char)val & 0xff; + else + return val & 0xff; + } + + if (strcmp(type, "short") == 0) { + if (sign) + return (unsigned long long)(short)val & 0xffff; + else + return val & 0xffff; + } + + if (strcmp(type, "int") == 0) { + if (sign) + return (unsigned long long)(int)val & 0xffffffff; + else + return val & 0xffffffff; + } + + return val; +} + +/* + * Try to figure out the type. + */ +static unsigned long long +eval_type(unsigned long long val, struct tep_print_arg *arg, int pointer) +{ + if (arg->type != TEP_PRINT_TYPE) { + do_warning("expected type argument"); + return 0; + } + + return eval_type_str(val, arg->typecast.type, pointer); +} + +static int arg_num_eval(struct tep_print_arg *arg, long long *val) +{ + long long left, right; + int ret = 1; + + switch (arg->type) { + case TEP_PRINT_ATOM: + *val = strtoll(arg->atom.atom, NULL, 0); + break; + case TEP_PRINT_TYPE: + ret = arg_num_eval(arg->typecast.item, val); + if (!ret) + break; + *val = eval_type(*val, arg, 0); + break; + case TEP_PRINT_OP: + switch (arg->op.op[0]) { + case '|': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + if (arg->op.op[1]) + *val = left || right; + else + *val = left | right; + break; + case '&': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + if (arg->op.op[1]) + *val = left && right; + else + *val = left & right; + break; + case '<': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + switch (arg->op.op[1]) { + case 0: + *val = left < right; + break; + case '<': + *val = left << right; + break; + case '=': + *val = left <= right; + break; + default: + do_warning("unknown op '%s'", arg->op.op); + ret = 0; + } + break; + case '>': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + switch (arg->op.op[1]) { + case 0: + *val = left > right; + break; + case '>': + *val = left >> right; + break; + case '=': + *val = left >= right; + break; + default: + do_warning("unknown op '%s'", arg->op.op); + ret = 0; + } + break; + case '=': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + + if (arg->op.op[1] != '=') { + do_warning("unknown op '%s'", arg->op.op); + ret = 0; + } else + *val = left == right; + break; + case '!': + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + + switch (arg->op.op[1]) { + case '=': + *val = left != right; + break; + default: + do_warning("unknown op '%s'", arg->op.op); + ret = 0; + } + break; + case '-': + /* check for negative */ + if (arg->op.left->type == TEP_PRINT_NULL) + left = 0; + else + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + *val = left - right; + break; + case '+': + if (arg->op.left->type == TEP_PRINT_NULL) + left = 0; + else + ret = arg_num_eval(arg->op.left, &left); + if (!ret) + break; + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + *val = left + right; + break; + case '~': + ret = arg_num_eval(arg->op.right, &right); + if (!ret) + break; + *val = ~right; + break; + default: + do_warning("unknown op '%s'", arg->op.op); + ret = 0; + } + break; + + case TEP_PRINT_NULL: + case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL: + case TEP_PRINT_STRING: + case TEP_PRINT_BSTRING: + case TEP_PRINT_BITMASK: + default: + do_warning("invalid eval type %d", arg->type); + ret = 0; + + } + return ret; +} + +static char *arg_eval (struct tep_print_arg *arg) +{ + long long val; + static char buf[24]; + + switch (arg->type) { + case TEP_PRINT_ATOM: + return arg->atom.atom; + case TEP_PRINT_TYPE: + return arg_eval(arg->typecast.item); + case TEP_PRINT_OP: + if (!arg_num_eval(arg, &val)) + break; + sprintf(buf, "%lld", val); + return buf; + + case TEP_PRINT_NULL: + case TEP_PRINT_FIELD ... TEP_PRINT_SYMBOL: + case TEP_PRINT_STRING: + case TEP_PRINT_BSTRING: + case TEP_PRINT_BITMASK: + default: + do_warning("invalid eval type %d", arg->type); + break; + } + + return NULL; +} + +static enum tep_event_type +process_fields(struct tep_event *event, struct tep_print_flag_sym **list, char **tok) +{ + enum tep_event_type type; + struct tep_print_arg *arg = NULL; + struct tep_print_flag_sym *field; + char *token = *tok; + char *value; + + do { + free_token(token); + type = read_token_item(&token); + if (test_type_token(type, token, TEP_EVENT_OP, "{")) + break; + + arg = alloc_arg(); + if (!arg) + goto out_free; + + free_token(token); + type = process_arg(event, arg, &token); + + if (type == TEP_EVENT_OP) + type = process_op(event, arg, &token); + + if (type == TEP_EVENT_ERROR) + goto out_free; + + if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) + goto out_free; + + field = calloc(1, sizeof(*field)); + if (!field) + goto out_free; + + value = arg_eval(arg); + if (value == NULL) + goto out_free_field; + field->value = strdup(value); + if (field->value == NULL) + goto out_free_field; + + free_arg(arg); + arg = alloc_arg(); + if (!arg) + goto out_free; + + free_token(token); + type = process_arg(event, arg, &token); + if (test_type_token(type, token, TEP_EVENT_OP, "}")) + goto out_free_field; + + value = arg_eval(arg); + if (value == NULL) + goto out_free_field; + field->str = strdup(value); + if (field->str == NULL) + goto out_free_field; + free_arg(arg); + arg = NULL; + + *list = field; + list = &field->next; + + free_token(token); + type = read_token_item(&token); + } while (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0); + + *tok = token; + return type; + +out_free_field: + free_flag_sym(field); +out_free: + free_arg(arg); + free_token(token); + *tok = NULL; + + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_flags(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + struct tep_print_arg *field; + enum tep_event_type type; + char *token = NULL; + + memset(arg, 0, sizeof(*arg)); + arg->type = TEP_PRINT_FLAGS; + + field = alloc_arg(); + if (!field) { + do_warning_event(event, "%s: not enough memory!", __func__); + goto out_free; + } + + type = process_field_arg(event, field, &token); + + /* Handle operations in the first argument */ + while (type == TEP_EVENT_OP) + type = process_op(event, field, &token); + + if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) + goto out_free_field; + free_token(token); + + arg->flags.field = field; + + type = read_token_item(&token); + if (event_item_type(type)) { + arg->flags.delim = token; + type = read_token_item(&token); + } + + if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) + goto out_free; + + type = process_fields(event, &arg->flags.flags, &token); + if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) + goto out_free; + + free_token(token); + type = read_token_item(tok); + return type; + +out_free_field: + free_arg(field); +out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_symbols(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + struct tep_print_arg *field; + enum tep_event_type type; + char *token = NULL; + + memset(arg, 0, sizeof(*arg)); + arg->type = TEP_PRINT_SYMBOL; + + field = alloc_arg(); + if (!field) { + do_warning_event(event, "%s: not enough memory!", __func__); + goto out_free; + } + + type = process_field_arg(event, field, &token); + + if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) + goto out_free_field; + + arg->symbol.field = field; + + type = process_fields(event, &arg->symbol.symbols, &token); + if (test_type_token(type, token, TEP_EVENT_DELIM, ")")) + goto out_free; + + free_token(token); + type = read_token_item(tok); + return type; + +out_free_field: + free_arg(field); +out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_hex_common(struct tep_event *event, struct tep_print_arg *arg, + char **tok, enum tep_print_arg_type type) +{ + memset(arg, 0, sizeof(*arg)); + arg->type = type; + + if (alloc_and_process_delim(event, ",", &arg->hex.field)) + goto out; + + if (alloc_and_process_delim(event, ")", &arg->hex.size)) + goto free_field; + + return read_token_item(tok); + +free_field: + free_arg(arg->hex.field); + arg->hex.field = NULL; +out: + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_hex(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + return process_hex_common(event, arg, tok, TEP_PRINT_HEX); +} + +static enum tep_event_type +process_hex_str(struct tep_event *event, struct tep_print_arg *arg, + char **tok) +{ + return process_hex_common(event, arg, tok, TEP_PRINT_HEX_STR); +} + +static enum tep_event_type +process_int_array(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + memset(arg, 0, sizeof(*arg)); + arg->type = TEP_PRINT_INT_ARRAY; + + if (alloc_and_process_delim(event, ",", &arg->int_array.field)) + goto out; + + if (alloc_and_process_delim(event, ",", &arg->int_array.count)) + goto free_field; + + if (alloc_and_process_delim(event, ")", &arg->int_array.el_size)) + goto free_size; + + return read_token_item(tok); + +free_size: + free_arg(arg->int_array.count); + arg->int_array.count = NULL; +free_field: + free_arg(arg->int_array.field); + arg->int_array.field = NULL; +out: + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_dynamic_array(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + struct tep_format_field *field; + enum tep_event_type type; + char *token; + + memset(arg, 0, sizeof(*arg)); + arg->type = TEP_PRINT_DYNAMIC_ARRAY; + + /* + * The item within the parenthesis is another field that holds + * the index into where the array starts. + */ + type = read_token(&token); + *tok = token; + if (type != TEP_EVENT_ITEM) + goto out_free; + + /* Find the field */ + + field = tep_find_field(event, token); + if (!field) + goto out_free; + + arg->dynarray.field = field; + arg->dynarray.index = 0; + + if (read_expected(TEP_EVENT_DELIM, ")") < 0) + goto out_free; + + free_token(token); + type = read_token_item(&token); + *tok = token; + if (type != TEP_EVENT_OP || strcmp(token, "[") != 0) + return type; + + free_token(token); + arg = alloc_arg(); + if (!arg) { + do_warning_event(event, "%s: not enough memory!", __func__); + *tok = NULL; + return TEP_EVENT_ERROR; + } + + type = process_arg(event, arg, &token); + if (type == TEP_EVENT_ERROR) + goto out_free_arg; + + if (!test_type_token(type, token, TEP_EVENT_OP, "]")) + goto out_free_arg; + + free_token(token); + type = read_token_item(tok); + return type; + + out_free_arg: + free_arg(arg); + out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_dynamic_array_len(struct tep_event *event, struct tep_print_arg *arg, + char **tok) +{ + struct tep_format_field *field; + enum tep_event_type type; + char *token; + + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto out_free; + + arg->type = TEP_PRINT_DYNAMIC_ARRAY_LEN; + + /* Find the field */ + field = tep_find_field(event, token); + if (!field) + goto out_free; + + arg->dynarray.field = field; + arg->dynarray.index = 0; + + if (read_expected(TEP_EVENT_DELIM, ")") < 0) + goto out_err; + + free_token(token); + type = read_token(&token); + *tok = token; + + return type; + + out_free: + free_token(token); + out_err: + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_paren(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + struct tep_print_arg *item_arg; + enum tep_event_type type; + char *token; + + type = process_arg(event, arg, &token); + + if (type == TEP_EVENT_ERROR) + goto out_free; + + if (type == TEP_EVENT_OP) + type = process_op(event, arg, &token); + + if (type == TEP_EVENT_ERROR) + goto out_free; + + if (test_type_token(type, token, TEP_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 == TEP_EVENT_DELIM && strcmp(token, "(") == 0)) { + + /* make this a typecast and contine */ + + /* prevous must be an atom */ + if (arg->type != TEP_PRINT_ATOM) { + do_warning_event(event, "previous needed to be TEP_PRINT_ATOM"); + goto out_free; + } + + item_arg = alloc_arg(); + if (!item_arg) { + do_warning_event(event, "%s: not enough memory!", + __func__); + goto out_free; + } + + arg->type = TEP_PRINT_TYPE; + arg->typecast.type = arg->atom.atom; + arg->typecast.item = item_arg; + type = process_arg_token(event, item_arg, &token, type); + + } + + *tok = token; + return type; + + out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + + +static enum tep_event_type +process_str(struct tep_event *event __maybe_unused, struct tep_print_arg *arg, + char **tok) +{ + enum tep_event_type type; + char *token; + + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto out_free; + + arg->type = TEP_PRINT_STRING; + arg->string.string = token; + arg->string.offset = -1; + + if (read_expected(TEP_EVENT_DELIM, ")") < 0) + goto out_err; + + type = read_token(&token); + *tok = token; + + return type; + + out_free: + free_token(token); + out_err: + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_bitmask(struct tep_event *event __maybe_unused, struct tep_print_arg *arg, + char **tok) +{ + enum tep_event_type type; + char *token; + + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto out_free; + + arg->type = TEP_PRINT_BITMASK; + arg->bitmask.bitmask = token; + arg->bitmask.offset = -1; + + if (read_expected(TEP_EVENT_DELIM, ")") < 0) + goto out_err; + + type = read_token(&token); + *tok = token; + + return type; + + out_free: + free_token(token); + out_err: + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static struct tep_function_handler * +find_func_handler(struct tep_handle *tep, char *func_name) +{ + struct tep_function_handler *func; + + if (!tep) + return NULL; + + for (func = tep->func_handlers; func; func = func->next) { + if (strcmp(func->name, func_name) == 0) + break; + } + + return func; +} + +static void remove_func_handler(struct tep_handle *tep, char *func_name) +{ + struct tep_function_handler *func; + struct tep_function_handler **next; + + next = &tep->func_handlers; + while ((func = *next)) { + if (strcmp(func->name, func_name) == 0) { + *next = func->next; + free_func_handle(func); + break; + } + next = &func->next; + } +} + +static enum tep_event_type +process_func_handler(struct tep_event *event, struct tep_function_handler *func, + struct tep_print_arg *arg, char **tok) +{ + struct tep_print_arg **next_arg; + struct tep_print_arg *farg; + enum tep_event_type type; + char *token; + int i; + + arg->type = TEP_PRINT_FUNC; + arg->func.func = func; + + *tok = NULL; + + next_arg = &(arg->func.args); + for (i = 0; i < func->nr_args; i++) { + farg = alloc_arg(); + if (!farg) { + do_warning_event(event, "%s: not enough memory!", + __func__); + return TEP_EVENT_ERROR; + } + + type = process_arg(event, farg, &token); + if (i < (func->nr_args - 1)) { + if (type != TEP_EVENT_DELIM || strcmp(token, ",") != 0) { + do_warning_event(event, + "Error: function '%s()' expects %d arguments but event %s only uses %d", + func->name, func->nr_args, + event->name, i + 1); + goto err; + } + } else { + if (type != TEP_EVENT_DELIM || strcmp(token, ")") != 0) { + do_warning_event(event, + "Error: function '%s()' only expects %d arguments but event %s has more", + func->name, func->nr_args, event->name); + goto err; + } + } + + *next_arg = farg; + next_arg = &(farg->next); + free_token(token); + } + + type = read_token(&token); + *tok = token; + + return type; + +err: + free_arg(farg); + free_token(token); + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_builtin_expect(struct tep_event *event, struct tep_print_arg *arg, char **tok) +{ + enum tep_event_type type; + char *token = NULL; + + /* Handle __builtin_expect( cond, #) */ + type = process_arg(event, arg, &token); + + if (type != TEP_EVENT_DELIM || token[0] != ',') + goto out_free; + + free_token(token); + + /* We don't care what the second parameter is of the __builtin_expect() */ + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto out_free; + + if (read_expected(TEP_EVENT_DELIM, ")") < 0) + goto out_free; + + free_token(token); + type = read_token_item(tok); + return type; + +out_free: + free_token(token); + *tok = NULL; + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_function(struct tep_event *event, struct tep_print_arg *arg, + char *token, char **tok) +{ + struct tep_function_handler *func; + + if (strcmp(token, "__print_flags") == 0) { + free_token(token); + is_flag_field = 1; + return process_flags(event, arg, tok); + } + if (strcmp(token, "__print_symbolic") == 0) { + free_token(token); + is_symbolic_field = 1; + return process_symbols(event, arg, tok); + } + if (strcmp(token, "__print_hex") == 0) { + free_token(token); + return process_hex(event, arg, tok); + } + if (strcmp(token, "__print_hex_str") == 0) { + free_token(token); + return process_hex_str(event, arg, tok); + } + if (strcmp(token, "__print_array") == 0) { + free_token(token); + return process_int_array(event, arg, tok); + } + if (strcmp(token, "__get_str") == 0) { + free_token(token); + return process_str(event, arg, tok); + } + if (strcmp(token, "__get_bitmask") == 0) { + free_token(token); + return process_bitmask(event, arg, tok); + } + if (strcmp(token, "__get_dynamic_array") == 0) { + free_token(token); + return process_dynamic_array(event, arg, tok); + } + if (strcmp(token, "__get_dynamic_array_len") == 0) { + free_token(token); + return process_dynamic_array_len(event, arg, tok); + } + if (strcmp(token, "__builtin_expect") == 0) { + free_token(token); + return process_builtin_expect(event, arg, tok); + } + + func = find_func_handler(event->tep, token); + if (func) { + free_token(token); + return process_func_handler(event, func, arg, tok); + } + + do_warning_event(event, "function %s not defined", token); + free_token(token); + return TEP_EVENT_ERROR; +} + +static enum tep_event_type +process_arg_token(struct tep_event *event, struct tep_print_arg *arg, + char **tok, enum tep_event_type type) +{ + char *token; + char *atom; + + token = *tok; + + switch (type) { + case TEP_EVENT_ITEM: + if (strcmp(token, "REC") == 0) { + free_token(token); + type = process_entry(event, arg, &token); + break; + } + atom = token; + /* test the next token */ + type = read_token_item(&token); + + /* + * If the next token is a parenthesis, then this + * is a function. + */ + if (type == TEP_EVENT_DELIM && strcmp(token, "(") == 0) { + free_token(token); + token = NULL; + /* this will free atom. */ + type = process_function(event, arg, atom, &token); + break; + } + /* atoms can be more than one token long */ + while (type == TEP_EVENT_ITEM) { + int ret; + + ret = append(&atom, " ", token); + if (ret < 0) { + free(atom); + *tok = NULL; + free_token(token); + return TEP_EVENT_ERROR; + } + free_token(token); + type = read_token_item(&token); + } + + arg->type = TEP_PRINT_ATOM; + arg->atom.atom = atom; + break; + + case TEP_EVENT_DQUOTE: + case TEP_EVENT_SQUOTE: + arg->type = TEP_PRINT_ATOM; + arg->atom.atom = token; + type = read_token_item(&token); + break; + case TEP_EVENT_DELIM: + if (strcmp(token, "(") == 0) { + free_token(token); + type = process_paren(event, arg, &token); + break; + } + case TEP_EVENT_OP: + /* handle single ops */ + arg->type = TEP_PRINT_OP; + arg->op.op = token; + arg->op.left = NULL; + type = process_op(event, arg, &token); + + /* On error, the op is freed */ + if (type == TEP_EVENT_ERROR) + arg->op.op = NULL; + + /* return error type if errored */ + break; + + case TEP_EVENT_ERROR ... TEP_EVENT_NEWLINE: + default: + do_warning_event(event, "unexpected type %d", type); + return TEP_EVENT_ERROR; + } + *tok = token; + + return type; +} + +static int event_read_print_args(struct tep_event *event, struct tep_print_arg **list) +{ + enum tep_event_type type = TEP_EVENT_ERROR; + struct tep_print_arg *arg; + char *token; + int args = 0; + + do { + if (type == TEP_EVENT_NEWLINE) { + type = read_token_item(&token); + continue; + } + + arg = alloc_arg(); + if (!arg) { + do_warning_event(event, "%s: not enough memory!", + __func__); + return -1; + } + + type = process_arg(event, arg, &token); + + if (type == TEP_EVENT_ERROR) { + free_token(token); + free_arg(arg); + return -1; + } + + *list = arg; + args++; + + if (type == TEP_EVENT_OP) { + type = process_op(event, arg, &token); + free_token(token); + if (type == TEP_EVENT_ERROR) { + *list = NULL; + free_arg(arg); + return -1; + } + list = &arg->next; + continue; + } + + if (type == TEP_EVENT_DELIM && strcmp(token, ",") == 0) { + free_token(token); + *list = arg; + list = &arg->next; + continue; + } + break; + } while (type != TEP_EVENT_NONE); + + if (type != TEP_EVENT_NONE && type != TEP_EVENT_ERROR) + free_token(token); + + return args; +} + +static int event_read_print(struct tep_event *event) +{ + enum tep_event_type type; + char *token; + int ret; + + if (read_expected_item(TEP_EVENT_ITEM, "print") < 0) + return -1; + + if (read_expected(TEP_EVENT_ITEM, "fmt") < 0) + return -1; + + if (read_expected(TEP_EVENT_OP, ":") < 0) + return -1; + + if (read_expect_type(TEP_EVENT_DQUOTE, &token) < 0) + goto fail; + + concat: + event->print_fmt.format = token; + event->print_fmt.args = NULL; + + /* ok to have no arg */ + type = read_token_item(&token); + + if (type == TEP_EVENT_NONE) + return 0; + + /* Handle concatenation of print lines */ + if (type == TEP_EVENT_DQUOTE) { + char *cat; + + if (asprintf(&cat, "%s%s", event->print_fmt.format, token) < 0) + goto fail; + free_token(token); + free_token(event->print_fmt.format); + event->print_fmt.format = NULL; + token = cat; + goto concat; + } + + if (test_type_token(type, token, TEP_EVENT_DELIM, ",")) + goto fail; + + free_token(token); + + ret = event_read_print_args(event, &event->print_fmt.args); + if (ret < 0) + return -1; + + return ret; + + fail: + free_token(token); + return -1; +} + +/** + * tep_find_common_field - return a common field by event + * @event: handle for the event + * @name: the name of the common field to return + * + * Returns a common field from the event by the given @name. + * This only searches the common fields and not all field. + */ +struct tep_format_field * +tep_find_common_field(struct tep_event *event, const char *name) +{ + struct tep_format_field *format; + + for (format = event->format.common_fields; + format; format = format->next) { + if (strcmp(format->name, name) == 0) + break; + } + + return format; +} + +/** + * tep_find_field - find a non-common field + * @event: handle for the event + * @name: the name of the non-common field + * + * Returns a non-common field by the given @name. + * This does not search common fields. + */ +struct tep_format_field * +tep_find_field(struct tep_event *event, const char *name) +{ + struct tep_format_field *format; + + for (format = event->format.fields; + format; format = format->next) { + if (strcmp(format->name, name) == 0) + break; + } + + return format; +} + +/** + * tep_find_any_field - find any field by name + * @event: handle for the event + * @name: the name of the field + * + * Returns a field by the given @name. + * This searches the common field names first, then + * the non-common ones if a common one was not found. + */ +struct tep_format_field * +tep_find_any_field(struct tep_event *event, const char *name) +{ + struct tep_format_field *format; + + format = tep_find_common_field(event, name); + if (format) + return format; + return tep_find_field(event, name); +} + +/** + * tep_read_number - read a number from data + * @tep: a handle to the trace event parser context + * @ptr: the raw data + * @size: the size of the data that holds the number + * + * Returns the number (converted to host) from the + * raw data. + */ +unsigned long long tep_read_number(struct tep_handle *tep, + const void *ptr, int size) +{ + unsigned long long val; + + switch (size) { + case 1: + return *(unsigned char *)ptr; + case 2: + return data2host2(tep, *(unsigned short *)ptr); + case 4: + return data2host4(tep, *(unsigned int *)ptr); + case 8: + memcpy(&val, (ptr), sizeof(unsigned long long)); + return data2host8(tep, val); + default: + /* BUG! */ + return 0; + } +} + +/** + * tep_read_number_field - read a number from data + * @field: a handle to the field + * @data: the raw data to read + * @value: the value to place the number in + * + * Reads raw data according to a field offset and size, + * and translates it into @value. + * + * Returns 0 on success, -1 otherwise. + */ +int tep_read_number_field(struct tep_format_field *field, const void *data, + unsigned long long *value) +{ + if (!field) + return -1; + switch (field->size) { + case 1: + case 2: + case 4: + case 8: + *value = tep_read_number(field->event->tep, + data + field->offset, field->size); + return 0; + default: + return -1; + } +} + +static int get_common_info(struct tep_handle *tep, + const char *type, int *offset, int *size) +{ + struct tep_event *event; + struct tep_format_field *field; + + /* + * All events should have the same common elements. + * Pick any event to find where the type is; + */ + if (!tep->events) { + do_warning("no event_list!"); + return -1; + } + + event = tep->events[0]; + field = tep_find_common_field(event, type); + if (!field) + return -1; + + *offset = field->offset; + *size = field->size; + + return 0; +} + +static int __parse_common(struct tep_handle *tep, void *data, + int *size, int *offset, const char *name) +{ + int ret; + + if (!*size) { + ret = get_common_info(tep, name, offset, size); + if (ret < 0) + return ret; + } + return tep_read_number(tep, data + *offset, *size); +} + +static int trace_parse_common_type(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->type_size, &tep->type_offset, + "common_type"); +} + +static int parse_common_pid(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->pid_size, &tep->pid_offset, + "common_pid"); +} + +static int parse_common_pc(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->pc_size, &tep->pc_offset, + "common_preempt_count"); +} + +static int parse_common_flags(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->flags_size, &tep->flags_offset, + "common_flags"); +} + +static int parse_common_lock_depth(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->ld_size, &tep->ld_offset, + "common_lock_depth"); +} + +static int parse_common_migrate_disable(struct tep_handle *tep, void *data) +{ + return __parse_common(tep, data, + &tep->ld_size, &tep->ld_offset, + "common_migrate_disable"); +} + +static int events_id_cmp(const void *a, const void *b); + +/** + * tep_find_event - find an event by given id + * @tep: a handle to the trace event parser context + * @id: the id of the event + * + * Returns an event that has a given @id. + */ +struct tep_event *tep_find_event(struct tep_handle *tep, int id) +{ + struct tep_event **eventptr; + struct tep_event key; + struct tep_event *pkey = &key; + + /* Check cache first */ + if (tep->last_event && tep->last_event->id == id) + return tep->last_event; + + key.id = id; + + eventptr = bsearch(&pkey, tep->events, tep->nr_events, + sizeof(*tep->events), events_id_cmp); + + if (eventptr) { + tep->last_event = *eventptr; + return *eventptr; + } + + return NULL; +} + +/** + * tep_find_event_by_name - find an event by given name + * @tep: a handle to the trace event parser context + * @sys: the system name to search for + * @name: the name of the event to search for + * + * This returns an event with a given @name and under the system + * @sys. If @sys is NULL the first event with @name is returned. + */ +struct tep_event * +tep_find_event_by_name(struct tep_handle *tep, + const char *sys, const char *name) +{ + struct tep_event *event = NULL; + int i; + + if (tep->last_event && + strcmp(tep->last_event->name, name) == 0 && + (!sys || strcmp(tep->last_event->system, sys) == 0)) + return tep->last_event; + + for (i = 0; i < tep->nr_events; i++) { + event = tep->events[i]; + if (strcmp(event->name, name) == 0) { + if (!sys) + break; + if (strcmp(event->system, sys) == 0) + break; + } + } + if (i == tep->nr_events) + event = NULL; + + tep->last_event = event; + return event; +} + +static unsigned long long +eval_num_arg(void *data, int size, struct tep_event *event, struct tep_print_arg *arg) +{ + struct tep_handle *tep = event->tep; + unsigned long long val = 0; + unsigned long long left, right; + struct tep_print_arg *typearg = NULL; + struct tep_print_arg *larg; + unsigned long offset; + unsigned int field_size; + + switch (arg->type) { + case TEP_PRINT_NULL: + /* ?? */ + return 0; + case TEP_PRINT_ATOM: + return strtoull(arg->atom.atom, NULL, 0); + case TEP_PRINT_FIELD: + if (!arg->field.field) { + arg->field.field = tep_find_any_field(event, arg->field.name); + if (!arg->field.field) + goto out_warning_field; + + } + /* must be a number */ + val = tep_read_number(tep, data + arg->field.field->offset, + arg->field.field->size); + break; + case TEP_PRINT_FLAGS: + case TEP_PRINT_SYMBOL: + case TEP_PRINT_INT_ARRAY: + case TEP_PRINT_HEX: + case TEP_PRINT_HEX_STR: + break; + case TEP_PRINT_TYPE: + val = eval_num_arg(data, size, event, arg->typecast.item); + return eval_type(val, arg, 0); + case TEP_PRINT_STRING: + case TEP_PRINT_BSTRING: + case TEP_PRINT_BITMASK: + return 0; + case TEP_PRINT_FUNC: { + struct trace_seq s; + trace_seq_init(&s); + val = process_defined_func(&s, data, size, event, arg); + trace_seq_destroy(&s); + return val; + } + case TEP_PRINT_OP: + if (strcmp(arg->op.op, "[") == 0) { + /* + * Arrays are special, since we don't want + * to read the arg as is. + */ + right = eval_num_arg(data, size, event, arg->op.right); + + /* handle typecasts */ + larg = arg->op.left; + while (larg->type == TEP_PRINT_TYPE) { + if (!typearg) + typearg = larg; + larg = larg->typecast.item; + } + + /* Default to long size */ + field_size = tep->long_size; + + switch (larg->type) { + case TEP_PRINT_DYNAMIC_ARRAY: + offset = tep_read_number(tep, + 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 TEP_PRINT_FIELD: + if (!larg->field.field) { + larg->field.field = + tep_find_any_field(event, larg->field.name); + if (!larg->field.field) { + arg = larg; + goto out_warning_field; + } + } + field_size = larg->field.field->elementsize; + offset = larg->field.field->offset + + right * larg->field.field->elementsize; + break; + default: + goto default_op; /* oops, all bets off */ + } + val = tep_read_number(tep, + data + offset, field_size); + if (typearg) + val = eval_type(val, typearg, 1); + break; + } else if (strcmp(arg->op.op, "?") == 0) { + left = eval_num_arg(data, size, event, arg->op.left); + arg = arg->op.right; + if (left) + val = eval_num_arg(data, size, event, arg->op.left); + else + val = eval_num_arg(data, size, event, arg->op.right); + break; + } + default_op: + left = eval_num_arg(data, size, event, arg->op.left); + right = eval_num_arg(data, size, event, arg->op.right); + switch (arg->op.op[0]) { + case '!': + switch (arg->op.op[1]) { + case 0: + val = !right; + break; + case '=': + val = left != right; + break; + default: + goto out_warning_op; + } + break; + case '~': + val = ~right; + break; + case '|': + if (arg->op.op[1]) + val = left || right; + else + val = left | right; + break; + case '&': + if (arg->op.op[1]) + val = left && right; + else + val = left & right; + break; + case '<': + switch (arg->op.op[1]) { + case 0: + val = left < right; + break; + case '<': + val = left << right; + break; + case '=': + val = left <= right; + break; + default: + goto out_warning_op; + } + break; + case '>': + switch (arg->op.op[1]) { + case 0: + val = left > right; + break; + case '>': + val = left >> right; + break; + case '=': + val = left >= right; + break; + default: + goto out_warning_op; + } + break; + case '=': + if (arg->op.op[1] != '=') + goto out_warning_op; + + val = left == right; + break; + case '-': + val = left - right; + break; + case '+': + val = left + right; + break; + case '/': + val = left / right; + break; + case '%': + val = left % right; + break; + case '*': + val = left * right; + break; + default: + goto out_warning_op; + } + break; + case TEP_PRINT_DYNAMIC_ARRAY_LEN: + offset = tep_read_number(tep, + data + arg->dynarray.field->offset, + arg->dynarray.field->size); + /* + * The total allocated length of the dynamic array is + * stored in the top half of the field, and the offset + * is in the bottom half of the 32 bit field. + */ + val = (unsigned long long)(offset >> 16); + break; + case TEP_PRINT_DYNAMIC_ARRAY: + /* Without [], we pass the address to the dynamic data */ + offset = tep_read_number(tep, + data + arg->dynarray.field->offset, + arg->dynarray.field->size); + /* + * The total allocated length of the dynamic array is + * stored in the top half of the field, and the offset + * is in the bottom half of the 32 bit field. + */ + offset &= 0xffff; + val = (unsigned long long)((unsigned long)data + offset); + break; + default: /* not sure what to do there */ + return 0; + } + return val; + +out_warning_op: + do_warning_event(event, "%s: unknown op '%s'", __func__, arg->op.op); + return 0; + +out_warning_field: + do_warning_event(event, "%s: field %s not found", + __func__, arg->field.name); + return 0; +} + +struct flag { + const char *name; + unsigned long long value; +}; + +static const struct flag flags[] = { + { "HI_SOFTIRQ", 0 }, + { "TIMER_SOFTIRQ", 1 }, + { "NET_TX_SOFTIRQ", 2 }, + { "NET_RX_SOFTIRQ", 3 }, + { "BLOCK_SOFTIRQ", 4 }, + { "IRQ_POLL_SOFTIRQ", 5 }, + { "TASKLET_SOFTIRQ", 6 }, + { "SCHED_SOFTIRQ", 7 }, + { "HRTIMER_SOFTIRQ", 8 }, + { "RCU_SOFTIRQ", 9 }, + + { "HRTIMER_NORESTART", 0 }, + { "HRTIMER_RESTART", 1 }, +}; + +static long long eval_flag(const char *flag) +{ + int i; + + /* + * Some flags in the format files do not get converted. + * If the flag is not numeric, see if it is something that + * we already know about. + */ + if (isdigit(flag[0])) + return strtoull(flag, NULL, 0); + + for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++) + if (strcmp(flags[i].name, flag) == 0) + return flags[i].value; + + return -1LL; +} + +static void print_str_to_seq(struct trace_seq *s, const char *format, + int len_arg, const char *str) +{ + if (len_arg >= 0) + trace_seq_printf(s, format, len_arg, str); + else + trace_seq_printf(s, format, str); +} + +static void print_bitmask_to_seq(struct tep_handle *tep, + struct trace_seq *s, const char *format, + int len_arg, const void *data, int size) +{ + int nr_bits = size * 8; + int str_size = (nr_bits + 3) / 4; + int len = 0; + char buf[3]; + char *str; + int index; + int i; + + /* + * The kernel likes to put in commas every 32 bits, we + * can do the same. + */ + str_size += (nr_bits - 1) / 32; + + str = malloc(str_size + 1); + if (!str) { + do_warning("%s: not enough memory!", __func__); + return; + } + str[str_size] = 0; + + /* Start out with -2 for the two chars per byte */ + for (i = str_size - 2; i >= 0; i -= 2) { + /* + * data points to a bit mask of size bytes. + * In the kernel, this is an array of long words, thus + * endianness is very important. + */ + if (tep->file_bigendian) + index = size - (len + 1); + else + index = len; + + snprintf(buf, 3, "%02x", *((unsigned char *)data + index)); + memcpy(str + i, buf, 2); + len++; + if (!(len & 3) && i > 0) { + i--; + str[i] = ','; + } + } + + if (len_arg >= 0) + trace_seq_printf(s, format, len_arg, str); + else + trace_seq_printf(s, format, str); + + free(str); +} + +static void print_str_arg(struct trace_seq *s, void *data, int size, + struct tep_event *event, const char *format, + int len_arg, struct tep_print_arg *arg) +{ + struct tep_handle *tep = event->tep; + struct tep_print_flag_sym *flag; + struct tep_format_field *field; + struct printk_map *printk; + long long val, fval; + unsigned long long addr; + char *str; + unsigned char *hex; + int print; + int i, len; + + switch (arg->type) { + case TEP_PRINT_NULL: + /* ?? */ + return; + case TEP_PRINT_ATOM: + print_str_to_seq(s, format, len_arg, arg->atom.atom); + return; + case TEP_PRINT_FIELD: + field = arg->field.field; + if (!field) { + field = tep_find_any_field(event, arg->field.name); + if (!field) { + str = arg->field.name; + goto out_warning_field; + } + arg->field.field = field; + } + /* Zero sized fields, mean the rest of the data */ + len = field->size ? : size - field->offset; + + /* + * Some events pass in pointers. If this is not an array + * and the size is the same as long_size, assume that it + * is a pointer. + */ + if (!(field->flags & TEP_FIELD_IS_ARRAY) && + field->size == tep->long_size) { + + /* Handle heterogeneous recording and processing + * architectures + * + * CASE I: + * Traces recorded on 32-bit devices (32-bit + * addressing) and processed on 64-bit devices: + * In this case, only 32 bits should be read. + * + * CASE II: + * Traces recorded on 64 bit devices and processed + * on 32-bit devices: + * In this case, 64 bits must be read. + */ + addr = (tep->long_size == 8) ? + *(unsigned long long *)(data + field->offset) : + (unsigned long long)*(unsigned int *)(data + field->offset); + + /* Check if it matches a print format */ + printk = find_printk(tep, addr); + if (printk) + trace_seq_puts(s, printk->printk); + else + trace_seq_printf(s, "%llx", addr); + break; + } + str = malloc(len + 1); + if (!str) { + do_warning_event(event, "%s: not enough memory!", + __func__); + return; + } + memcpy(str, data + field->offset, len); + str[len] = 0; + print_str_to_seq(s, format, len_arg, str); + free(str); + break; + case TEP_PRINT_FLAGS: + val = eval_num_arg(data, size, event, arg->flags.field); + print = 0; + for (flag = arg->flags.flags; flag; flag = flag->next) { + fval = eval_flag(flag->value); + if (!val && fval < 0) { + print_str_to_seq(s, format, len_arg, flag->str); + break; + } + if (fval > 0 && (val & fval) == fval) { + if (print && arg->flags.delim) + trace_seq_puts(s, arg->flags.delim); + print_str_to_seq(s, format, len_arg, flag->str); + print = 1; + val &= ~fval; + } + } + if (val) { + if (print && arg->flags.delim) + trace_seq_puts(s, arg->flags.delim); + trace_seq_printf(s, "0x%llx", val); + } + break; + case TEP_PRINT_SYMBOL: + val = eval_num_arg(data, size, event, arg->symbol.field); + for (flag = arg->symbol.symbols; flag; flag = flag->next) { + fval = eval_flag(flag->value); + if (val == fval) { + print_str_to_seq(s, format, len_arg, flag->str); + break; + } + } + if (!flag) + trace_seq_printf(s, "0x%llx", val); + break; + case TEP_PRINT_HEX: + case TEP_PRINT_HEX_STR: + if (arg->hex.field->type == TEP_PRINT_DYNAMIC_ARRAY) { + unsigned long offset; + offset = tep_read_number(tep, + 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 = tep_find_any_field(event, str); + if (!field) + goto out_warning_field; + arg->hex.field->field.field = field; + } + hex = data + field->offset; + } + len = eval_num_arg(data, size, event, arg->hex.size); + for (i = 0; i < len; i++) { + if (i && arg->type == TEP_PRINT_HEX) + trace_seq_putc(s, ' '); + trace_seq_printf(s, "%02x", hex[i]); + } + break; + + case TEP_PRINT_INT_ARRAY: { + void *num; + int el_size; + + if (arg->int_array.field->type == TEP_PRINT_DYNAMIC_ARRAY) { + unsigned long offset; + struct tep_format_field *field = + arg->int_array.field->dynarray.field; + offset = tep_read_number(tep, + data + field->offset, + field->size); + num = data + (offset & 0xffff); + } else { + field = arg->int_array.field->field.field; + if (!field) { + str = arg->int_array.field->field.name; + field = tep_find_any_field(event, str); + if (!field) + goto out_warning_field; + arg->int_array.field->field.field = field; + } + num = data + field->offset; + } + len = eval_num_arg(data, size, event, arg->int_array.count); + el_size = eval_num_arg(data, size, event, + arg->int_array.el_size); + for (i = 0; i < len; i++) { + if (i) + trace_seq_putc(s, ' '); + + if (el_size == 1) { + trace_seq_printf(s, "%u", *(uint8_t *)num); + } else if (el_size == 2) { + trace_seq_printf(s, "%u", *(uint16_t *)num); + } else if (el_size == 4) { + trace_seq_printf(s, "%u", *(uint32_t *)num); + } else if (el_size == 8) { + trace_seq_printf(s, "%"PRIu64, *(uint64_t *)num); + } else { + trace_seq_printf(s, "BAD SIZE:%d 0x%x", + el_size, *(uint8_t *)num); + el_size = 1; + } + + num += el_size; + } + break; + } + case TEP_PRINT_TYPE: + break; + case TEP_PRINT_STRING: { + int str_offset; + + if (arg->string.offset == -1) { + struct tep_format_field *f; + + f = tep_find_any_field(event, arg->string.string); + arg->string.offset = f->offset; + } + str_offset = data2host4(tep, *(unsigned int *)(data + arg->string.offset)); + str_offset &= 0xffff; + print_str_to_seq(s, format, len_arg, ((char *)data) + str_offset); + break; + } + case TEP_PRINT_BSTRING: + print_str_to_seq(s, format, len_arg, arg->string.string); + break; + case TEP_PRINT_BITMASK: { + int bitmask_offset; + int bitmask_size; + + if (arg->bitmask.offset == -1) { + struct tep_format_field *f; + + f = tep_find_any_field(event, arg->bitmask.bitmask); + arg->bitmask.offset = f->offset; + } + bitmask_offset = data2host4(tep, *(unsigned int *)(data + arg->bitmask.offset)); + bitmask_size = bitmask_offset >> 16; + bitmask_offset &= 0xffff; + print_bitmask_to_seq(tep, s, format, len_arg, + data + bitmask_offset, bitmask_size); + break; + } + case TEP_PRINT_OP: + /* + * The only op for string should be ? : + */ + if (arg->op.op[0] != '?') + return; + val = eval_num_arg(data, size, event, arg->op.left); + if (val) + print_str_arg(s, data, size, event, + format, len_arg, arg->op.right->op.left); + else + print_str_arg(s, data, size, event, + format, len_arg, arg->op.right->op.right); + break; + case TEP_PRINT_FUNC: + process_defined_func(s, data, size, event, arg); + break; + default: + /* well... */ + break; + } + + return; + +out_warning_field: + do_warning_event(event, "%s: field %s not found", + __func__, arg->field.name); +} + +static unsigned long long +process_defined_func(struct trace_seq *s, void *data, int size, + struct tep_event *event, struct tep_print_arg *arg) +{ + struct tep_function_handler *func_handle = arg->func.func; + struct func_params *param; + unsigned long long *args; + unsigned long long ret; + struct tep_print_arg *farg; + struct trace_seq str; + struct save_str { + struct save_str *next; + char *str; + } *strings = NULL, *string; + int i; + + if (!func_handle->nr_args) { + ret = (*func_handle->func)(s, NULL); + goto out; + } + + farg = arg->func.args; + param = func_handle->params; + + ret = ULLONG_MAX; + args = malloc(sizeof(*args) * func_handle->nr_args); + if (!args) + goto out; + + for (i = 0; i < func_handle->nr_args; i++) { + switch (param->type) { + case TEP_FUNC_ARG_INT: + case TEP_FUNC_ARG_LONG: + case TEP_FUNC_ARG_PTR: + args[i] = eval_num_arg(data, size, event, farg); + break; + case TEP_FUNC_ARG_STRING: + trace_seq_init(&str); + print_str_arg(&str, data, size, event, "%s", -1, farg); + trace_seq_terminate(&str); + string = malloc(sizeof(*string)); + if (!string) { + do_warning_event(event, "%s(%d): malloc str", + __func__, __LINE__); + goto out_free; + } + string->next = strings; + string->str = strdup(str.buffer); + if (!string->str) { + free(string); + do_warning_event(event, "%s(%d): malloc str", + __func__, __LINE__); + goto out_free; + } + args[i] = (uintptr_t)string->str; + strings = string; + trace_seq_destroy(&str); + break; + default: + /* + * Something went totally wrong, this is not + * an input error, something in this code broke. + */ + do_warning_event(event, "Unexpected end of arguments\n"); + goto out_free; + } + farg = farg->next; + param = param->next; + } + + ret = (*func_handle->func)(s, args); +out_free: + free(args); + while (strings) { + string = strings; + strings = string->next; + free(string->str); + free(string); + } + + out: + /* TBD : handle return type here */ + return ret; +} + +static void free_args(struct tep_print_arg *args) +{ + struct tep_print_arg *next; + + while (args) { + next = args->next; + + free_arg(args); + args = next; + } +} + +static struct tep_print_arg *make_bprint_args(char *fmt, void *data, int size, struct tep_event *event) +{ + struct tep_handle *tep = event->tep; + struct tep_format_field *field, *ip_field; + struct tep_print_arg *args, *arg, **next; + unsigned long long ip, val; + char *ptr; + void *bptr; + int vsize = 0; + + field = tep->bprint_buf_field; + ip_field = tep->bprint_ip_field; + + if (!field) { + field = tep_find_field(event, "buf"); + if (!field) { + do_warning_event(event, "can't find buffer field for binary printk"); + return NULL; + } + ip_field = tep_find_field(event, "ip"); + if (!ip_field) { + do_warning_event(event, "can't find ip field for binary printk"); + return NULL; + } + tep->bprint_buf_field = field; + tep->bprint_ip_field = ip_field; + } + + ip = tep_read_number(tep, data + ip_field->offset, ip_field->size); + + /* + * The first arg is the IP pointer. + */ + args = alloc_arg(); + if (!args) { + do_warning_event(event, "%s(%d): not enough memory!", + __func__, __LINE__); + return NULL; + } + arg = args; + arg->next = NULL; + next = &arg->next; + + arg->type = TEP_PRINT_ATOM; + + if (asprintf(&arg->atom.atom, "%lld", ip) < 0) + goto out_free; + + /* skip the first "%ps: " */ + for (ptr = fmt + 5, bptr = data + field->offset; + bptr < data + size && *ptr; ptr++) { + int ls = 0; + + if (*ptr == '%') { + process_again: + ptr++; + switch (*ptr) { + case '%': + break; + case 'l': + ls++; + goto process_again; + case 'L': + ls = 2; + goto process_again; + case '0' ... '9': + goto process_again; + case '.': + goto process_again; + case 'z': + case 'Z': + ls = 1; + goto process_again; + case 'p': + ls = 1; + if (isalnum(ptr[1])) { + ptr++; + /* Check for special pointers */ + switch (*ptr) { + case 's': + case 'S': + case 'x': + break; + case 'f': + case 'F': + /* + * Pre-5.5 kernels use %pf and + * %pF for printing symbols + * while kernels since 5.5 use + * %pfw for fwnodes. So check + * %p[fF] isn't followed by 'w'. + */ + if (ptr[1] != 'w') + break; + /* fall through */ + default: + /* + * Older kernels do not process + * dereferenced pointers. + * Only process if the pointer + * value is a printable. + */ + if (isprint(*(char *)bptr)) + goto process_string; + } + } + /* fall through */ + case 'd': + case 'u': + case 'i': + case 'x': + case 'X': + case 'o': + switch (ls) { + case 0: + vsize = 4; + break; + case 1: + vsize = tep->long_size; + break; + case 2: + vsize = 8; + break; + default: + vsize = ls; /* ? */ + break; + } + /* fall through */ + case '*': + if (*ptr == '*') + vsize = 4; + + /* the pointers are always 4 bytes aligned */ + bptr = (void *)(((unsigned long)bptr + 3) & + ~3); + val = tep_read_number(tep, bptr, vsize); + bptr += vsize; + arg = alloc_arg(); + if (!arg) { + do_warning_event(event, "%s(%d): not enough memory!", + __func__, __LINE__); + goto out_free; + } + arg->next = NULL; + arg->type = TEP_PRINT_ATOM; + if (asprintf(&arg->atom.atom, "%lld", val) < 0) { + free(arg); + goto out_free; + } + *next = arg; + next = &arg->next; + /* + * The '*' case means that an arg is used as the length. + * We need to continue to figure out for what. + */ + if (*ptr == '*') + goto process_again; + + break; + case 's': + process_string: + arg = alloc_arg(); + if (!arg) { + do_warning_event(event, "%s(%d): not enough memory!", + __func__, __LINE__); + goto out_free; + } + arg->next = NULL; + arg->type = TEP_PRINT_BSTRING; + arg->string.string = strdup(bptr); + if (!arg->string.string) + goto out_free; + bptr += strlen(bptr) + 1; + *next = arg; + next = &arg->next; + default: + break; + } + } + } + + return args; + +out_free: + free_args(args); + return NULL; +} + +static char * +get_bprint_format(void *data, int size __maybe_unused, + struct tep_event *event) +{ + struct tep_handle *tep = event->tep; + unsigned long long addr; + struct tep_format_field *field; + struct printk_map *printk; + char *format; + + field = tep->bprint_fmt_field; + + if (!field) { + field = tep_find_field(event, "fmt"); + if (!field) { + do_warning_event(event, "can't find format field for binary printk"); + return NULL; + } + tep->bprint_fmt_field = field; + } + + addr = tep_read_number(tep, data + field->offset, field->size); + + printk = find_printk(tep, addr); + if (!printk) { + if (asprintf(&format, "%%ps: (NO FORMAT FOUND at %llx)\n", addr) < 0) + return NULL; + return format; + } + + if (asprintf(&format, "%s: %s", "%ps", printk->printk) < 0) + return NULL; + + return format; +} + +static int print_mac_arg(struct trace_seq *s, const char *format, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x"; + bool reverse = false; + unsigned char *buf; + int ret = 0; + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return 0; + } + + if (arg->type != TEP_PRINT_FIELD) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", + arg->type); + return 0; + } + + if (format[0] == 'm') { + fmt = "%.2x%.2x%.2x%.2x%.2x%.2x"; + } else if (format[0] == 'M' && format[1] == 'F') { + fmt = "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"; + ret++; + } + if (format[1] == 'R') { + reverse = true; + ret++; + } + + if (!arg->field.field) { + arg->field.field = + tep_find_any_field(event, arg->field.name); + if (!arg->field.field) { + do_warning_event(event, "%s: field %s not found", + __func__, arg->field.name); + return ret; + } + } + if (arg->field.field->size != 6) { + trace_seq_printf(s, "INVALIDMAC"); + return ret; + } + + buf = data + arg->field.field->offset; + if (reverse) + trace_seq_printf(s, fmt, buf[5], buf[4], buf[3], buf[2], buf[1], buf[0]); + else + trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); + + return ret; +} + +static int parse_ip4_print_args(struct tep_handle *tep, + const char *ptr, bool *reverse) +{ + int ret = 0; + + *reverse = false; + + /* hnbl */ + switch (*ptr) { + case 'h': + if (tep->file_bigendian) + *reverse = false; + else + *reverse = true; + ret++; + break; + case 'l': + *reverse = true; + ret++; + break; + case 'n': + case 'b': + ret++; + /* fall through */ + default: + *reverse = false; + break; + } + + return ret; +} + +static void print_ip4_addr(struct trace_seq *s, char i, bool reverse, unsigned char *buf) +{ + const char *fmt; + + if (i == 'i') + fmt = "%03d.%03d.%03d.%03d"; + else + fmt = "%d.%d.%d.%d"; + + if (reverse) + trace_seq_printf(s, fmt, buf[3], buf[2], buf[1], buf[0]); + else + trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3]); + +} + +static inline bool ipv6_addr_v4mapped(const struct in6_addr *a) +{ + return ((unsigned long)(a->s6_addr32[0] | a->s6_addr32[1]) | + (unsigned long)(a->s6_addr32[2] ^ htonl(0x0000ffff))) == 0UL; +} + +static inline bool ipv6_addr_is_isatap(const struct in6_addr *addr) +{ + return (addr->s6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE); +} + +static void print_ip6c_addr(struct trace_seq *s, unsigned char *addr) +{ + int i, j, range; + unsigned char zerolength[8]; + int longest = 1; + int colonpos = -1; + uint16_t word; + uint8_t hi, lo; + bool needcolon = false; + bool useIPv4; + struct in6_addr in6; + + memcpy(&in6, addr, sizeof(struct in6_addr)); + + useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6); + + memset(zerolength, 0, sizeof(zerolength)); + + if (useIPv4) + range = 6; + else + range = 8; + + /* find position of longest 0 run */ + for (i = 0; i < range; i++) { + for (j = i; j < range; j++) { + if (in6.s6_addr16[j] != 0) + break; + zerolength[i]++; + } + } + for (i = 0; i < range; i++) { + if (zerolength[i] > longest) { + longest = zerolength[i]; + colonpos = i; + } + } + if (longest == 1) /* don't compress a single 0 */ + colonpos = -1; + + /* emit address */ + for (i = 0; i < range; i++) { + if (i == colonpos) { + if (needcolon || i == 0) + trace_seq_printf(s, ":"); + trace_seq_printf(s, ":"); + needcolon = false; + i += longest - 1; + continue; + } + if (needcolon) { + trace_seq_printf(s, ":"); + needcolon = false; + } + /* hex u16 without leading 0s */ + word = ntohs(in6.s6_addr16[i]); + hi = word >> 8; + lo = word & 0xff; + if (hi) + trace_seq_printf(s, "%x%02x", hi, lo); + else + trace_seq_printf(s, "%x", lo); + + needcolon = true; + } + + if (useIPv4) { + if (needcolon) + trace_seq_printf(s, ":"); + print_ip4_addr(s, 'I', false, &in6.s6_addr[12]); + } + + return; +} + +static void print_ip6_addr(struct trace_seq *s, char i, unsigned char *buf) +{ + int j; + + for (j = 0; j < 16; j += 2) { + trace_seq_printf(s, "%02x%02x", buf[j], buf[j+1]); + if (i == 'I' && j < 14) + trace_seq_printf(s, ":"); + } +} + +/* + * %pi4 print an IPv4 address with leading zeros + * %pI4 print an IPv4 address without leading zeros + * %pi6 print an IPv6 address without colons + * %pI6 print an IPv6 address with colons + * %pI6c print an IPv6 address in compressed form with colons + * %pISpc print an IP address based on sockaddr; p adds port. + */ +static int print_ipv4_arg(struct trace_seq *s, const char *ptr, char i, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + bool reverse = false; + unsigned char *buf; + int ret; + + ret = parse_ip4_print_args(event->tep, ptr, &reverse); + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return ret; + } + + if (arg->type != TEP_PRINT_FIELD) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); + return ret; + } + + if (!arg->field.field) { + arg->field.field = + tep_find_any_field(event, arg->field.name); + if (!arg->field.field) { + do_warning("%s: field %s not found", + __func__, arg->field.name); + return ret; + } + } + + buf = data + arg->field.field->offset; + + if (arg->field.field->size != 4) { + trace_seq_printf(s, "INVALIDIPv4"); + return ret; + } + + print_ip4_addr(s, i, reverse, buf); + return ret; + +} + +static int print_ipv6_arg(struct trace_seq *s, const char *ptr, char i, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + char have_c = 0; + unsigned char *buf; + int rc = 0; + + /* pI6c */ + if (i == 'I' && *ptr == 'c') { + have_c = 1; + ptr++; + rc++; + } + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return rc; + } + + if (arg->type != TEP_PRINT_FIELD) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); + return rc; + } + + if (!arg->field.field) { + arg->field.field = + tep_find_any_field(event, arg->field.name); + if (!arg->field.field) { + do_warning("%s: field %s not found", + __func__, arg->field.name); + return rc; + } + } + + buf = data + arg->field.field->offset; + + if (arg->field.field->size != 16) { + trace_seq_printf(s, "INVALIDIPv6"); + return rc; + } + + if (have_c) + print_ip6c_addr(s, buf); + else + print_ip6_addr(s, i, buf); + + return rc; +} + +static int print_ipsa_arg(struct trace_seq *s, const char *ptr, char i, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + char have_c = 0, have_p = 0; + unsigned char *buf; + struct sockaddr_storage *sa; + bool reverse = false; + int rc = 0; + int ret; + + /* pISpc */ + if (i == 'I') { + if (*ptr == 'p') { + have_p = 1; + ptr++; + rc++; + } + if (*ptr == 'c') { + have_c = 1; + ptr++; + rc++; + } + } + ret = parse_ip4_print_args(event->tep, ptr, &reverse); + ptr += ret; + rc += ret; + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return rc; + } + + if (arg->type != TEP_PRINT_FIELD) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); + return rc; + } + + if (!arg->field.field) { + arg->field.field = + tep_find_any_field(event, arg->field.name); + if (!arg->field.field) { + do_warning("%s: field %s not found", + __func__, arg->field.name); + return rc; + } + } + + sa = (struct sockaddr_storage *) (data + arg->field.field->offset); + + if (sa->ss_family == AF_INET) { + struct sockaddr_in *sa4 = (struct sockaddr_in *) sa; + + if (arg->field.field->size < sizeof(struct sockaddr_in)) { + trace_seq_printf(s, "INVALIDIPv4"); + return rc; + } + + print_ip4_addr(s, i, reverse, (unsigned char *) &sa4->sin_addr); + if (have_p) + trace_seq_printf(s, ":%d", ntohs(sa4->sin_port)); + + + } else if (sa->ss_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) sa; + + if (arg->field.field->size < sizeof(struct sockaddr_in6)) { + trace_seq_printf(s, "INVALIDIPv6"); + return rc; + } + + if (have_p) + trace_seq_printf(s, "["); + + buf = (unsigned char *) &sa6->sin6_addr; + if (have_c) + print_ip6c_addr(s, buf); + else + print_ip6_addr(s, i, buf); + + if (have_p) + trace_seq_printf(s, "]:%d", ntohs(sa6->sin6_port)); + } + + return rc; +} + +static int print_ip_arg(struct trace_seq *s, const char *ptr, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + char i = *ptr; /* 'i' or 'I' */ + int rc = 1; + + /* IP version */ + ptr++; + + switch (*ptr) { + case '4': + rc += print_ipv4_arg(s, ptr + 1, i, data, size, event, arg); + break; + case '6': + rc += print_ipv6_arg(s, ptr + 1, i, data, size, event, arg); + break; + case 'S': + rc += print_ipsa_arg(s, ptr + 1, i, data, size, event, arg); + break; + default: + return 0; + } + + return rc; +} + +static const int guid_index[16] = {3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15}; +static const int uuid_index[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + +static int print_uuid_arg(struct trace_seq *s, const char *ptr, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + const int *index = uuid_index; + char *format = "%02x"; + int ret = 0; + char *buf; + int i; + + switch (*(ptr + 1)) { + case 'L': + format = "%02X"; + /* fall through */ + case 'l': + index = guid_index; + ret++; + break; + case 'B': + format = "%02X"; + /* fall through */ + case 'b': + ret++; + break; + } + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return ret; + } + + if (arg->type != TEP_PRINT_FIELD) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); + return ret; + } + + if (!arg->field.field) { + arg->field.field = + tep_find_any_field(event, arg->field.name); + if (!arg->field.field) { + do_warning("%s: field %s not found", + __func__, arg->field.name); + return ret; + } + } + + if (arg->field.field->size != 16) { + trace_seq_printf(s, "INVALIDUUID"); + return ret; + } + + buf = data + arg->field.field->offset; + + for (i = 0; i < 16; i++) { + trace_seq_printf(s, format, buf[index[i]] & 0xff); + switch (i) { + case 3: + case 5: + case 7: + case 9: + trace_seq_printf(s, "-"); + break; + } + } + + return ret; +} + +static int print_raw_buff_arg(struct trace_seq *s, const char *ptr, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg, int print_len) +{ + int plen = print_len; + char *delim = " "; + int ret = 0; + char *buf; + int i; + unsigned long offset; + int arr_len; + + switch (*(ptr + 1)) { + case 'C': + delim = ":"; + ret++; + break; + case 'D': + delim = "-"; + ret++; + break; + case 'N': + delim = ""; + ret++; + break; + } + + if (arg->type == TEP_PRINT_FUNC) { + process_defined_func(s, data, size, event, arg); + return ret; + } + + if (arg->type != TEP_PRINT_DYNAMIC_ARRAY) { + trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); + return ret; + } + + offset = tep_read_number(event->tep, + data + arg->dynarray.field->offset, + arg->dynarray.field->size); + arr_len = (unsigned long long)(offset >> 16); + buf = data + (offset & 0xffff); + + if (arr_len < plen) + plen = arr_len; + + if (plen < 1) + return ret; + + trace_seq_printf(s, "%02x", buf[0] & 0xff); + for (i = 1; i < plen; i++) + trace_seq_printf(s, "%s%02x", delim, buf[i] & 0xff); + + return ret; +} + +static int is_printable_array(char *p, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len && p[i]; i++) + if (!isprint(p[i]) && !isspace(p[i])) + return 0; + return 1; +} + +void tep_print_field(struct trace_seq *s, void *data, + struct tep_format_field *field) +{ + unsigned long long val; + unsigned int offset, len, i; + struct tep_handle *tep = field->event->tep; + + if (field->flags & TEP_FIELD_IS_ARRAY) { + offset = field->offset; + len = field->size; + if (field->flags & TEP_FIELD_IS_DYNAMIC) { + val = tep_read_number(tep, data + offset, len); + offset = val; + len = offset >> 16; + offset &= 0xffff; + } + if (field->flags & TEP_FIELD_IS_STRING && + is_printable_array(data + offset, len)) { + trace_seq_printf(s, "%s", (char *)data + offset); + } else { + trace_seq_puts(s, "ARRAY["); + for (i = 0; i < len; i++) { + if (i) + trace_seq_puts(s, ", "); + trace_seq_printf(s, "%02x", + *((unsigned char *)data + offset + i)); + } + trace_seq_putc(s, ']'); + field->flags &= ~TEP_FIELD_IS_STRING; + } + } else { + val = tep_read_number(tep, data + field->offset, + field->size); + if (field->flags & TEP_FIELD_IS_POINTER) { + trace_seq_printf(s, "0x%llx", val); + } else if (field->flags & TEP_FIELD_IS_SIGNED) { + switch (field->size) { + case 4: + /* + * If field is long then print it in hex. + * A long usually stores pointers. + */ + if (field->flags & TEP_FIELD_IS_LONG) + trace_seq_printf(s, "0x%x", (int)val); + else + trace_seq_printf(s, "%d", (int)val); + break; + case 2: + trace_seq_printf(s, "%2d", (short)val); + break; + case 1: + trace_seq_printf(s, "%1d", (char)val); + break; + default: + trace_seq_printf(s, "%lld", val); + } + } else { + if (field->flags & TEP_FIELD_IS_LONG) + trace_seq_printf(s, "0x%llx", val); + else + trace_seq_printf(s, "%llu", val); + } + } +} + +void tep_print_fields(struct trace_seq *s, void *data, + int size __maybe_unused, struct tep_event *event) +{ + struct tep_format_field *field; + + field = event->format.fields; + while (field) { + trace_seq_printf(s, " %s=", field->name); + tep_print_field(s, data, field); + field = field->next; + } +} + +static int print_function(struct trace_seq *s, const char *format, + void *data, int size, struct tep_event *event, + struct tep_print_arg *arg) +{ + struct func_map *func; + unsigned long long val; + + val = eval_num_arg(data, size, event, arg); + func = find_func(event->tep, val); + if (func) { + trace_seq_puts(s, func->func); + if (*format == 'F' || *format == 'S') + trace_seq_printf(s, "+0x%llx", val - func->addr); + } else { + if (event->tep->long_size == 4) + trace_seq_printf(s, "0x%lx", (long)val); + else + trace_seq_printf(s, "0x%llx", (long long)val); + } + + return 0; +} + +static int print_arg_pointer(struct trace_seq *s, const char *format, int plen, + void *data, int size, + struct tep_event *event, struct tep_print_arg *arg) +{ + unsigned long long val; + int ret = 1; + + if (arg->type == TEP_PRINT_BSTRING) { + trace_seq_puts(s, arg->string.string); + return 0; + } + while (*format) { + if (*format == 'p') { + format++; + break; + } + format++; + } + + switch (*format) { + case 'F': + case 'f': + case 'S': + case 's': + ret += print_function(s, format, data, size, event, arg); + break; + case 'M': + case 'm': + ret += print_mac_arg(s, format, data, size, event, arg); + break; + case 'I': + case 'i': + ret += print_ip_arg(s, format, data, size, event, arg); + break; + case 'U': + ret += print_uuid_arg(s, format, data, size, event, arg); + break; + case 'h': + ret += print_raw_buff_arg(s, format, data, size, event, arg, plen); + break; + default: + ret = 0; + val = eval_num_arg(data, size, event, arg); + trace_seq_printf(s, "%p", (void *)(intptr_t)val); + break; + } + + return ret; + +} + +static int print_arg_number(struct trace_seq *s, const char *format, int plen, + void *data, int size, int ls, + struct tep_event *event, struct tep_print_arg *arg) +{ + unsigned long long val; + + val = eval_num_arg(data, size, event, arg); + + switch (ls) { + case -2: + if (plen >= 0) + trace_seq_printf(s, format, plen, (char)val); + else + trace_seq_printf(s, format, (char)val); + break; + case -1: + if (plen >= 0) + trace_seq_printf(s, format, plen, (short)val); + else + trace_seq_printf(s, format, (short)val); + break; + case 0: + if (plen >= 0) + trace_seq_printf(s, format, plen, (int)val); + else + trace_seq_printf(s, format, (int)val); + break; + case 1: + if (plen >= 0) + trace_seq_printf(s, format, plen, (long)val); + else + trace_seq_printf(s, format, (long)val); + break; + case 2: + if (plen >= 0) + trace_seq_printf(s, format, plen, (long long)val); + else + trace_seq_printf(s, format, (long long)val); + break; + default: + do_warning_event(event, "bad count (%d)", ls); + event->flags |= TEP_EVENT_FL_FAILED; + } + return 0; +} + + +static void print_arg_string(struct trace_seq *s, const char *format, int plen, + void *data, int size, + struct tep_event *event, struct tep_print_arg *arg) +{ + struct trace_seq p; + + /* Use helper trace_seq */ + trace_seq_init(&p); + print_str_arg(&p, data, size, event, + format, plen, arg); + trace_seq_terminate(&p); + trace_seq_puts(s, p.buffer); + trace_seq_destroy(&p); +} + +static int parse_arg_format_pointer(const char *format) +{ + int ret = 0; + int index; + int loop; + + switch (*format) { + case 'F': + case 'S': + case 'f': + case 's': + ret++; + break; + case 'M': + case 'm': + /* [mM]R , [mM]F */ + switch (format[1]) { + case 'R': + case 'F': + ret++; + break; + } + ret++; + break; + case 'I': + case 'i': + index = 2; + loop = 1; + switch (format[1]) { + case 'S': + /*[S][pfs]*/ + while (loop) { + switch (format[index]) { + case 'p': + case 'f': + case 's': + ret++; + index++; + break; + default: + loop = 0; + break; + } + } + /* fall through */ + case '4': + /* [4S][hnbl] */ + switch (format[index]) { + case 'h': + case 'n': + case 'l': + case 'b': + ret++; + index++; + break; + } + if (format[1] == '4') { + ret++; + break; + } + /* fall through */ + case '6': + /* [6S]c */ + if (format[index] == 'c') + ret++; + ret++; + break; + } + ret++; + break; + case 'U': + switch (format[1]) { + case 'L': + case 'l': + case 'B': + case 'b': + ret++; + break; + } + ret++; + break; + case 'h': + switch (format[1]) { + case 'C': + case 'D': + case 'N': + ret++; + break; + } + ret++; + break; + default: + break; + } + + return ret; +} + +static void free_parse_args(struct tep_print_parse *arg) +{ + struct tep_print_parse *del; + + while (arg) { + del = arg; + arg = del->next; + free(del->format); + free(del); + } +} + +static int parse_arg_add(struct tep_print_parse **parse, char *format, + enum tep_print_parse_type type, + struct tep_print_arg *arg, + struct tep_print_arg *len_as_arg, + int ls) +{ + struct tep_print_parse *parg = NULL; + + parg = calloc(1, sizeof(*parg)); + if (!parg) + goto error; + parg->format = strdup(format); + if (!parg->format) + goto error; + parg->type = type; + parg->arg = arg; + parg->len_as_arg = len_as_arg; + parg->ls = ls; + *parse = parg; + return 0; +error: + if (parg) { + free(parg->format); + free(parg); + } + return -1; +} + +static int parse_arg_format(struct tep_print_parse **parse, + struct tep_event *event, + const char *format, struct tep_print_arg **arg) +{ + struct tep_print_arg *len_arg = NULL; + char print_format[32]; + const char *start = format; + int ret = 0; + int ls = 0; + int res; + int len; + + format++; + ret++; + for (; *format; format++) { + switch (*format) { + case '#': + /* FIXME: need to handle properly */ + break; + case 'h': + ls--; + break; + case 'l': + ls++; + break; + case 'L': + ls = 2; + break; + case '.': + case 'z': + case 'Z': + case '0' ... '9': + case '-': + break; + case '*': + /* The argument is the length. */ + if (!*arg) { + do_warning_event(event, "no argument match"); + event->flags |= TEP_EVENT_FL_FAILED; + goto out_failed; + } + if (len_arg) { + do_warning_event(event, "argument already matched"); + event->flags |= TEP_EVENT_FL_FAILED; + goto out_failed; + } + len_arg = *arg; + *arg = (*arg)->next; + break; + case 'p': + if (!*arg) { + do_warning_event(event, "no argument match"); + event->flags |= TEP_EVENT_FL_FAILED; + goto out_failed; + } + res = parse_arg_format_pointer(format + 1); + if (res > 0) { + format += res; + ret += res; + } + len = ((unsigned long)format + 1) - + (unsigned long)start; + /* should never happen */ + if (len > 31) { + do_warning_event(event, "bad format!"); + event->flags |= TEP_EVENT_FL_FAILED; + len = 31; + } + memcpy(print_format, start, len); + print_format[len] = 0; + + parse_arg_add(parse, print_format, + PRINT_FMT_ARG_POINTER, *arg, len_arg, ls); + *arg = (*arg)->next; + ret++; + return ret; + case 'd': + case 'u': + case 'i': + case 'x': + case 'X': + case 'o': + if (!*arg) { + do_warning_event(event, "no argument match"); + event->flags |= TEP_EVENT_FL_FAILED; + goto out_failed; + } + + len = ((unsigned long)format + 1) - + (unsigned long)start; + + /* should never happen */ + if (len > 30) { + do_warning_event(event, "bad format!"); + event->flags |= TEP_EVENT_FL_FAILED; + len = 31; + } + memcpy(print_format, start, len); + print_format[len] = 0; + + if (event->tep->long_size == 8 && ls == 1 && + sizeof(long) != 8) { + char *p; + + /* make %l into %ll */ + if (ls == 1 && (p = strchr(print_format, 'l'))) + memmove(p+1, p, strlen(p)+1); + ls = 2; + } + if (ls < -2 || ls > 2) { + do_warning_event(event, "bad count (%d)", ls); + event->flags |= TEP_EVENT_FL_FAILED; + } + parse_arg_add(parse, print_format, + PRINT_FMT_ARG_DIGIT, *arg, len_arg, ls); + *arg = (*arg)->next; + ret++; + return ret; + case 's': + if (!*arg) { + do_warning_event(event, "no matching argument"); + event->flags |= TEP_EVENT_FL_FAILED; + goto out_failed; + } + + len = ((unsigned long)format + 1) - + (unsigned long)start; + + /* should never happen */ + if (len > 31) { + do_warning_event(event, "bad format!"); + event->flags |= TEP_EVENT_FL_FAILED; + len = 31; + } + + memcpy(print_format, start, len); + print_format[len] = 0; + + parse_arg_add(parse, print_format, + PRINT_FMT_ARG_STRING, *arg, len_arg, 0); + *arg = (*arg)->next; + ret++; + return ret; + default: + snprintf(print_format, 32, ">%c<", *format); + parse_arg_add(parse, print_format, + PRINT_FMT_STRING, NULL, NULL, 0); + ret++; + return ret; + } + ret++; + } + +out_failed: + return ret; + +} + +static int parse_arg_string(struct tep_print_parse **parse, const char *format) +{ + struct trace_seq s; + int ret = 0; + + trace_seq_init(&s); + for (; *format; format++) { + if (*format == '\\') { + format++; + ret++; + switch (*format) { + case 'n': + trace_seq_putc(&s, '\n'); + break; + case 't': + trace_seq_putc(&s, '\t'); + break; + case 'r': + trace_seq_putc(&s, '\r'); + break; + case '\\': + trace_seq_putc(&s, '\\'); + break; + default: + trace_seq_putc(&s, *format); + break; + } + } else if (*format == '%') { + if (*(format + 1) == '%') { + trace_seq_putc(&s, '%'); + format++; + ret++; + } else + break; + } else + trace_seq_putc(&s, *format); + + ret++; + } + trace_seq_terminate(&s); + parse_arg_add(parse, s.buffer, PRINT_FMT_STRING, NULL, NULL, 0); + trace_seq_destroy(&s); + + return ret; +} + +static struct tep_print_parse * +parse_args(struct tep_event *event, const char *format, struct tep_print_arg *arg) +{ + struct tep_print_parse *parse_ret = NULL; + struct tep_print_parse **parse = NULL; + int ret; + int len; + + len = strlen(format); + while (*format) { + if (!parse_ret) + parse = &parse_ret; + if (*format == '%' && *(format + 1) != '%') + ret = parse_arg_format(parse, event, format, &arg); + else + ret = parse_arg_string(parse, format); + if (*parse) + parse = &((*parse)->next); + + len -= ret; + if (len > 0) + format += ret; + else + break; + } + return parse_ret; +} + +static void print_event_cache(struct tep_print_parse *parse, struct trace_seq *s, + void *data, int size, struct tep_event *event) +{ + int len_arg; + + while (parse) { + if (parse->len_as_arg) + len_arg = eval_num_arg(data, size, event, parse->len_as_arg); + switch (parse->type) { + case PRINT_FMT_ARG_DIGIT: + print_arg_number(s, parse->format, + parse->len_as_arg ? len_arg : -1, data, + size, parse->ls, event, parse->arg); + break; + case PRINT_FMT_ARG_POINTER: + print_arg_pointer(s, parse->format, + parse->len_as_arg ? len_arg : 1, + data, size, event, parse->arg); + break; + case PRINT_FMT_ARG_STRING: + print_arg_string(s, parse->format, + parse->len_as_arg ? len_arg : -1, + data, size, event, parse->arg); + break; + case PRINT_FMT_STRING: + default: + trace_seq_printf(s, "%s", parse->format); + break; + } + parse = parse->next; + } +} + +static void pretty_print(struct trace_seq *s, void *data, int size, struct tep_event *event) +{ + struct tep_print_parse *parse = event->print_fmt.print_cache; + struct tep_print_arg *args = NULL; + char *bprint_fmt = NULL; + + if (event->flags & TEP_EVENT_FL_FAILED) { + trace_seq_printf(s, "[FAILED TO PARSE]"); + tep_print_fields(s, data, size, event); + return; + } + + if (event->flags & TEP_EVENT_FL_ISBPRINT) { + bprint_fmt = get_bprint_format(data, size, event); + args = make_bprint_args(bprint_fmt, data, size, event); + parse = parse_args(event, bprint_fmt, args); + } + + print_event_cache(parse, s, data, size, event); + + if (event->flags & TEP_EVENT_FL_ISBPRINT) { + free_parse_args(parse); + free_args(args); + free(bprint_fmt); + } +} + +/* + * This parses out the Latency format (interrupts disabled, + * need rescheduling, in hard/soft interrupt, preempt count + * and lock depth) and places it into the trace_seq. + */ +static void data_latency_format(struct tep_handle *tep, struct trace_seq *s, + char *format, struct tep_record *record) +{ + static int check_lock_depth = 1; + static int check_migrate_disable = 1; + static int lock_depth_exists; + static int migrate_disable_exists; + unsigned int lat_flags; + struct trace_seq sq; + unsigned int pc; + int lock_depth = 0; + int migrate_disable = 0; + int hardirq; + int softirq; + void *data = record->data; + + trace_seq_init(&sq); + lat_flags = parse_common_flags(tep, data); + pc = parse_common_pc(tep, data); + /* lock_depth may not always exist */ + if (lock_depth_exists) + lock_depth = parse_common_lock_depth(tep, data); + else if (check_lock_depth) { + lock_depth = parse_common_lock_depth(tep, data); + if (lock_depth < 0) + check_lock_depth = 0; + else + lock_depth_exists = 1; + } + + /* migrate_disable may not always exist */ + if (migrate_disable_exists) + migrate_disable = parse_common_migrate_disable(tep, data); + else if (check_migrate_disable) { + migrate_disable = parse_common_migrate_disable(tep, data); + if (migrate_disable < 0) + check_migrate_disable = 0; + else + migrate_disable_exists = 1; + } + + hardirq = lat_flags & TRACE_FLAG_HARDIRQ; + softirq = lat_flags & TRACE_FLAG_SOFTIRQ; + + trace_seq_printf(&sq, "%c%c%c", + (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' : + (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ? + 'X' : '.', + (lat_flags & TRACE_FLAG_NEED_RESCHED) ? + 'N' : '.', + (hardirq && softirq) ? 'H' : + hardirq ? 'h' : softirq ? 's' : '.'); + + if (pc) + trace_seq_printf(&sq, "%x", pc); + else + trace_seq_printf(&sq, "."); + + if (migrate_disable_exists) { + if (migrate_disable < 0) + trace_seq_printf(&sq, "."); + else + trace_seq_printf(&sq, "%d", migrate_disable); + } + + if (lock_depth_exists) { + if (lock_depth < 0) + trace_seq_printf(&sq, "."); + else + trace_seq_printf(&sq, "%d", lock_depth); + } + + if (sq.state == TRACE_SEQ__MEM_ALLOC_FAILED) { + s->state = TRACE_SEQ__MEM_ALLOC_FAILED; + return; + } + + trace_seq_terminate(&sq); + trace_seq_puts(s, sq.buffer); + trace_seq_destroy(&sq); + trace_seq_terminate(s); +} + +/** + * tep_data_type - parse out the given event type + * @tep: a handle to the trace event parser context + * @rec: the record to read from + * + * This returns the event id from the @rec. + */ +int tep_data_type(struct tep_handle *tep, struct tep_record *rec) +{ + return trace_parse_common_type(tep, rec->data); +} + +/** + * tep_data_pid - parse the PID from record + * @tep: a handle to the trace event parser context + * @rec: the record to parse + * + * This returns the PID from a record. + */ +int tep_data_pid(struct tep_handle *tep, struct tep_record *rec) +{ + return parse_common_pid(tep, rec->data); +} + +/** + * tep_data_preempt_count - parse the preempt count from the record + * @tep: a handle to the trace event parser context + * @rec: the record to parse + * + * This returns the preempt count from a record. + */ +int tep_data_preempt_count(struct tep_handle *tep, struct tep_record *rec) +{ + return parse_common_pc(tep, rec->data); +} + +/** + * tep_data_flags - parse the latency flags from the record + * @tep: a handle to the trace event parser context + * @rec: the record to parse + * + * This returns the latency flags from a record. + * + * Use trace_flag_type enum for the flags (see event-parse.h). + */ +int tep_data_flags(struct tep_handle *tep, struct tep_record *rec) +{ + return parse_common_flags(tep, rec->data); +} + +/** + * tep_data_comm_from_pid - return the command line from PID + * @tep: a handle to the trace event parser context + * @pid: the PID of the task to search for + * + * This returns a pointer to the command line that has the given + * @pid. + */ +const char *tep_data_comm_from_pid(struct tep_handle *tep, int pid) +{ + const char *comm; + + comm = find_cmdline(tep, pid); + return comm; +} + +static struct tep_cmdline * +pid_from_cmdlist(struct tep_handle *tep, const char *comm, struct tep_cmdline *next) +{ + struct cmdline_list *cmdlist = (struct cmdline_list *)next; + + if (cmdlist) + cmdlist = cmdlist->next; + else + cmdlist = tep->cmdlist; + + while (cmdlist && strcmp(cmdlist->comm, comm) != 0) + cmdlist = cmdlist->next; + + return (struct tep_cmdline *)cmdlist; +} + +/** + * tep_data_pid_from_comm - return the pid from a given comm + * @tep: a handle to the trace event parser context + * @comm: the cmdline to find the pid from + * @next: the cmdline structure to find the next comm + * + * This returns the cmdline structure that holds a pid for a given + * comm, or NULL if none found. As there may be more than one pid for + * a given comm, the result of this call can be passed back into + * a recurring call in the @next parameter, and then it will find the + * next pid. + * Also, it does a linear search, so it may be slow. + */ +struct tep_cmdline *tep_data_pid_from_comm(struct tep_handle *tep, const char *comm, + struct tep_cmdline *next) +{ + struct tep_cmdline *cmdline; + + /* + * If the cmdlines have not been converted yet, then use + * the list. + */ + if (!tep->cmdlines) + return pid_from_cmdlist(tep, comm, next); + + if (next) { + /* + * The next pointer could have been still from + * a previous call before cmdlines were created + */ + if (next < tep->cmdlines || + next >= tep->cmdlines + tep->cmdline_count) + next = NULL; + else + cmdline = next++; + } + + if (!next) + cmdline = tep->cmdlines; + + while (cmdline < tep->cmdlines + tep->cmdline_count) { + if (strcmp(cmdline->comm, comm) == 0) + return cmdline; + cmdline++; + } + return NULL; +} + +/** + * tep_cmdline_pid - return the pid associated to a given cmdline + * @tep: a handle to the trace event parser context + * @cmdline: The cmdline structure to get the pid from + * + * Returns the pid for a give cmdline. If @cmdline is NULL, then + * -1 is returned. + */ +int tep_cmdline_pid(struct tep_handle *tep, struct tep_cmdline *cmdline) +{ + struct cmdline_list *cmdlist = (struct cmdline_list *)cmdline; + + if (!cmdline) + return -1; + + /* + * If cmdlines have not been created yet, or cmdline is + * not part of the array, then treat it as a cmdlist instead. + */ + if (!tep->cmdlines || + cmdline < tep->cmdlines || + cmdline >= tep->cmdlines + tep->cmdline_count) + return cmdlist->pid; + + return cmdline->pid; +} + +/* + * This parses the raw @data using the given @event information and + * writes the print format into the trace_seq. + */ +static void print_event_info(struct trace_seq *s, char *format, bool raw, + struct tep_event *event, struct tep_record *record) +{ + int print_pretty = 1; + + if (raw || (event->flags & TEP_EVENT_FL_PRINTRAW)) + tep_print_fields(s, record->data, record->size, event); + else { + + if (event->handler && !(event->flags & TEP_EVENT_FL_NOHANDLE)) + print_pretty = event->handler(s, record, event, + event->context); + + if (print_pretty) + pretty_print(s, record->data, record->size, event); + } + + trace_seq_terminate(s); +} + +/** + * tep_find_event_by_record - return the event from a given record + * @tep: a handle to the trace event parser context + * @record: The record to get the event from + * + * Returns the associated event for a given record, or NULL if non is + * is found. + */ +struct tep_event * +tep_find_event_by_record(struct tep_handle *tep, struct tep_record *record) +{ + int type; + + if (record->size < 0) { + do_warning("ug! negative record size %d", record->size); + return NULL; + } + + type = trace_parse_common_type(tep, record->data); + + return tep_find_event(tep, type); +} + +/* + * Writes the timestamp of the record into @s. Time divisor and precision can be + * specified as part of printf @format string. Example: + * "%3.1000d" - divide the time by 1000 and print the first 3 digits + * before the dot. Thus, the timestamp "123456000" will be printed as + * "123.456" + */ +static void print_event_time(struct tep_handle *tep, struct trace_seq *s, + char *format, struct tep_event *event, + struct tep_record *record) +{ + unsigned long long time; + char *divstr; + int prec = 0, pr; + int div = 0; + int p10 = 1; + + if (isdigit(*(format + 1))) + prec = atoi(format + 1); + divstr = strchr(format, '.'); + if (divstr && isdigit(*(divstr + 1))) + div = atoi(divstr + 1); + time = record->ts; + if (div) { + time += div / 2; + time /= div; + } + pr = prec; + while (pr--) + p10 *= 10; + + if (p10 > 1 && p10 < time) + trace_seq_printf(s, "%5llu.%0*llu", time / p10, prec, time % p10); + else + trace_seq_printf(s, "%12llu", time); +} + +struct print_event_type { + enum { + EVENT_TYPE_INT = 1, + EVENT_TYPE_STRING, + EVENT_TYPE_UNKNOWN, + } type; + char format[32]; +}; + +static void print_string(struct tep_handle *tep, struct trace_seq *s, + struct tep_record *record, struct tep_event *event, + const char *arg, struct print_event_type *type) +{ + const char *comm; + int pid; + + if (strncmp(arg, TEP_PRINT_LATENCY, strlen(TEP_PRINT_LATENCY)) == 0) { + data_latency_format(tep, s, type->format, record); + } else if (strncmp(arg, TEP_PRINT_COMM, strlen(TEP_PRINT_COMM)) == 0) { + pid = parse_common_pid(tep, record->data); + comm = find_cmdline(tep, pid); + trace_seq_printf(s, type->format, comm); + } else if (strncmp(arg, TEP_PRINT_INFO_RAW, strlen(TEP_PRINT_INFO_RAW)) == 0) { + print_event_info(s, type->format, true, event, record); + } else if (strncmp(arg, TEP_PRINT_INFO, strlen(TEP_PRINT_INFO)) == 0) { + print_event_info(s, type->format, false, event, record); + } else if (strncmp(arg, TEP_PRINT_NAME, strlen(TEP_PRINT_NAME)) == 0) { + trace_seq_printf(s, type->format, event->name); + } else { + trace_seq_printf(s, "[UNKNOWN TEP TYPE %s]", arg); + } + +} + +static void print_int(struct tep_handle *tep, struct trace_seq *s, + struct tep_record *record, struct tep_event *event, + int arg, struct print_event_type *type) +{ + int param; + + switch (arg) { + case TEP_PRINT_CPU: + param = record->cpu; + break; + case TEP_PRINT_PID: + param = parse_common_pid(tep, record->data); + break; + case TEP_PRINT_TIME: + return print_event_time(tep, s, type->format, event, record); + default: + return; + } + trace_seq_printf(s, type->format, param); +} + +static int tep_print_event_param_type(char *format, + struct print_event_type *type) +{ + char *str = format + 1; + int i = 1; + + type->type = EVENT_TYPE_UNKNOWN; + while (*str) { + switch (*str) { + case 'd': + case 'u': + case 'i': + case 'x': + case 'X': + case 'o': + type->type = EVENT_TYPE_INT; + break; + case 's': + type->type = EVENT_TYPE_STRING; + break; + } + str++; + i++; + if (type->type != EVENT_TYPE_UNKNOWN) + break; + } + memset(type->format, 0, 32); + memcpy(type->format, format, i < 32 ? i : 31); + return i; +} + +/** + * tep_print_event - Write various event information + * @tep: a handle to the trace event parser context + * @s: the trace_seq to write to + * @record: The record to get the event from + * @format: a printf format string. Supported event fileds: + * TEP_PRINT_PID, "%d" - event PID + * TEP_PRINT_CPU, "%d" - event CPU + * TEP_PRINT_COMM, "%s" - event command string + * TEP_PRINT_NAME, "%s" - event name + * TEP_PRINT_LATENCY, "%s" - event latency + * TEP_PRINT_TIME, %d - event time stamp. A divisor and precision + * can be specified as part of this format string: + * "%precision.divisord". Example: + * "%3.1000d" - divide the time by 1000 and print the first + * 3 digits before the dot. Thus, the time stamp + * "123456000" will be printed as "123.456" + * TEP_PRINT_INFO, "%s" - event information. If any width is specified in + * the format string, the event information will be printed + * in raw format. + * Writes the specified event information into @s. + */ +void tep_print_event(struct tep_handle *tep, struct trace_seq *s, + struct tep_record *record, const char *fmt, ...) +{ + struct print_event_type type; + char *format = strdup(fmt); + char *current = format; + char *str = format; + int offset; + va_list args; + struct tep_event *event; + + if (!format) + return; + + event = tep_find_event_by_record(tep, record); + va_start(args, fmt); + while (*current) { + current = strchr(str, '%'); + if (!current) { + trace_seq_puts(s, str); + break; + } + memset(&type, 0, sizeof(type)); + offset = tep_print_event_param_type(current, &type); + *current = '\0'; + trace_seq_puts(s, str); + current += offset; + switch (type.type) { + case EVENT_TYPE_STRING: + print_string(tep, s, record, event, + va_arg(args, char*), &type); + break; + case EVENT_TYPE_INT: + print_int(tep, s, record, event, + va_arg(args, int), &type); + break; + case EVENT_TYPE_UNKNOWN: + default: + trace_seq_printf(s, "[UNKNOWN TYPE]"); + break; + } + str = current; + + } + va_end(args); + free(format); +} + +static int events_id_cmp(const void *a, const void *b) +{ + struct tep_event * const * ea = a; + struct tep_event * const * eb = b; + + if ((*ea)->id < (*eb)->id) + return -1; + + if ((*ea)->id > (*eb)->id) + return 1; + + return 0; +} + +static int events_name_cmp(const void *a, const void *b) +{ + struct tep_event * const * ea = a; + struct tep_event * const * eb = b; + int res; + + res = strcmp((*ea)->name, (*eb)->name); + if (res) + return res; + + res = strcmp((*ea)->system, (*eb)->system); + if (res) + return res; + + return events_id_cmp(a, b); +} + +static int events_system_cmp(const void *a, const void *b) +{ + struct tep_event * const * ea = a; + struct tep_event * const * eb = b; + int res; + + res = strcmp((*ea)->system, (*eb)->system); + if (res) + return res; + + res = strcmp((*ea)->name, (*eb)->name); + if (res) + return res; + + return events_id_cmp(a, b); +} + +static struct tep_event **list_events_copy(struct tep_handle *tep) +{ + struct tep_event **events; + + if (!tep) + return NULL; + + events = malloc(sizeof(*events) * (tep->nr_events + 1)); + if (!events) + return NULL; + + memcpy(events, tep->events, sizeof(*events) * tep->nr_events); + events[tep->nr_events] = NULL; + return events; +} + +static void list_events_sort(struct tep_event **events, int nr_events, + enum tep_event_sort_type sort_type) +{ + int (*sort)(const void *a, const void *b); + + switch (sort_type) { + case TEP_EVENT_SORT_ID: + sort = events_id_cmp; + break; + case TEP_EVENT_SORT_NAME: + sort = events_name_cmp; + break; + case TEP_EVENT_SORT_SYSTEM: + sort = events_system_cmp; + break; + default: + sort = NULL; + } + + if (sort) + qsort(events, nr_events, sizeof(*events), sort); +} + +/** + * tep_list_events - Get events, sorted by given criteria. + * @tep: a handle to the tep context + * @sort_type: desired sort order of the events in the array + * + * Returns an array of pointers to all events, sorted by the given + * @sort_type criteria. The last element of the array is NULL. The returned + * memory must not be freed, it is managed by the library. + * The function is not thread safe. + */ +struct tep_event **tep_list_events(struct tep_handle *tep, + enum tep_event_sort_type sort_type) +{ + struct tep_event **events; + + if (!tep) + return NULL; + + events = tep->sort_events; + if (events && tep->last_type == sort_type) + return events; + + if (!events) { + events = list_events_copy(tep); + if (!events) + return NULL; + + tep->sort_events = events; + + /* the internal events are sorted by id */ + if (sort_type == TEP_EVENT_SORT_ID) { + tep->last_type = sort_type; + return events; + } + } + + list_events_sort(events, tep->nr_events, sort_type); + tep->last_type = sort_type; + + return events; +} + + +/** + * tep_list_events_copy - Thread safe version of tep_list_events() + * @tep: a handle to the tep context + * @sort_type: desired sort order of the events in the array + * + * Returns an array of pointers to all events, sorted by the given + * @sort_type criteria. The last element of the array is NULL. The returned + * array is newly allocated inside the function and must be freed by the caller + */ +struct tep_event **tep_list_events_copy(struct tep_handle *tep, + enum tep_event_sort_type sort_type) +{ + struct tep_event **events; + + if (!tep) + return NULL; + + events = list_events_copy(tep); + if (!events) + return NULL; + + /* the internal events are sorted by id */ + if (sort_type == TEP_EVENT_SORT_ID) + return events; + + list_events_sort(events, tep->nr_events, sort_type); + + return events; +} + +static struct tep_format_field ** +get_event_fields(const char *type, const char *name, + int count, struct tep_format_field *list) +{ + struct tep_format_field **fields; + struct tep_format_field *field; + int i = 0; + + fields = malloc(sizeof(*fields) * (count + 1)); + if (!fields) + return NULL; + + for (field = list; field; field = field->next) { + fields[i++] = field; + if (i == count + 1) { + do_warning("event %s has more %s fields than specified", + name, type); + i--; + break; + } + } + + if (i != count) + do_warning("event %s has less %s fields than specified", + name, type); + + fields[i] = NULL; + + return fields; +} + +/** + * tep_event_common_fields - return a list of common fields for an event + * @event: the event to return the common fields of. + * + * Returns an allocated array of fields. The last item in the array is NULL. + * The array must be freed with free(). + */ +struct tep_format_field **tep_event_common_fields(struct tep_event *event) +{ + return get_event_fields("common", event->name, + event->format.nr_common, + event->format.common_fields); +} + +/** + * tep_event_fields - return a list of event specific fields for an event + * @event: the event to return the fields of. + * + * Returns an allocated array of fields. The last item in the array is NULL. + * The array must be freed with free(). + */ +struct tep_format_field **tep_event_fields(struct tep_event *event) +{ + return get_event_fields("event", event->name, + event->format.nr_fields, + event->format.fields); +} + +static void print_fields(struct trace_seq *s, struct tep_print_flag_sym *field) +{ + trace_seq_printf(s, "{ %s, %s }", field->value, field->str); + if (field->next) { + trace_seq_puts(s, ", "); + print_fields(s, field->next); + } +} + +/* for debugging */ +static void print_args(struct tep_print_arg *args) +{ + int print_paren = 1; + struct trace_seq s; + + switch (args->type) { + case TEP_PRINT_NULL: + printf("null"); + break; + case TEP_PRINT_ATOM: + printf("%s", args->atom.atom); + break; + case TEP_PRINT_FIELD: + printf("REC->%s", args->field.name); + break; + case TEP_PRINT_FLAGS: + printf("__print_flags("); + print_args(args->flags.field); + printf(", %s, ", args->flags.delim); + trace_seq_init(&s); + print_fields(&s, args->flags.flags); + trace_seq_do_printf(&s); + trace_seq_destroy(&s); + printf(")"); + break; + case TEP_PRINT_SYMBOL: + printf("__print_symbolic("); + print_args(args->symbol.field); + printf(", "); + trace_seq_init(&s); + print_fields(&s, args->symbol.symbols); + trace_seq_do_printf(&s); + trace_seq_destroy(&s); + printf(")"); + break; + case TEP_PRINT_HEX: + printf("__print_hex("); + print_args(args->hex.field); + printf(", "); + print_args(args->hex.size); + printf(")"); + break; + case TEP_PRINT_HEX_STR: + printf("__print_hex_str("); + print_args(args->hex.field); + printf(", "); + print_args(args->hex.size); + printf(")"); + break; + case TEP_PRINT_INT_ARRAY: + printf("__print_array("); + print_args(args->int_array.field); + printf(", "); + print_args(args->int_array.count); + printf(", "); + print_args(args->int_array.el_size); + printf(")"); + break; + case TEP_PRINT_STRING: + case TEP_PRINT_BSTRING: + printf("__get_str(%s)", args->string.string); + break; + case TEP_PRINT_BITMASK: + printf("__get_bitmask(%s)", args->bitmask.bitmask); + break; + case TEP_PRINT_TYPE: + printf("(%s)", args->typecast.type); + print_args(args->typecast.item); + break; + case TEP_PRINT_OP: + if (strcmp(args->op.op, ":") == 0) + print_paren = 0; + if (print_paren) + printf("("); + print_args(args->op.left); + printf(" %s ", args->op.op); + print_args(args->op.right); + if (print_paren) + printf(")"); + break; + default: + /* we should warn... */ + return; + } + if (args->next) { + printf("\n"); + print_args(args->next); + } +} + +static void parse_header_field(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(TEP_EVENT_ITEM, "field") < 0) + return; + if (read_expected(TEP_EVENT_OP, ":") < 0) + return; + + /* type */ + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + free_token(token); + + /* + * If this is not a mandatory field, then test it first. + */ + if (mandatory) { + if (read_expected(TEP_EVENT_ITEM, field) < 0) + return; + } else { + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + if (strcmp(token, field) != 0) + goto discard; + free_token(token); + } + + if (read_expected(TEP_EVENT_OP, ";") < 0) + return; + if (read_expected(TEP_EVENT_ITEM, "offset") < 0) + return; + if (read_expected(TEP_EVENT_OP, ":") < 0) + return; + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + *offset = atoi(token); + free_token(token); + if (read_expected(TEP_EVENT_OP, ";") < 0) + return; + if (read_expected(TEP_EVENT_ITEM, "size") < 0) + return; + if (read_expected(TEP_EVENT_OP, ":") < 0) + return; + if (read_expect_type(TEP_EVENT_ITEM, &token) < 0) + goto fail; + *size = atoi(token); + free_token(token); + if (read_expected(TEP_EVENT_OP, ";") < 0) + return; + type = read_token(&token); + if (type != TEP_EVENT_NEWLINE) { + /* newer versions of the kernel have a "signed" type */ + if (type != TEP_EVENT_ITEM) + goto fail; + + if (strcmp(token, "signed") != 0) + goto fail; + + free_token(token); + + if (read_expected(TEP_EVENT_OP, ":") < 0) + return; + + if (read_expect_type(TEP_EVENT_ITEM, &token)) + goto fail; + + free_token(token); + if (read_expected(TEP_EVENT_OP, ";") < 0) + return; + + if (read_expect_type(TEP_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); +} + +/** + * tep_parse_header_page - parse the data stored in the header page + * @tep: a handle to the trace event parser context + * @buf: the buffer storing the header page format string + * @size: the size of @buf + * @long_size: the long size to use if there is no header + * + * This parses the header page format for information on the + * ring buffer used. The @buf should be copied from + * + * /sys/kernel/debug/tracing/events/header_page + */ +int tep_parse_header_page(struct tep_handle *tep, char *buf, unsigned long size, + int long_size) +{ + int ignore; + + if (!size) { + /* + * Old kernels did not have header page info. + * Sorry but we just use what we find here in user space. + */ + tep->header_page_ts_size = sizeof(long long); + tep->header_page_size_size = long_size; + tep->header_page_data_offset = sizeof(long long) + long_size; + tep->old_format = 1; + return -1; + } + init_input_buf(buf, size); + + parse_header_field("timestamp", &tep->header_page_ts_offset, + &tep->header_page_ts_size, 1); + parse_header_field("commit", &tep->header_page_size_offset, + &tep->header_page_size_size, 1); + parse_header_field("overwrite", &tep->header_page_overwrite, + &ignore, 0); + parse_header_field("data", &tep->header_page_data_offset, + &tep->header_page_data_size, 1); + + return 0; +} + +static int event_matches(struct tep_event *event, + int id, const char *sys_name, + const char *event_name) +{ + if (id >= 0 && id != event->id) + return 0; + + if (event_name && (strcmp(event_name, event->name) != 0)) + return 0; + + if (sys_name && (strcmp(sys_name, event->system) != 0)) + return 0; + + return 1; +} + +static void free_handler(struct event_handler *handle) +{ + free((void *)handle->sys_name); + free((void *)handle->event_name); + free(handle); +} + +static int find_event_handle(struct tep_handle *tep, struct tep_event *event) +{ + struct event_handler *handle, **next; + + for (next = &tep->handlers; *next; + next = &(*next)->next) { + handle = *next; + if (event_matches(event, handle->id, + handle->sys_name, + handle->event_name)) + break; + } + + if (!(*next)) + return 0; + + 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; +} + +/** + * parse_format - parse the event format + * @buf: the buffer storing the event format string + * @size: the size of @buf + * @sys: the system the event belongs to + * + * This parses the event format and creates an event structure + * to quickly parse raw data for a given event. + * + * These files currently come from: + * + * /sys/kernel/debug/tracing/events/.../.../format + */ +static enum tep_errno parse_format(struct tep_event **eventp, + struct tep_handle *tep, const char *buf, + unsigned long size, const char *sys) +{ + struct tep_event *event; + int ret; + + init_input_buf(buf, size); + + *eventp = event = alloc_event(); + if (!event) + return TEP_ERRNO__MEM_ALLOC_FAILED; + + event->name = event_read_name(); + if (!event->name) { + /* Bad event? */ + ret = TEP_ERRNO__MEM_ALLOC_FAILED; + goto event_alloc_failed; + } + + if (strcmp(sys, "ftrace") == 0) { + event->flags |= TEP_EVENT_FL_ISFTRACE; + + if (strcmp(event->name, "bprint") == 0) + event->flags |= TEP_EVENT_FL_ISBPRINT; + } + + event->id = event_read_id(); + if (event->id < 0) { + ret = TEP_ERRNO__READ_ID_FAILED; + /* + * This isn't an allocation error actually. + * But as the ID is critical, just bail out. + */ + goto event_alloc_failed; + } + + event->system = strdup(sys); + if (!event->system) { + ret = TEP_ERRNO__MEM_ALLOC_FAILED; + goto event_alloc_failed; + } + + /* Add tep to event so that it can be referenced */ + event->tep = tep; + + ret = event_read_format(event); + if (ret < 0) { + ret = TEP_ERRNO__READ_FORMAT_FAILED; + goto event_parse_failed; + } + + /* + * If the event has an override, don't print warnings if the event + * print format fails to parse. + */ + if (tep && find_event_handle(tep, event)) + show_warning = 0; + + ret = event_read_print(event); + show_warning = 1; + + if (ret < 0) { + ret = TEP_ERRNO__READ_PRINT_FAILED; + goto event_parse_failed; + } + + if (!ret && (event->flags & TEP_EVENT_FL_ISFTRACE)) { + struct tep_format_field *field; + struct tep_print_arg *arg, **list; + + /* old ftrace had no args */ + list = &event->print_fmt.args; + for (field = event->format.fields; field; field = field->next) { + arg = alloc_arg(); + if (!arg) { + event->flags |= TEP_EVENT_FL_FAILED; + return TEP_ERRNO__OLD_FTRACE_ARG_FAILED; + } + arg->type = TEP_PRINT_FIELD; + arg->field.name = strdup(field->name); + if (!arg->field.name) { + event->flags |= TEP_EVENT_FL_FAILED; + free_arg(arg); + return TEP_ERRNO__OLD_FTRACE_ARG_FAILED; + } + arg->field.field = field; + *list = arg; + list = &arg->next; + } + } + + if (!(event->flags & TEP_EVENT_FL_ISBPRINT)) + event->print_fmt.print_cache = parse_args(event, + event->print_fmt.format, + event->print_fmt.args); + + return 0; + + event_parse_failed: + event->flags |= TEP_EVENT_FL_FAILED; + return ret; + + event_alloc_failed: + free(event->system); + free(event->name); + free(event); + *eventp = NULL; + return ret; +} + +static enum tep_errno +__parse_event(struct tep_handle *tep, + struct tep_event **eventp, + const char *buf, unsigned long size, + const char *sys) +{ + int ret = parse_format(eventp, tep, buf, size, sys); + struct tep_event *event = *eventp; + + if (event == NULL) + return ret; + + if (tep && add_event(tep, event)) { + ret = TEP_ERRNO__MEM_ALLOC_FAILED; + goto event_add_failed; + } + +#define PRINT_ARGS 0 + if (PRINT_ARGS && event->print_fmt.args) + print_args(event->print_fmt.args); + + return 0; + +event_add_failed: + free_tep_event(event); + return ret; +} + +/** + * tep_parse_format - parse the event format + * @tep: a handle to the trace event parser context + * @eventp: returned format + * @buf: the buffer storing the event format string + * @size: the size of @buf + * @sys: the system the event belongs to + * + * This parses the event format and creates an event structure + * to quickly parse raw data for a given event. + * + * These files currently come from: + * + * /sys/kernel/debug/tracing/events/.../.../format + */ +enum tep_errno tep_parse_format(struct tep_handle *tep, + struct tep_event **eventp, + const char *buf, + unsigned long size, const char *sys) +{ + return __parse_event(tep, eventp, buf, size, sys); +} + +/** + * tep_parse_event - parse the event format + * @tep: a handle to the trace event parser context + * @buf: the buffer storing the event format string + * @size: the size of @buf + * @sys: the system the event belongs to + * + * This parses the event format and creates an event structure + * to quickly parse raw data for a given event. + * + * These files currently come from: + * + * /sys/kernel/debug/tracing/events/.../.../format + */ +enum tep_errno tep_parse_event(struct tep_handle *tep, const char *buf, + unsigned long size, const char *sys) +{ + struct tep_event *event = NULL; + return __parse_event(tep, &event, buf, size, sys); +} + +int get_field_val(struct trace_seq *s, struct tep_format_field *field, + const char *name, struct tep_record *record, + unsigned long long *val, int err) +{ + if (!field) { + if (err) + trace_seq_printf(s, "<CANT FIND FIELD %s>", name); + return -1; + } + + if (tep_read_number_field(field, record->data, val)) { + if (err) + trace_seq_printf(s, " %s=INVALID", name); + return -1; + } + + return 0; +} + +/** + * tep_get_field_raw - return the raw pointer into the data field + * @s: The seq to print to on error + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @len: place to store the field length. + * @err: print default error if failed. + * + * Returns a pointer into record->data of the field and places + * the length of the field in @len. + * + * On failure, it returns NULL. + */ +void *tep_get_field_raw(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + int *len, int err) +{ + struct tep_format_field *field; + void *data = record->data; + unsigned offset; + int dummy; + + if (!event) + return NULL; + + field = tep_find_field(event, name); + + if (!field) { + if (err) + trace_seq_printf(s, "<CANT FIND FIELD %s>", name); + return NULL; + } + + /* Allow @len to be NULL */ + if (!len) + len = &dummy; + + offset = field->offset; + if (field->flags & TEP_FIELD_IS_DYNAMIC) { + offset = tep_read_number(event->tep, + data + offset, field->size); + *len = offset >> 16; + offset &= 0xffff; + } else + *len = field->size; + + return data + offset; +} + +/** + * tep_get_field_val - find a field and return its value + * @s: The seq to print to on error + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @val: place to store the value of the field. + * @err: print default error if failed. + * + * Returns 0 on success -1 on field not found. + */ +int tep_get_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err) +{ + struct tep_format_field *field; + + if (!event) + return -1; + + field = tep_find_field(event, name); + + return get_field_val(s, field, name, record, val, err); +} + +/** + * tep_get_common_field_val - find a common field and return its value + * @s: The seq to print to on error + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @val: place to store the value of the field. + * @err: print default error if failed. + * + * Returns 0 on success -1 on field not found. + */ +int tep_get_common_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err) +{ + struct tep_format_field *field; + + if (!event) + return -1; + + field = tep_find_common_field(event, name); + + return get_field_val(s, field, name, record, val, err); +} + +/** + * tep_get_any_field_val - find a any field and return its value + * @s: The seq to print to on error + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @val: place to store the value of the field. + * @err: print default error if failed. + * + * Returns 0 on success -1 on field not found. + */ +int tep_get_any_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err) +{ + struct tep_format_field *field; + + if (!event) + return -1; + + field = tep_find_any_field(event, name); + + return get_field_val(s, field, name, record, val, err); +} + +/** + * tep_print_num_field - print a field and a format + * @s: The seq to print to + * @fmt: The printf format to print the field with. + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @err: print default error if failed. + * + * Returns positive value on success, negative in case of an error, + * or 0 if buffer is full. + */ +int tep_print_num_field(struct trace_seq *s, const char *fmt, + struct tep_event *event, const char *name, + struct tep_record *record, int err) +{ + struct tep_format_field *field = tep_find_field(event, name); + unsigned long long val; + + if (!field) + goto failed; + + if (tep_read_number_field(field, record->data, &val)) + goto failed; + + return trace_seq_printf(s, fmt, val); + + failed: + if (err) + trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); + return -1; +} + +/** + * tep_print_func_field - print a field and a format for function pointers + * @s: The seq to print to + * @fmt: The printf format to print the field with. + * @event: the event that the field is for + * @name: The name of the field + * @record: The record with the field name. + * @err: print default error if failed. + * + * Returns positive value on success, negative in case of an error, + * or 0 if buffer is full. + */ +int tep_print_func_field(struct trace_seq *s, const char *fmt, + struct tep_event *event, const char *name, + struct tep_record *record, int err) +{ + struct tep_format_field *field = tep_find_field(event, name); + struct tep_handle *tep = event->tep; + unsigned long long val; + struct func_map *func; + char tmp[128]; + + if (!field) + goto failed; + + if (tep_read_number_field(field, record->data, &val)) + goto failed; + + func = find_func(tep, val); + + if (func) + snprintf(tmp, 128, "%s/0x%llx", func->func, func->addr - val); + else + sprintf(tmp, "0x%08llx", val); + + return trace_seq_printf(s, fmt, tmp); + + failed: + if (err) + trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); + return -1; +} + +static void free_func_handle(struct tep_function_handler *func) +{ + struct func_params *params; + + free(func->name); + + while (func->params) { + params = func->params; + func->params = params->next; + free(params); + } + + free(func); +} + +/** + * tep_register_print_function - register a helper function + * @tep: a handle to the trace event parser context + * @func: the function to process the helper function + * @ret_type: the return type of the helper function + * @name: the name of the helper function + * @parameters: A list of enum tep_func_arg_type + * + * Some events may have helper functions in the print format arguments. + * This allows a plugin to dynamically create a way to process one + * of these functions. + * + * The @parameters is a variable list of tep_func_arg_type enums that + * must end with TEP_FUNC_ARG_VOID. + */ +int tep_register_print_function(struct tep_handle *tep, + tep_func_handler func, + enum tep_func_arg_type ret_type, + char *name, ...) +{ + struct tep_function_handler *func_handle; + struct func_params **next_param; + struct func_params *param; + enum tep_func_arg_type type; + va_list ap; + int ret; + + func_handle = find_func_handler(tep, name); + if (func_handle) { + /* + * This is most like caused by the users own + * plugins updating the function. This overrides the + * system defaults. + */ + pr_stat("override of function helper '%s'", name); + remove_func_handler(tep, name); + } + + func_handle = calloc(1, sizeof(*func_handle)); + if (!func_handle) { + do_warning("Failed to allocate function handler"); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + func_handle->ret_type = ret_type; + func_handle->name = strdup(name); + func_handle->func = func; + if (!func_handle->name) { + do_warning("Failed to allocate function name"); + free(func_handle); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + next_param = &(func_handle->params); + va_start(ap, name); + for (;;) { + type = va_arg(ap, enum tep_func_arg_type); + if (type == TEP_FUNC_ARG_VOID) + break; + + if (type >= TEP_FUNC_ARG_MAX_TYPES) { + do_warning("Invalid argument type %d", type); + ret = TEP_ERRNO__INVALID_ARG_TYPE; + goto out_free; + } + + param = malloc(sizeof(*param)); + if (!param) { + do_warning("Failed to allocate function param"); + ret = TEP_ERRNO__MEM_ALLOC_FAILED; + goto out_free; + } + param->type = type; + param->next = NULL; + + *next_param = param; + next_param = &(param->next); + + func_handle->nr_args++; + } + va_end(ap); + + func_handle->next = tep->func_handlers; + tep->func_handlers = func_handle; + + return 0; + out_free: + va_end(ap); + free_func_handle(func_handle); + return ret; +} + +/** + * tep_unregister_print_function - unregister a helper function + * @tep: a handle to the trace event parser context + * @func: the function to process the helper function + * @name: the name of the helper function + * + * This function removes existing print handler for function @name. + * + * Returns 0 if the handler was removed successully, -1 otherwise. + */ +int tep_unregister_print_function(struct tep_handle *tep, + tep_func_handler func, char *name) +{ + struct tep_function_handler *func_handle; + + func_handle = find_func_handler(tep, name); + if (func_handle && func_handle->func == func) { + remove_func_handler(tep, name); + return 0; + } + return -1; +} + +static struct tep_event *search_event(struct tep_handle *tep, int id, + const char *sys_name, + const char *event_name) +{ + struct tep_event *event; + + if (id >= 0) { + /* search by id */ + event = tep_find_event(tep, id); + if (!event) + return NULL; + if (event_name && (strcmp(event_name, event->name) != 0)) + return NULL; + if (sys_name && (strcmp(sys_name, event->system) != 0)) + return NULL; + } else { + event = tep_find_event_by_name(tep, sys_name, event_name); + if (!event) + return NULL; + } + return event; +} + +/** + * tep_register_event_handler - register a way to parse an event + * @tep: a handle to the trace event parser context + * @id: the id of the event to register + * @sys_name: the system name the event belongs to + * @event_name: the name of the event + * @func: the function to call to parse the event information + * @context: the data to be passed to @func + * + * This function allows a developer to override the parsing of + * a given event. If for some reason the default print format + * is not sufficient, this function will register a function + * for an event to be used to parse the data instead. + * + * If @id is >= 0, then it is used to find the event. + * else @sys_name and @event_name are used. + * + * Returns: + * TEP_REGISTER_SUCCESS_OVERWRITE if an existing handler is overwritten + * TEP_REGISTER_SUCCESS if a new handler is registered successfully + * negative TEP_ERRNO_... in case of an error + * + */ +int tep_register_event_handler(struct tep_handle *tep, int id, + const char *sys_name, const char *event_name, + tep_event_handler_func func, void *context) +{ + struct tep_event *event; + struct event_handler *handle; + + event = search_event(tep, id, sys_name, event_name); + if (event == NULL) + goto not_found; + + pr_stat("overriding event (%d) %s:%s with new print handler", + event->id, event->system, event->name); + + event->handler = func; + event->context = context; + return TEP_REGISTER_SUCCESS_OVERWRITE; + + not_found: + /* Save for later use. */ + handle = calloc(1, sizeof(*handle)); + if (!handle) { + do_warning("Failed to allocate event handler"); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + handle->id = id; + if (event_name) + handle->event_name = strdup(event_name); + if (sys_name) + handle->sys_name = strdup(sys_name); + + if ((event_name && !handle->event_name) || + (sys_name && !handle->sys_name)) { + do_warning("Failed to allocate event/sys name"); + free((void *)handle->event_name); + free((void *)handle->sys_name); + free(handle); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + handle->func = func; + handle->next = tep->handlers; + tep->handlers = handle; + handle->context = context; + + return TEP_REGISTER_SUCCESS; +} + +static int handle_matches(struct event_handler *handler, int id, + const char *sys_name, const char *event_name, + tep_event_handler_func func, void *context) +{ + if (id >= 0 && id != handler->id) + return 0; + + if (event_name && (strcmp(event_name, handler->event_name) != 0)) + return 0; + + if (sys_name && (strcmp(sys_name, handler->sys_name) != 0)) + return 0; + + if (func != handler->func || context != handler->context) + return 0; + + return 1; +} + +/** + * tep_unregister_event_handler - unregister an existing event handler + * @tep: a handle to the trace event parser context + * @id: the id of the event to unregister + * @sys_name: the system name the handler belongs to + * @event_name: the name of the event handler + * @func: the function to call to parse the event information + * @context: the data to be passed to @func + * + * This function removes existing event handler (parser). + * + * If @id is >= 0, then it is used to find the event. + * else @sys_name and @event_name are used. + * + * Returns 0 if handler was removed successfully, -1 if event was not found. + */ +int tep_unregister_event_handler(struct tep_handle *tep, int id, + const char *sys_name, const char *event_name, + tep_event_handler_func func, void *context) +{ + struct tep_event *event; + struct event_handler *handle; + struct event_handler **next; + + event = search_event(tep, id, sys_name, event_name); + if (event == NULL) + goto not_found; + + if (event->handler == func && event->context == context) { + 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 = &tep->handlers; *next; next = &(*next)->next) { + handle = *next; + if (handle_matches(handle, id, sys_name, event_name, + func, context)) + break; + } + + if (!(*next)) + return -1; + + *next = handle->next; + free_handler(handle); + + return 0; +} + +/** + * tep_alloc - create a tep handle + */ +struct tep_handle *tep_alloc(void) +{ + struct tep_handle *tep = calloc(1, sizeof(*tep)); + + if (tep) { + tep->ref_count = 1; + tep->host_bigendian = tep_is_bigendian(); + } + + return tep; +} + +void tep_ref(struct tep_handle *tep) +{ + tep->ref_count++; +} + +int tep_get_ref(struct tep_handle *tep) +{ + if (tep) + return tep->ref_count; + return 0; +} + +__hidden void free_tep_format_field(struct tep_format_field *field) +{ + free(field->type); + if (field->alias != field->name) + free(field->alias); + free(field->name); + free(field); +} + +static void free_format_fields(struct tep_format_field *field) +{ + struct tep_format_field *next; + + while (field) { + next = field->next; + free_tep_format_field(field); + field = next; + } +} + +static void free_formats(struct tep_format *format) +{ + free_format_fields(format->common_fields); + free_format_fields(format->fields); +} + +__hidden void free_tep_event(struct tep_event *event) +{ + free(event->name); + free(event->system); + + free_formats(&event->format); + + free(event->print_fmt.format); + free_args(event->print_fmt.args); + free_parse_args(event->print_fmt.print_cache); + free(event); +} + +/** + * tep_free - free a tep handle + * @tep: the tep handle to free + */ +void tep_free(struct tep_handle *tep) +{ + struct cmdline_list *cmdlist, *cmdnext; + struct func_list *funclist, *funcnext; + struct printk_list *printklist, *printknext; + struct tep_function_handler *func_handler; + struct event_handler *handle; + int i; + + if (!tep) + return; + + cmdlist = tep->cmdlist; + funclist = tep->funclist; + printklist = tep->printklist; + + tep->ref_count--; + if (tep->ref_count) + return; + + if (tep->cmdlines) { + for (i = 0; i < tep->cmdline_count; i++) + free(tep->cmdlines[i].comm); + free(tep->cmdlines); + } + + while (cmdlist) { + cmdnext = cmdlist->next; + free(cmdlist->comm); + free(cmdlist); + cmdlist = cmdnext; + } + + if (tep->func_map) { + for (i = 0; i < (int)tep->func_count; i++) { + free(tep->func_map[i].func); + free(tep->func_map[i].mod); + } + free(tep->func_map); + } + + while (funclist) { + funcnext = funclist->next; + free(funclist->func); + free(funclist->mod); + free(funclist); + funclist = funcnext; + } + + while (tep->func_handlers) { + func_handler = tep->func_handlers; + tep->func_handlers = func_handler->next; + free_func_handle(func_handler); + } + + if (tep->printk_map) { + for (i = 0; i < (int)tep->printk_count; i++) + free(tep->printk_map[i].printk); + free(tep->printk_map); + } + + while (printklist) { + printknext = printklist->next; + free(printklist->printk); + free(printklist); + printklist = printknext; + } + + for (i = 0; i < tep->nr_events; i++) + free_tep_event(tep->events[i]); + + while (tep->handlers) { + handle = tep->handlers; + tep->handlers = handle->next; + free_handler(handle); + } + + free(tep->events); + free(tep->sort_events); + free(tep->func_resolver); + free_tep_plugin_paths(tep); + + free(tep); +} + +void tep_unref(struct tep_handle *tep) +{ + tep_free(tep); +} diff --git a/tools/lib/traceevent/event-parse.h b/tools/lib/traceevent/event-parse.h new file mode 100644 index 000000000..a67ad9a5b --- /dev/null +++ b/tools/lib/traceevent/event-parse.h @@ -0,0 +1,749 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#ifndef _PARSE_EVENTS_H +#define _PARSE_EVENTS_H + +#include <stdbool.h> +#include <stdarg.h> +#include <stdio.h> +#include <regex.h> +#include <string.h> + +#include "trace-seq.h" + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((unused)) +#endif + +#ifndef DEBUG_RECORD +#define DEBUG_RECORD 0 +#endif + +struct tep_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 tep_record *prev; + struct tep_record *next; + long alloc_addr; +#endif +}; + +/* ----------------------- tep ----------------------- */ + +struct tep_handle; +struct tep_event; + +typedef int (*tep_event_handler_func)(struct trace_seq *s, + struct tep_record *record, + struct tep_event *event, + void *context); + +typedef int (*tep_plugin_load_func)(struct tep_handle *tep); +typedef int (*tep_plugin_unload_func)(struct tep_handle *tep); + +struct tep_plugin_option { + struct tep_plugin_option *next; + void *handle; + char *file; + char *name; + char *plugin_alias; + char *description; + const char *value; + void *priv; + int set; +}; + +/* + * Plugin hooks that can be called: + * + * TEP_PLUGIN_LOADER: (required) + * The function name to initialized the plugin. + * + * int TEP_PLUGIN_LOADER(struct tep_handle *tep) + * + * TEP_PLUGIN_UNLOADER: (optional) + * The function called just before unloading + * + * int TEP_PLUGIN_UNLOADER(struct tep_handle *tep) + * + * TEP_PLUGIN_OPTIONS: (optional) + * Plugin options that can be set before loading + * + * struct tep_plugin_option TEP_PLUGIN_OPTIONS[] = { + * { + * .name = "option-name", + * .plugin_alias = "override-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. + * + * If .value is not set, then it is considered a boolean and only + * .set will be processed. If .value is defined, then it is considered + * a string option and .set will be ignored. + * + * TEP_PLUGIN_ALIAS: (optional) + * The name to use for finding options (uses filename if not defined) + */ +#define TEP_PLUGIN_LOADER tep_plugin_loader +#define TEP_PLUGIN_UNLOADER tep_plugin_unloader +#define TEP_PLUGIN_OPTIONS tep_plugin_options +#define TEP_PLUGIN_ALIAS tep_plugin_alias +#define _MAKE_STR(x) #x +#define MAKE_STR(x) _MAKE_STR(x) +#define TEP_PLUGIN_LOADER_NAME MAKE_STR(TEP_PLUGIN_LOADER) +#define TEP_PLUGIN_UNLOADER_NAME MAKE_STR(TEP_PLUGIN_UNLOADER) +#define TEP_PLUGIN_OPTIONS_NAME MAKE_STR(TEP_PLUGIN_OPTIONS) +#define TEP_PLUGIN_ALIAS_NAME MAKE_STR(TEP_PLUGIN_ALIAS) + +enum tep_format_flags { + TEP_FIELD_IS_ARRAY = 1, + TEP_FIELD_IS_POINTER = 2, + TEP_FIELD_IS_SIGNED = 4, + TEP_FIELD_IS_STRING = 8, + TEP_FIELD_IS_DYNAMIC = 16, + TEP_FIELD_IS_LONG = 32, + TEP_FIELD_IS_FLAG = 64, + TEP_FIELD_IS_SYMBOLIC = 128, +}; + +struct tep_format_field { + struct tep_format_field *next; + struct tep_event *event; + char *type; + char *name; + char *alias; + int offset; + int size; + unsigned int arraylen; + unsigned int elementsize; + unsigned long flags; +}; + +struct tep_format { + int nr_common; + int nr_fields; + struct tep_format_field *common_fields; + struct tep_format_field *fields; +}; + +struct tep_print_arg_atom { + char *atom; +}; + +struct tep_print_arg_string { + char *string; + int offset; +}; + +struct tep_print_arg_bitmask { + char *bitmask; + int offset; +}; + +struct tep_print_arg_field { + char *name; + struct tep_format_field *field; +}; + +struct tep_print_flag_sym { + struct tep_print_flag_sym *next; + char *value; + char *str; +}; + +struct tep_print_arg_typecast { + char *type; + struct tep_print_arg *item; +}; + +struct tep_print_arg_flags { + struct tep_print_arg *field; + char *delim; + struct tep_print_flag_sym *flags; +}; + +struct tep_print_arg_symbol { + struct tep_print_arg *field; + struct tep_print_flag_sym *symbols; +}; + +struct tep_print_arg_hex { + struct tep_print_arg *field; + struct tep_print_arg *size; +}; + +struct tep_print_arg_int_array { + struct tep_print_arg *field; + struct tep_print_arg *count; + struct tep_print_arg *el_size; +}; + +struct tep_print_arg_dynarray { + struct tep_format_field *field; + struct tep_print_arg *index; +}; + +struct tep_print_arg; + +struct tep_print_arg_op { + char *op; + int prio; + struct tep_print_arg *left; + struct tep_print_arg *right; +}; + +struct tep_function_handler; + +struct tep_print_arg_func { + struct tep_function_handler *func; + struct tep_print_arg *args; +}; + +enum tep_print_arg_type { + TEP_PRINT_NULL, + TEP_PRINT_ATOM, + TEP_PRINT_FIELD, + TEP_PRINT_FLAGS, + TEP_PRINT_SYMBOL, + TEP_PRINT_HEX, + TEP_PRINT_INT_ARRAY, + TEP_PRINT_TYPE, + TEP_PRINT_STRING, + TEP_PRINT_BSTRING, + TEP_PRINT_DYNAMIC_ARRAY, + TEP_PRINT_OP, + TEP_PRINT_FUNC, + TEP_PRINT_BITMASK, + TEP_PRINT_DYNAMIC_ARRAY_LEN, + TEP_PRINT_HEX_STR, +}; + +struct tep_print_arg { + struct tep_print_arg *next; + enum tep_print_arg_type type; + union { + struct tep_print_arg_atom atom; + struct tep_print_arg_field field; + struct tep_print_arg_typecast typecast; + struct tep_print_arg_flags flags; + struct tep_print_arg_symbol symbol; + struct tep_print_arg_hex hex; + struct tep_print_arg_int_array int_array; + struct tep_print_arg_func func; + struct tep_print_arg_string string; + struct tep_print_arg_bitmask bitmask; + struct tep_print_arg_op op; + struct tep_print_arg_dynarray dynarray; + }; +}; + +struct tep_print_parse; + +struct tep_print_fmt { + char *format; + struct tep_print_arg *args; + struct tep_print_parse *print_cache; +}; + +struct tep_event { + struct tep_handle *tep; + char *name; + int id; + int flags; + struct tep_format format; + struct tep_print_fmt print_fmt; + char *system; + tep_event_handler_func handler; + void *context; +}; + +enum { + TEP_EVENT_FL_ISFTRACE = 0x01, + TEP_EVENT_FL_ISPRINT = 0x02, + TEP_EVENT_FL_ISBPRINT = 0x04, + TEP_EVENT_FL_ISFUNCENT = 0x10, + TEP_EVENT_FL_ISFUNCRET = 0x20, + TEP_EVENT_FL_NOHANDLE = 0x40, + TEP_EVENT_FL_PRINTRAW = 0x80, + + TEP_EVENT_FL_FAILED = 0x80000000 +}; + +enum tep_event_sort_type { + TEP_EVENT_SORT_ID, + TEP_EVENT_SORT_NAME, + TEP_EVENT_SORT_SYSTEM, +}; + +enum tep_event_type { + TEP_EVENT_ERROR, + TEP_EVENT_NONE, + TEP_EVENT_SPACE, + TEP_EVENT_NEWLINE, + TEP_EVENT_OP, + TEP_EVENT_DELIM, + TEP_EVENT_ITEM, + TEP_EVENT_DQUOTE, + TEP_EVENT_SQUOTE, +}; + +typedef unsigned long long (*tep_func_handler)(struct trace_seq *s, + unsigned long long *args); + +enum tep_func_arg_type { + TEP_FUNC_ARG_VOID, + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_LONG, + TEP_FUNC_ARG_STRING, + TEP_FUNC_ARG_PTR, + TEP_FUNC_ARG_MAX_TYPES +}; + +enum tep_flag { + TEP_NSEC_OUTPUT = 1, /* output in NSECS */ + TEP_DISABLE_SYS_PLUGINS = 1 << 1, + TEP_DISABLE_PLUGINS = 1 << 2, +}; + +#define TEP_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) TEP_ERRNO__ ## __code +enum tep_errno { + TEP_ERRNO__SUCCESS = 0, + TEP_ERRNO__FILTER_MATCH = TEP_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. + * + * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html + */ + __TEP_ERRNO__START = -100000, + + TEP_ERRORS, + + __TEP_ERRNO__END, +}; +#undef _PE + +struct tep_plugin_list; + +#define INVALID_PLUGIN_LIST_OPTION ((char **)((unsigned long)-1)) + +enum tep_plugin_load_priority { + TEP_PLUGIN_FIRST, + TEP_PLUGIN_LAST, +}; + +int tep_add_plugin_path(struct tep_handle *tep, char *path, + enum tep_plugin_load_priority prio); +struct tep_plugin_list *tep_load_plugins(struct tep_handle *tep); +void tep_unload_plugins(struct tep_plugin_list *plugin_list, + struct tep_handle *tep); +void tep_load_plugins_hook(struct tep_handle *tep, const char *suffix, + void (*load_plugin)(struct tep_handle *tep, + const char *path, + const char *name, + void *data), + void *data); +char **tep_plugin_list_options(void); +void tep_plugin_free_options_list(char **list); +int tep_plugin_add_options(const char *name, + struct tep_plugin_option *options); +int tep_plugin_add_option(const char *name, const char *val); +void tep_plugin_remove_options(struct tep_plugin_option *options); +void tep_plugin_print_options(struct trace_seq *s); +void tep_print_plugins(struct trace_seq *s, + const char *prefix, const char *suffix, + const struct tep_plugin_list *list); + +/* tep_handle */ +typedef char *(tep_func_resolver_t)(void *priv, + unsigned long long *addrp, char **modp); +void tep_set_flag(struct tep_handle *tep, int flag); +void tep_clear_flag(struct tep_handle *tep, enum tep_flag flag); +bool tep_test_flag(struct tep_handle *tep, enum tep_flag flags); + +static inline int tep_is_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 tep_set_function_resolver(struct tep_handle *tep, + tep_func_resolver_t *func, void *priv); +void tep_reset_function_resolver(struct tep_handle *tep); +int tep_register_comm(struct tep_handle *tep, const char *comm, int pid); +int tep_override_comm(struct tep_handle *tep, const char *comm, int pid); +int tep_register_function(struct tep_handle *tep, char *name, + unsigned long long addr, char *mod); +int tep_register_print_string(struct tep_handle *tep, const char *fmt, + unsigned long long addr); +bool tep_is_pid_registered(struct tep_handle *tep, int pid); + +struct tep_event *tep_get_event(struct tep_handle *tep, int index); + +#define TEP_PRINT_INFO "INFO" +#define TEP_PRINT_INFO_RAW "INFO_RAW" +#define TEP_PRINT_COMM "COMM" +#define TEP_PRINT_LATENCY "LATENCY" +#define TEP_PRINT_NAME "NAME" +#define TEP_PRINT_PID 1U +#define TEP_PRINT_TIME 2U +#define TEP_PRINT_CPU 3U + +void tep_print_event(struct tep_handle *tep, struct trace_seq *s, + struct tep_record *record, const char *fmt, ...) + __attribute__ ((format (printf, 4, 5))); + +int tep_parse_header_page(struct tep_handle *tep, char *buf, unsigned long size, + int long_size); + +enum tep_errno tep_parse_event(struct tep_handle *tep, const char *buf, + unsigned long size, const char *sys); +enum tep_errno tep_parse_format(struct tep_handle *tep, + struct tep_event **eventp, + const char *buf, + unsigned long size, const char *sys); + +void *tep_get_field_raw(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + int *len, int err); + +int tep_get_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err); +int tep_get_common_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err); +int tep_get_any_field_val(struct trace_seq *s, struct tep_event *event, + const char *name, struct tep_record *record, + unsigned long long *val, int err); + +int tep_print_num_field(struct trace_seq *s, const char *fmt, + struct tep_event *event, const char *name, + struct tep_record *record, int err); + +int tep_print_func_field(struct trace_seq *s, const char *fmt, + struct tep_event *event, const char *name, + struct tep_record *record, int err); + +enum tep_reg_handler { + TEP_REGISTER_SUCCESS = 0, + TEP_REGISTER_SUCCESS_OVERWRITE, +}; + +int tep_register_event_handler(struct tep_handle *tep, int id, + const char *sys_name, const char *event_name, + tep_event_handler_func func, void *context); +int tep_unregister_event_handler(struct tep_handle *tep, int id, + const char *sys_name, const char *event_name, + tep_event_handler_func func, void *context); +int tep_register_print_function(struct tep_handle *tep, + tep_func_handler func, + enum tep_func_arg_type ret_type, + char *name, ...); +int tep_unregister_print_function(struct tep_handle *tep, + tep_func_handler func, char *name); + +struct tep_format_field *tep_find_common_field(struct tep_event *event, const char *name); +struct tep_format_field *tep_find_field(struct tep_event *event, const char *name); +struct tep_format_field *tep_find_any_field(struct tep_event *event, const char *name); + +const char *tep_find_function(struct tep_handle *tep, unsigned long long addr); +unsigned long long +tep_find_function_address(struct tep_handle *tep, unsigned long long addr); +unsigned long long tep_read_number(struct tep_handle *tep, const void *ptr, int size); +int tep_read_number_field(struct tep_format_field *field, const void *data, + unsigned long long *value); + +struct tep_event *tep_get_first_event(struct tep_handle *tep); +int tep_get_events_count(struct tep_handle *tep); +struct tep_event *tep_find_event(struct tep_handle *tep, int id); + +struct tep_event * +tep_find_event_by_name(struct tep_handle *tep, const char *sys, const char *name); +struct tep_event * +tep_find_event_by_record(struct tep_handle *tep, struct tep_record *record); + +int tep_data_type(struct tep_handle *tep, struct tep_record *rec); +int tep_data_pid(struct tep_handle *tep, struct tep_record *rec); +int tep_data_preempt_count(struct tep_handle *tep, struct tep_record *rec); +int tep_data_flags(struct tep_handle *tep, struct tep_record *rec); +const char *tep_data_comm_from_pid(struct tep_handle *tep, int pid); +struct tep_cmdline; +struct tep_cmdline *tep_data_pid_from_comm(struct tep_handle *tep, const char *comm, + struct tep_cmdline *next); +int tep_cmdline_pid(struct tep_handle *tep, struct tep_cmdline *cmdline); + +void tep_print_field(struct trace_seq *s, void *data, + struct tep_format_field *field); +void tep_print_fields(struct trace_seq *s, void *data, + int size __maybe_unused, struct tep_event *event); +int tep_strerror(struct tep_handle *tep, enum tep_errno errnum, + char *buf, size_t buflen); + +struct tep_event **tep_list_events(struct tep_handle *tep, enum tep_event_sort_type); +struct tep_event **tep_list_events_copy(struct tep_handle *tep, + enum tep_event_sort_type); +struct tep_format_field **tep_event_common_fields(struct tep_event *event); +struct tep_format_field **tep_event_fields(struct tep_event *event); + +enum tep_endian { + TEP_LITTLE_ENDIAN = 0, + TEP_BIG_ENDIAN +}; +int tep_get_cpus(struct tep_handle *tep); +void tep_set_cpus(struct tep_handle *tep, int cpus); +int tep_get_long_size(struct tep_handle *tep); +void tep_set_long_size(struct tep_handle *tep, int long_size); +int tep_get_page_size(struct tep_handle *tep); +void tep_set_page_size(struct tep_handle *tep, int _page_size); +bool tep_is_file_bigendian(struct tep_handle *tep); +void tep_set_file_bigendian(struct tep_handle *tep, enum tep_endian endian); +bool tep_is_local_bigendian(struct tep_handle *tep); +void tep_set_local_bigendian(struct tep_handle *tep, enum tep_endian endian); +int tep_get_header_page_size(struct tep_handle *tep); +int tep_get_header_timestamp_size(struct tep_handle *tep); +bool tep_is_old_format(struct tep_handle *tep); +void tep_set_test_filters(struct tep_handle *tep, int test_filters); + +struct tep_handle *tep_alloc(void); +void tep_free(struct tep_handle *tep); +void tep_ref(struct tep_handle *tep); +void tep_unref(struct tep_handle *tep); +int tep_get_ref(struct tep_handle *tep); + +/* for debugging */ +void tep_print_funcs(struct tep_handle *tep); +void tep_print_printk(struct tep_handle *tep); + +/* ----------------------- filtering ----------------------- */ + +enum tep_filter_boolean_type { + TEP_FILTER_FALSE, + TEP_FILTER_TRUE, +}; + +enum tep_filter_op_type { + TEP_FILTER_OP_AND = 1, + TEP_FILTER_OP_OR, + TEP_FILTER_OP_NOT, +}; + +enum tep_filter_cmp_type { + TEP_FILTER_CMP_NONE, + TEP_FILTER_CMP_EQ, + TEP_FILTER_CMP_NE, + TEP_FILTER_CMP_GT, + TEP_FILTER_CMP_LT, + TEP_FILTER_CMP_GE, + TEP_FILTER_CMP_LE, + TEP_FILTER_CMP_MATCH, + TEP_FILTER_CMP_NOT_MATCH, + TEP_FILTER_CMP_REGEX, + TEP_FILTER_CMP_NOT_REGEX, +}; + +enum tep_filter_exp_type { + TEP_FILTER_EXP_NONE, + TEP_FILTER_EXP_ADD, + TEP_FILTER_EXP_SUB, + TEP_FILTER_EXP_MUL, + TEP_FILTER_EXP_DIV, + TEP_FILTER_EXP_MOD, + TEP_FILTER_EXP_RSHIFT, + TEP_FILTER_EXP_LSHIFT, + TEP_FILTER_EXP_AND, + TEP_FILTER_EXP_OR, + TEP_FILTER_EXP_XOR, + TEP_FILTER_EXP_NOT, +}; + +enum tep_filter_arg_type { + TEP_FILTER_ARG_NONE, + TEP_FILTER_ARG_BOOLEAN, + TEP_FILTER_ARG_VALUE, + TEP_FILTER_ARG_FIELD, + TEP_FILTER_ARG_EXP, + TEP_FILTER_ARG_OP, + TEP_FILTER_ARG_NUM, + TEP_FILTER_ARG_STR, +}; + +enum tep_filter_value_type { + TEP_FILTER_NUMBER, + TEP_FILTER_STRING, + TEP_FILTER_CHAR +}; + +struct tep_filter_arg; + +struct tep_filter_arg_boolean { + enum tep_filter_boolean_type value; +}; + +struct tep_filter_arg_field { + struct tep_format_field *field; +}; + +struct tep_filter_arg_value { + enum tep_filter_value_type type; + union { + char *str; + unsigned long long val; + }; +}; + +struct tep_filter_arg_op { + enum tep_filter_op_type type; + struct tep_filter_arg *left; + struct tep_filter_arg *right; +}; + +struct tep_filter_arg_exp { + enum tep_filter_exp_type type; + struct tep_filter_arg *left; + struct tep_filter_arg *right; +}; + +struct tep_filter_arg_num { + enum tep_filter_cmp_type type; + struct tep_filter_arg *left; + struct tep_filter_arg *right; +}; + +struct tep_filter_arg_str { + enum tep_filter_cmp_type type; + struct tep_format_field *field; + char *val; + char *buffer; + regex_t reg; +}; + +struct tep_filter_arg { + enum tep_filter_arg_type type; + union { + struct tep_filter_arg_boolean boolean; + struct tep_filter_arg_field field; + struct tep_filter_arg_value value; + struct tep_filter_arg_op op; + struct tep_filter_arg_exp exp; + struct tep_filter_arg_num num; + struct tep_filter_arg_str str; + }; +}; + +struct tep_filter_type { + int event_id; + struct tep_event *event; + struct tep_filter_arg *filter; +}; + +#define TEP_FILTER_ERROR_BUFSZ 1024 + +struct tep_event_filter { + struct tep_handle *tep; + int filters; + struct tep_filter_type *event_filters; + char error_buffer[TEP_FILTER_ERROR_BUFSZ]; +}; + +struct tep_event_filter *tep_filter_alloc(struct tep_handle *tep); + +/* for backward compatibility */ +#define FILTER_NONE TEP_ERRNO__NO_FILTER +#define FILTER_NOEXIST TEP_ERRNO__FILTER_NOT_FOUND +#define FILTER_MISS TEP_ERRNO__FILTER_MISS +#define FILTER_MATCH TEP_ERRNO__FILTER_MATCH + +enum tep_errno tep_filter_add_filter_str(struct tep_event_filter *filter, + const char *filter_str); + +enum tep_errno tep_filter_match(struct tep_event_filter *filter, + struct tep_record *record); + +int tep_filter_strerror(struct tep_event_filter *filter, enum tep_errno err, + char *buf, size_t buflen); + +int tep_event_filtered(struct tep_event_filter *filter, + int event_id); + +void tep_filter_reset(struct tep_event_filter *filter); + +void tep_filter_free(struct tep_event_filter *filter); + +char *tep_filter_make_string(struct tep_event_filter *filter, int event_id); + +int tep_filter_remove_event(struct tep_event_filter *filter, + int event_id); + +int tep_filter_copy(struct tep_event_filter *dest, struct tep_event_filter *source); + +int tep_filter_compare(struct tep_event_filter *filter1, struct tep_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 000000000..e7f93d5fe --- /dev/null +++ b/tools/lib/traceevent/event-plugin.c @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ + +#include <ctype.h> +#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 <errno.h> +#include "event-parse.h" +#include "event-parse-local.h" +#include "event-utils.h" +#include "trace-seq.h" + +#define LOCAL_PLUGIN_DIR ".local/lib/traceevent/plugins/" + +static struct registered_plugin_options { + struct registered_plugin_options *next; + struct tep_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 tep_plugin_list { + struct tep_plugin_list *next; + char *name; + void *handle; +}; + +struct tep_plugins_dir { + struct tep_plugins_dir *next; + char *path; + enum tep_plugin_load_priority prio; +}; + +static void lower_case(char *str) +{ + if (!str) + return; + for (; *str; str++) + *str = tolower(*str); +} + +static int update_option_value(struct tep_plugin_option *op, const char *val) +{ + char *op_val; + + if (!val) { + /* toggle, only if option is boolean */ + if (op->value) + /* Warn? */ + return 0; + op->set ^= 1; + return 0; + } + + /* + * If the option has a value then it takes a string + * otherwise the option is a boolean. + */ + if (op->value) { + op->value = val; + return 0; + } + + /* Option is boolean, must be either "1", "0", "true" or "false" */ + + op_val = strdup(val); + if (!op_val) + return -1; + lower_case(op_val); + + if (strcmp(val, "1") == 0 || strcmp(val, "true") == 0) + op->set = 1; + else if (strcmp(val, "0") == 0 || strcmp(val, "false") == 0) + op->set = 0; + free(op_val); + + return 0; +} + +/** + * tep_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 tep_plugin_free_options_list(). + */ +char **tep_plugin_list_options(void) +{ + struct registered_plugin_options *reg; + struct tep_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; + int ret; + + ret = asprintf(&name, "%s:%s", alias, op->name); + if (ret < 0) + goto err; + + 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 tep_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 tep_plugin_option *option) +{ + struct trace_plugin_options *op; + char *plugin; + int ret = 0; + + 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; + + ret = update_option_value(option, op->value); + if (ret) + goto out; + break; + } + + /* 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; + + ret = update_option_value(option, op->value); + break; + } + + out: + free(plugin); + return ret; +} + +/** + * tep_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 tep_plugin_add_options(const char *name, + struct tep_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; +} + +/** + * tep_plugin_remove_options - remove plugin options that were registered + * @options: Options to removed that were registered with tep_plugin_add_options + */ +void tep_plugin_remove_options(struct tep_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; + } + } +} + +static int parse_option_name(char **option, char **plugin) +{ + char *p; + + *plugin = NULL; + + if ((p = strstr(*option, ":"))) { + *plugin = *option; + *p = '\0'; + *option = strdup(p + 1); + if (!*option) + return -1; + } + return 0; +} + +static struct tep_plugin_option * +find_registered_option(const char *plugin, const char *option) +{ + struct registered_plugin_options *reg; + struct tep_plugin_option *op; + const char *op_plugin; + + for (reg = registered_options; reg; reg = reg->next) { + for (op = reg->options; op->name; op++) { + if (op->plugin_alias) + op_plugin = op->plugin_alias; + else + op_plugin = op->file; + + if (plugin && strcmp(plugin, op_plugin) != 0) + continue; + if (strcmp(option, op->name) != 0) + continue; + + return op; + } + } + + return NULL; +} + +static int process_option(const char *plugin, const char *option, const char *val) +{ + struct tep_plugin_option *op; + + op = find_registered_option(plugin, option); + if (!op) + return 0; + + return update_option_value(op, val); +} + +/** + * tep_plugin_add_option - add an option/val pair to set plugin options + * @name: The name of the option (format: <plugin>:<option> or just <option>) + * @val: (optional) the value for the option + * + * Modify a plugin option. If @val is given than the value of the option + * is set (note, some options just take a boolean, so @val must be either + * "1" or "0" or "true" or "false"). + */ +int tep_plugin_add_option(const char *name, const char *val) +{ + struct trace_plugin_options *op; + char *option_str; + char *plugin; + + option_str = strdup(name); + if (!option_str) + return -ENOMEM; + + if (parse_option_name(&option_str, &plugin) < 0) + return -ENOMEM; + + /* If the option exists, update the val */ + for (op = trace_plugin_options; op; op = op->next) { + /* Both must be NULL or not NULL */ + if ((!plugin || !op->plugin) && plugin != op->plugin) + continue; + if (plugin && strcmp(plugin, op->plugin) != 0) + continue; + if (strcmp(op->option, option_str) != 0) + continue; + + /* update option */ + free(op->value); + if (val) { + op->value = strdup(val); + if (!op->value) + goto out_free; + } else + op->value = NULL; + + /* plugin and option_str don't get freed at the end */ + free(plugin); + free(option_str); + + plugin = op->plugin; + option_str = op->option; + break; + } + + /* If not found, create */ + if (!op) { + op = malloc(sizeof(*op)); + if (!op) + goto out_free; + memset(op, 0, sizeof(*op)); + op->plugin = plugin; + op->option = option_str; + if (val) { + op->value = strdup(val); + if (!op->value) { + free(op); + goto out_free; + } + } + op->next = trace_plugin_options; + trace_plugin_options = op; + } + + return process_option(plugin, option_str, val); + +out_free: + free(plugin); + free(option_str); + return -ENOMEM; +} + +static void print_op_data(struct trace_seq *s, const char *name, + const char *op) +{ + if (op) + trace_seq_printf(s, "%8s:\t%s\n", name, op); +} + +/** + * tep_plugin_print_options - print out the registered plugin options + * @s: The trace_seq descriptor to write the plugin options into + * + * Writes a list of options into trace_seq @s. + */ +void tep_plugin_print_options(struct trace_seq *s) +{ + struct registered_plugin_options *reg; + struct tep_plugin_option *op; + + for (reg = registered_options; reg; reg = reg->next) { + if (reg != registered_options) + trace_seq_printf(s, "============\n"); + for (op = reg->options; op->name; op++) { + if (op != reg->options) + trace_seq_printf(s, "------------\n"); + print_op_data(s, "file", op->file); + print_op_data(s, "plugin", op->plugin_alias); + print_op_data(s, "option", op->name); + print_op_data(s, "desc", op->description); + print_op_data(s, "value", op->value); + trace_seq_printf(s, "%8s:\t%d\n", "set", op->set); + } + } +} + +/** + * tep_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 tep_load_plugins() + * + * Writes to the trace_seq @s the list of plugins (files) that is + * returned by tep_load_plugins(). Use @prefix and @suffix for formating: + * @prefix = " ", @suffix = "\n". + */ +void tep_print_plugins(struct trace_seq *s, + const char *prefix, const char *suffix, + const struct tep_plugin_list *list) +{ + while (list) { + trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix); + list = list->next; + } +} + +static void +load_plugin(struct tep_handle *tep, const char *path, + const char *file, void *data) +{ + struct tep_plugin_list **plugin_list = data; + struct tep_plugin_option *options; + tep_plugin_load_func func; + struct tep_plugin_list *list; + const char *alias; + char *plugin; + void *handle; + int ret; + + ret = asprintf(&plugin, "%s/%s", path, file); + if (ret < 0) { + warning("could not allocate plugin memory\n"); + return; + } + + 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, TEP_PLUGIN_ALIAS_NAME); + if (!alias) + alias = file; + + options = dlsym(handle, TEP_PLUGIN_OPTIONS_NAME); + if (options) { + while (options->name) { + ret = update_option(alias, options); + if (ret < 0) + goto out_free; + options++; + } + } + + func = dlsym(handle, TEP_PLUGIN_LOADER_NAME); + if (!func) { + warning("could not find func '%s' in plugin '%s'\n%s\n", + TEP_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(tep); + return; + + out_free: + free(plugin); +} + +static void +load_plugins_dir(struct tep_handle *tep, const char *suffix, + const char *path, + void (*load_plugin)(struct tep_handle *tep, + 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(tep, path, name, data); + } + + closedir(dir); +} + +/** + * tep_load_plugins_hook - call a user specified callback to load a plugin + * @tep: handler to traceevent context + * @suffix: filter only plugin files with given suffix + * @load_plugin: user specified callback, called for each plugin file + * @data: custom context, passed to @load_plugin + * + * Searches for traceevent plugin files and calls @load_plugin for each + * The order of plugins search is: + * - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_FIRST + * - Directory, specified at compile time with PLUGIN_TRACEEVENT_DIR + * - Directory, specified by environment variable TRACEEVENT_PLUGIN_DIR + * - In user's home: ~/.local/lib/traceevent/plugins/ + * - Directories, specified in @tep->plugins_dir and priority TEP_PLUGIN_LAST + * + */ +void tep_load_plugins_hook(struct tep_handle *tep, const char *suffix, + void (*load_plugin)(struct tep_handle *tep, + const char *path, + const char *name, + void *data), + void *data) +{ + struct tep_plugins_dir *dir = NULL; + char *home; + char *path; + char *envdir; + int ret; + + if (tep && tep->flags & TEP_DISABLE_PLUGINS) + return; + + if (tep) + dir = tep->plugins_dir; + while (dir) { + if (dir->prio == TEP_PLUGIN_FIRST) + load_plugins_dir(tep, suffix, dir->path, + load_plugin, data); + dir = dir->next; + } + + /* + * If a system plugin directory was defined, + * check that first. + */ +#ifdef PLUGIN_DIR + if (!tep || !(tep->flags & TEP_DISABLE_SYS_PLUGINS)) + load_plugins_dir(tep, 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(tep, suffix, envdir, load_plugin, data); + + /* + * Now let the home directory override the environment + * or system defaults. + */ + home = getenv("HOME"); + if (!home) + return; + + ret = asprintf(&path, "%s/%s", home, LOCAL_PLUGIN_DIR); + if (ret < 0) { + warning("could not allocate plugin memory\n"); + return; + } + + load_plugins_dir(tep, suffix, path, load_plugin, data); + + if (tep) + dir = tep->plugins_dir; + while (dir) { + if (dir->prio == TEP_PLUGIN_LAST) + load_plugins_dir(tep, suffix, dir->path, + load_plugin, data); + dir = dir->next; + } + + free(path); +} + +struct tep_plugin_list* +tep_load_plugins(struct tep_handle *tep) +{ + struct tep_plugin_list *list = NULL; + + tep_load_plugins_hook(tep, ".so", load_plugin, &list); + return list; +} + +/** + * tep_add_plugin_path - Add a new plugin directory. + * @tep: Trace event handler. + * @path: Path to a directory. All plugin files in that + * directory will be loaded. + *@prio: Load priority of the plugins in that directory. + * + * Returns -1 in case of an error, 0 otherwise. + */ +int tep_add_plugin_path(struct tep_handle *tep, char *path, + enum tep_plugin_load_priority prio) +{ + struct tep_plugins_dir *dir; + + if (!tep || !path) + return -1; + + dir = calloc(1, sizeof(*dir)); + if (!dir) + return -1; + + dir->path = strdup(path); + if (!dir->path) { + free(dir); + return -1; + } + dir->prio = prio; + dir->next = tep->plugins_dir; + tep->plugins_dir = dir; + + return 0; +} + +__hidden void free_tep_plugin_paths(struct tep_handle *tep) +{ + struct tep_plugins_dir *dir; + + if (!tep) + return; + + dir = tep->plugins_dir; + while (dir) { + tep->plugins_dir = tep->plugins_dir->next; + free(dir->path); + free(dir); + dir = tep->plugins_dir; + } +} + +void +tep_unload_plugins(struct tep_plugin_list *plugin_list, struct tep_handle *tep) +{ + tep_plugin_unload_func func; + struct tep_plugin_list *list; + + while (plugin_list) { + list = plugin_list; + plugin_list = list->next; + func = dlsym(list->handle, TEP_PLUGIN_UNLOADER_NAME); + if (func) + func(tep); + 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 000000000..0560b96a3 --- /dev/null +++ b/tools/lib/traceevent/event-utils.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#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 000000000..f1640d651 --- /dev/null +++ b/tools/lib/traceevent/kbuffer-parse.c @@ -0,0 +1,809 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "kbuffer.h" + +#define MISSING_EVENTS (1UL << 31) +#define MISSING_STORED (1UL << 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; + length = 0; + 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); + break; + + case KBUFFER_TYPE_TIME_EXTEND: + case KBUFFER_TYPE_TIME_STAMP: + extend = read_4(kbuf, data); + data += 4; + extend <<= TS_SHIFT; + extend += *delta; + *delta = extend; + *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); + + if (type_len == KBUFFER_TYPE_TIME_STAMP) + kbuf->timestamp = delta; + else + 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_TIME_STAMP || + 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_subbuf_timestamp - read the timestamp from a sub buffer + * @kbuf: The kbuffer to load + * @subbuf: The subbuffer to read from. + * + * Return the timestamp from a subbuffer. + */ +unsigned long long kbuffer_subbuf_timestamp(struct kbuffer *kbuf, void *subbuf) +{ + return kbuf->read_8(subbuf); +} + +/** + * kbuffer_ptr_delta - read the delta field from a record + * @kbuf: The kbuffer to load + * @ptr: The record in the buffe. + * + * Return the timestamp delta from a record + */ +unsigned int kbuffer_ptr_delta(struct kbuffer *kbuf, void *ptr) +{ + unsigned int type_len_ts; + + type_len_ts = read_4(kbuf, ptr); + return ts4host(kbuf, type_len_ts); +} + + +/** + * 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); + data = kbuffer_read_event(kbuf, ts); + + 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; +} + +/** + * kbuffer_start_of_data - return offset of where data starts on subbuffer + * @kbuf: The kbuffer + * + * Returns the location on the subbuffer where the data starts. + */ +int kbuffer_start_of_data(struct kbuffer *kbuf) +{ + return kbuf->start; +} + +/** + * kbuffer_raw_get - get raw buffer info + * @kbuf: The kbuffer + * @subbuf: Start of mapped subbuffer + * @info: Info descriptor to fill in + * + * For debugging. This can return internals of the ring buffer. + * Expects to have info->next set to what it will read. + * The type, length and timestamp delta will be filled in, and + * @info->next will be updated to the next element. + * The @subbuf is used to know if the info is passed the end of + * data and NULL will be returned if it is. + */ +struct kbuffer_raw_info * +kbuffer_raw_get(struct kbuffer *kbuf, void *subbuf, struct kbuffer_raw_info *info) +{ + unsigned long long flags; + unsigned long long delta; + unsigned int type_len; + unsigned int size; + int start; + int length; + void *ptr = info->next; + + if (!kbuf || !subbuf) + return NULL; + + if (kbuf->flags & KBUFFER_FL_LONG_8) + start = 16; + else + start = 12; + + flags = read_long(kbuf, subbuf + 8); + size = (unsigned int)flags & COMMIT_MASK; + + if (ptr < subbuf || ptr >= subbuf + start + size) + return NULL; + + type_len = translate_data(kbuf, ptr, &ptr, &delta, &length); + + info->next = ptr + length; + + info->type = type_len; + info->delta = delta; + info->length = length; + + return info; +} diff --git a/tools/lib/traceevent/kbuffer.h b/tools/lib/traceevent/kbuffer.h new file mode 100644 index 000000000..a2b522093 --- /dev/null +++ b/tools/lib/traceevent/kbuffer.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright (C) 2012 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#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); +unsigned long long kbuffer_subbuf_timestamp(struct kbuffer *kbuf, void *subbuf); +unsigned int kbuffer_ptr_delta(struct kbuffer *kbuf, void *ptr); + +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); +int kbuffer_start_of_data(struct kbuffer *kbuf); + +/* Debugging */ + +struct kbuffer_raw_info { + int type; + int length; + unsigned long long delta; + void *next; +}; + +/* Read raw data */ +struct kbuffer_raw_info *kbuffer_raw_get(struct kbuffer *kbuf, void *subbuf, + struct kbuffer_raw_info *info); + +#endif /* _K_BUFFER_H */ diff --git a/tools/lib/traceevent/libtraceevent.pc.template b/tools/lib/traceevent/libtraceevent.pc.template new file mode 100644 index 000000000..86384fcd5 --- /dev/null +++ b/tools/lib/traceevent/libtraceevent.pc.template @@ -0,0 +1,10 @@ +prefix=INSTALL_PREFIX +libdir=LIB_DIR +includedir=HEADER_DIR + +Name: libtraceevent +URL: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git +Description: Linux kernel trace event library +Version: LIB_VERSION +Cflags: -I${includedir} +Libs: -L${libdir} -ltraceevent diff --git a/tools/lib/traceevent/parse-filter.c b/tools/lib/traceevent/parse-filter.c new file mode 100644 index 000000000..368826bb5 --- /dev/null +++ b/tools/lib/traceevent/parse-filter.c @@ -0,0 +1,2278 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#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-parse-local.h" +#include "event-utils.h" + +#define COMM "COMM" +#define CPU "CPU" + +static struct tep_format_field comm = { + .name = "COMM", +}; + +static struct tep_format_field cpu = { + .name = "CPU", +}; + +struct event_list { + struct event_list *next; + struct tep_event *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 = get_input_buf(); + index = 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, TEP_FILTER_ERROR_BUFSZ - len, fmt, ap); + va_end(ap); +} + +static enum tep_event_type filter_read_token(char **tok) +{ + enum tep_event_type type; + char *token = NULL; + + do { + free_token(token); + type = read_token(&token); + } while (type == TEP_EVENT_NEWLINE || type == TEP_EVENT_SPACE); + + /* If token is = or ! check to see if the next char is ~ */ + if (token && + (strcmp(token, "=") == 0 || strcmp(token, "!") == 0) && + peek_char() == '~') { + /* append it */ + *tok = malloc(3); + if (*tok == NULL) { + free_token(token); + return TEP_EVENT_ERROR; + } + sprintf(*tok, "%c%c", *token, '~'); + free_token(token); + /* Now remove the '~' from the buffer */ + read_token(&token); + free_token(token); + } else + *tok = token; + + return type; +} + +static int filter_cmp(const void *a, const void *b) +{ + const struct tep_filter_type *ea = a; + const struct tep_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 tep_filter_type * +find_filter_type(struct tep_event_filter *filter, int id) +{ + struct tep_filter_type *filter_type; + struct tep_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 tep_filter_type * +add_filter_type(struct tep_event_filter *filter, int id) +{ + struct tep_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 = tep_find_event(filter->tep, id); + filter_type->filter = NULL; + + filter->filters++; + + return filter_type; +} + +/** + * tep_filter_alloc - create a new event filter + * @tep: The tep that this filter is associated with + */ +struct tep_event_filter *tep_filter_alloc(struct tep_handle *tep) +{ + struct tep_event_filter *filter; + + filter = malloc(sizeof(*filter)); + if (filter == NULL) + return NULL; + + memset(filter, 0, sizeof(*filter)); + filter->tep = tep; + tep_ref(tep); + + return filter; +} + +static struct tep_filter_arg *allocate_arg(void) +{ + return calloc(1, sizeof(struct tep_filter_arg)); +} + +static void free_arg(struct tep_filter_arg *arg) +{ + if (!arg) + return; + + switch (arg->type) { + case TEP_FILTER_ARG_NONE: + case TEP_FILTER_ARG_BOOLEAN: + break; + + case TEP_FILTER_ARG_NUM: + free_arg(arg->num.left); + free_arg(arg->num.right); + break; + + case TEP_FILTER_ARG_EXP: + free_arg(arg->exp.left); + free_arg(arg->exp.right); + break; + + case TEP_FILTER_ARG_STR: + free(arg->str.val); + regfree(&arg->str.reg); + free(arg->str.buffer); + break; + + case TEP_FILTER_ARG_VALUE: + if (arg->value.type == TEP_FILTER_STRING || + arg->value.type == TEP_FILTER_CHAR) + free(arg->value.str); + break; + + case TEP_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 tep_event *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 tep_event *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 tep_errno +find_event(struct tep_handle *tep, struct event_list **events, + char *sys_name, char *event_name) +{ + struct tep_event *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; + } + + ret = asprintf(®, "^%s$", event_name); + if (ret < 0) + return TEP_ERRNO__MEM_ALLOC_FAILED; + + ret = regcomp(&ereg, reg, REG_ICASE|REG_NOSUB); + free(reg); + + if (ret) + return TEP_ERRNO__INVALID_EVENT_NAME; + + if (sys_name) { + ret = asprintf(®, "^%s$", sys_name); + if (ret < 0) { + regfree(&ereg); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + ret = regcomp(&sreg, reg, REG_ICASE|REG_NOSUB); + free(reg); + if (ret) { + regfree(&ereg); + return TEP_ERRNO__INVALID_EVENT_NAME; + } + } + + for (i = 0; i < tep->nr_events; i++) { + event = tep->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 TEP_ERRNO__EVENT_NOT_FOUND; + if (fail) + return TEP_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 tep_errno +create_arg_item(struct tep_event *event, const char *token, + enum tep_event_type type, struct tep_filter_arg **parg, char *error_str) +{ + struct tep_format_field *field; + struct tep_filter_arg *arg; + + arg = allocate_arg(); + if (arg == NULL) { + show_error(error_str, "failed to allocate filter arg"); + return TEP_ERRNO__MEM_ALLOC_FAILED; + } + + switch (type) { + + case TEP_EVENT_SQUOTE: + case TEP_EVENT_DQUOTE: + arg->type = TEP_FILTER_ARG_VALUE; + arg->value.type = + type == TEP_EVENT_DQUOTE ? TEP_FILTER_STRING : TEP_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 TEP_ERRNO__MEM_ALLOC_FAILED; + } + break; + case TEP_EVENT_ITEM: + /* if it is a number, then convert it */ + if (isdigit(token[0])) { + arg->type = TEP_FILTER_ARG_VALUE; + arg->value.type = TEP_FILTER_NUMBER; + arg->value.val = strtoull(token, NULL, 0); + break; + } + /* Consider this a field */ + field = tep_find_any_field(event, token); + if (!field) { + /* If token is 'COMM' or 'CPU' then it is special */ + if (strcmp(token, COMM) == 0) { + field = &comm; + } else if (strcmp(token, CPU) == 0) { + field = &cpu; + } else { + /* not a field, Make it false */ + arg->type = TEP_FILTER_ARG_BOOLEAN; + arg->boolean.value = TEP_FILTER_FALSE; + break; + } + } + arg->type = TEP_FILTER_ARG_FIELD; + arg->field.field = field; + break; + default: + free_arg(arg); + show_error(error_str, "expected a value but found %s", token); + return TEP_ERRNO__UNEXPECTED_TYPE; + } + *parg = arg; + return 0; +} + +static struct tep_filter_arg * +create_arg_op(enum tep_filter_op_type btype) +{ + struct tep_filter_arg *arg; + + arg = allocate_arg(); + if (!arg) + return NULL; + + arg->type = TEP_FILTER_ARG_OP; + arg->op.type = btype; + + return arg; +} + +static struct tep_filter_arg * +create_arg_exp(enum tep_filter_exp_type etype) +{ + struct tep_filter_arg *arg; + + arg = allocate_arg(); + if (!arg) + return NULL; + + arg->type = TEP_FILTER_ARG_EXP; + arg->exp.type = etype; + + return arg; +} + +static struct tep_filter_arg * +create_arg_cmp(enum tep_filter_cmp_type ctype) +{ + struct tep_filter_arg *arg; + + arg = allocate_arg(); + if (!arg) + return NULL; + + /* Use NUM and change if necessary */ + arg->type = TEP_FILTER_ARG_NUM; + arg->num.type = ctype; + + return arg; +} + +static enum tep_errno +add_right(struct tep_filter_arg *op, struct tep_filter_arg *arg, char *error_str) +{ + struct tep_filter_arg *left; + char *str; + int op_type; + int ret; + + switch (op->type) { + case TEP_FILTER_ARG_EXP: + if (op->exp.right) + goto out_fail; + op->exp.right = arg; + break; + + case TEP_FILTER_ARG_OP: + if (op->op.right) + goto out_fail; + op->op.right = arg; + break; + + case TEP_FILTER_ARG_NUM: + if (op->op.right) + goto out_fail; + /* + * The arg must be num, str, or field + */ + switch (arg->type) { + case TEP_FILTER_ARG_VALUE: + case TEP_FILTER_ARG_FIELD: + break; + default: + show_error(error_str, "Illegal rvalue"); + return TEP_ERRNO__ILLEGAL_RVALUE; + } + + /* + * Depending on the type, we may need to + * convert this to a string or regex. + */ + switch (arg->value.type) { + case TEP_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 != TEP_FILTER_CMP_REGEX && + op->num.type != TEP_FILTER_CMP_NOT_REGEX) { + arg->value.type = TEP_FILTER_NUMBER; + goto do_int; + } + /* fall through */ + case TEP_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 == TEP_FILTER_ARG_BOOLEAN) { + free_arg(left); + free_arg(arg); + op->type = TEP_FILTER_ARG_BOOLEAN; + op->boolean.value = TEP_FILTER_FALSE; + break; + } + + /* Left arg must be a field */ + if (left->type != TEP_FILTER_ARG_FIELD) { + show_error(error_str, + "Illegal lvalue for string comparison"); + return TEP_ERRNO__ILLEGAL_LVALUE; + } + + /* Make sure this is a valid string compare */ + switch (op_type) { + case TEP_FILTER_CMP_EQ: + op_type = TEP_FILTER_CMP_MATCH; + break; + case TEP_FILTER_CMP_NE: + op_type = TEP_FILTER_CMP_NOT_MATCH; + break; + + case TEP_FILTER_CMP_REGEX: + case TEP_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 TEP_ERRNO__INVALID_REGEX; + } + break; + default: + show_error(error_str, + "Illegal comparison for string"); + return TEP_ERRNO__ILLEGAL_STRING_CMP; + } + + op->type = TEP_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 TEP_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 TEP_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 TEP_FILTER_NUMBER: + + do_int: + switch (op->num.type) { + case TEP_FILTER_CMP_REGEX: + case TEP_FILTER_CMP_NOT_REGEX: + show_error(error_str, + "Op not allowed with integers"); + return TEP_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 TEP_ERRNO__SYNTAX_ERROR; +} + +static struct tep_filter_arg * +rotate_op_right(struct tep_filter_arg *a, struct tep_filter_arg *b) +{ + struct tep_filter_arg *arg; + + arg = a->op.right; + a->op.right = b; + return arg; +} + +static enum tep_errno add_left(struct tep_filter_arg *op, struct tep_filter_arg *arg) +{ + switch (op->type) { + case TEP_FILTER_ARG_EXP: + if (arg->type == TEP_FILTER_ARG_OP) + arg = rotate_op_right(arg, op); + op->exp.left = arg; + break; + + case TEP_FILTER_ARG_OP: + op->op.left = arg; + break; + case TEP_FILTER_ARG_NUM: + if (arg->type == TEP_FILTER_ARG_OP) + arg = rotate_op_right(arg, op); + + /* left arg of compares must be a field */ + if (arg->type != TEP_FILTER_ARG_FIELD && + arg->type != TEP_FILTER_ARG_BOOLEAN) + return TEP_ERRNO__INVALID_ARG_TYPE; + op->num.left = arg; + break; + default: + return TEP_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 tep_filter_op_type *btype, + enum tep_filter_cmp_type *ctype, + enum tep_filter_exp_type *etype) +{ + *btype = TEP_FILTER_OP_NOT; + *etype = TEP_FILTER_EXP_NONE; + *ctype = TEP_FILTER_CMP_NONE; + + if (strcmp(token, "&&") == 0) + *btype = TEP_FILTER_OP_AND; + else if (strcmp(token, "||") == 0) + *btype = TEP_FILTER_OP_OR; + else if (strcmp(token, "!") == 0) + return OP_NOT; + + if (*btype != TEP_FILTER_OP_NOT) + return OP_BOOL; + + /* Check for value expressions */ + if (strcmp(token, "+") == 0) { + *etype = TEP_FILTER_EXP_ADD; + } else if (strcmp(token, "-") == 0) { + *etype = TEP_FILTER_EXP_SUB; + } else if (strcmp(token, "*") == 0) { + *etype = TEP_FILTER_EXP_MUL; + } else if (strcmp(token, "/") == 0) { + *etype = TEP_FILTER_EXP_DIV; + } else if (strcmp(token, "%") == 0) { + *etype = TEP_FILTER_EXP_MOD; + } else if (strcmp(token, ">>") == 0) { + *etype = TEP_FILTER_EXP_RSHIFT; + } else if (strcmp(token, "<<") == 0) { + *etype = TEP_FILTER_EXP_LSHIFT; + } else if (strcmp(token, "&") == 0) { + *etype = TEP_FILTER_EXP_AND; + } else if (strcmp(token, "|") == 0) { + *etype = TEP_FILTER_EXP_OR; + } else if (strcmp(token, "^") == 0) { + *etype = TEP_FILTER_EXP_XOR; + } else if (strcmp(token, "~") == 0) + *etype = TEP_FILTER_EXP_NOT; + + if (*etype != TEP_FILTER_EXP_NONE) + return OP_EXP; + + /* Check for compares */ + if (strcmp(token, "==") == 0) + *ctype = TEP_FILTER_CMP_EQ; + else if (strcmp(token, "!=") == 0) + *ctype = TEP_FILTER_CMP_NE; + else if (strcmp(token, "<") == 0) + *ctype = TEP_FILTER_CMP_LT; + else if (strcmp(token, ">") == 0) + *ctype = TEP_FILTER_CMP_GT; + else if (strcmp(token, "<=") == 0) + *ctype = TEP_FILTER_CMP_LE; + else if (strcmp(token, ">=") == 0) + *ctype = TEP_FILTER_CMP_GE; + else if (strcmp(token, "=~") == 0) + *ctype = TEP_FILTER_CMP_REGEX; + else if (strcmp(token, "!~") == 0) + *ctype = TEP_FILTER_CMP_NOT_REGEX; + else + return OP_NONE; + + return OP_CMP; +} + +static int check_op_done(struct tep_filter_arg *arg) +{ + switch (arg->type) { + case TEP_FILTER_ARG_EXP: + return arg->exp.right != NULL; + + case TEP_FILTER_ARG_OP: + return arg->op.right != NULL; + + case TEP_FILTER_ARG_NUM: + return arg->num.right != NULL; + + case TEP_FILTER_ARG_STR: + /* A string conversion is always done */ + return 1; + + case TEP_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 tep_errno +reparent_op_arg(struct tep_filter_arg *parent, struct tep_filter_arg *old_child, + struct tep_filter_arg *arg, char *error_str) +{ + struct tep_filter_arg *other_child; + struct tep_filter_arg **ptr; + + if (parent->type != TEP_FILTER_ARG_OP && + arg->type != TEP_FILTER_ARG_OP) { + show_error(error_str, "can not reparent other than OP"); + return TEP_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 TEP_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 TEP_ERRNO__REPARENT_FAILED; + } + + *ptr = arg; + + free_arg(old_child); + return 0; +} + +/* Returns either filter_vals (success) or tep_errno (failfure) */ +static int test_arg(struct tep_filter_arg *parent, struct tep_filter_arg *arg, + char *error_str) +{ + int lval, rval; + + switch (arg->type) { + + /* bad case */ + case TEP_FILTER_ARG_BOOLEAN: + return FILTER_VAL_FALSE + arg->boolean.value; + + /* good cases: */ + case TEP_FILTER_ARG_STR: + case TEP_FILTER_ARG_VALUE: + case TEP_FILTER_ARG_FIELD: + return FILTER_VAL_NORM; + + case TEP_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 TEP_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 TEP_FILTER_ARG_OP: + if (arg->op.type != TEP_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 == TEP_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 == TEP_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 == TEP_FILTER_OP_OR) + return FILTER_VAL_TRUE; + if (arg->op.type == TEP_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 == TEP_FILTER_OP_AND) + return FILTER_VAL_FALSE; + if (arg->op.type == TEP_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 TEP_ERRNO__BAD_FILTER_ARG; + } + return FILTER_VAL_NORM; +} + +/* Remove any unknown event fields */ +static int collapse_tree(struct tep_filter_arg *arg, + struct tep_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 = TEP_FILTER_ARG_BOOLEAN; + arg->boolean.value = ret == FILTER_VAL_TRUE; + } else { + show_error(error_str, "Failed to allocate filter arg"); + ret = TEP_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 tep_errno +process_filter(struct tep_event *event, struct tep_filter_arg **parg, + char *error_str, int not) +{ + enum tep_event_type type; + char *token = NULL; + struct tep_filter_arg *current_op = NULL; + struct tep_filter_arg *current_exp = NULL; + struct tep_filter_arg *left_item = NULL; + struct tep_filter_arg *arg = NULL; + enum op_type op_type; + enum tep_filter_op_type btype; + enum tep_filter_exp_type etype; + enum tep_filter_cmp_type ctype; + enum tep_errno ret; + + *parg = NULL; + + do { + free(token); + type = filter_read_token(&token); + switch (type) { + case TEP_EVENT_SQUOTE: + case TEP_EVENT_DQUOTE: + case TEP_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 TEP_EVENT_DELIM: + if (*token == ',') { + show_error(error_str, "Illegal token ','"); + ret = TEP_ERRNO__ILLEGAL_TOKEN; + goto fail; + } + + if (*token == '(') { + if (left_item) { + show_error(error_str, + "Open paren can not come after item"); + ret = TEP_ERRNO__INVALID_PAREN; + goto fail; + } + if (current_exp) { + show_error(error_str, + "Open paren can not come after expression"); + ret = TEP_ERRNO__INVALID_PAREN; + goto fail; + } + + ret = process_filter(event, &arg, error_str, 0); + if (ret != TEP_ERRNO__UNBALANCED_PAREN) { + if (ret == 0) { + show_error(error_str, + "Unbalanced number of '('"); + ret = TEP_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; + free(token); + return TEP_ERRNO__UNBALANCED_PAREN; + } + break; + + case TEP_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 = TEP_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 TEP_EVENT_NONE: + break; + case TEP_EVENT_ERROR: + goto fail_alloc; + default: + goto fail_syntax; + } + } while (type != TEP_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); + /* collapse_tree() may free current_op, and updates parg accordingly */ + current_op = NULL; + if (ret < 0) + goto fail; + + free(token); + return 0; + + fail_alloc: + show_error(error_str, "failed to allocate filter arg"); + ret = TEP_ERRNO__MEM_ALLOC_FAILED; + goto fail; + fail_syntax: + show_error(error_str, "Syntax error"); + ret = TEP_ERRNO__SYNTAX_ERROR; + fail: + free_arg(current_op); + free_arg(current_exp); + free_arg(arg); + free(token); + return ret; +} + +static enum tep_errno +process_event(struct tep_event *event, const char *filter_str, + struct tep_filter_arg **parg, char *error_str) +{ + int ret; + + init_input_buf(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 TEP_ERRNO__MEM_ALLOC_FAILED; + + (*parg)->type = TEP_FILTER_ARG_BOOLEAN; + (*parg)->boolean.value = TEP_FILTER_FALSE; + } + + return 0; +} + +static enum tep_errno +filter_event(struct tep_event_filter *filter, struct tep_event *event, + const char *filter_str, char *error_str) +{ + struct tep_filter_type *filter_type; + struct tep_filter_arg *arg; + enum tep_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 TEP_ERRNO__MEM_ALLOC_FAILED; + + arg->type = TEP_FILTER_ARG_BOOLEAN; + arg->boolean.value = TEP_FILTER_TRUE; + } + + filter_type = add_filter_type(filter, event->id); + if (filter_type == NULL) { + free_arg(arg); + return TEP_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 tep_event_filter *filter) +{ + /* clear buffer to reset show error */ + init_input_buf("", 0); + filter->error_buffer[0] = '\0'; +} + +/** + * tep_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 tep_filter_strerror() to see + * actual error message in case of error. + */ +enum tep_errno tep_filter_add_filter_str(struct tep_event_filter *filter, + const char *filter_str) +{ + struct tep_handle *tep = filter->tep; + 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 tep_errno rtn = 0; /* TEP_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 TEP_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 TEP_ERRNO__FILTER_NOT_FOUND; + } + + /* Find this event */ + ret = find_event(tep, &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 && tep->test_filters) { + char *test; + test = tep_filter_make_string(filter, event->event->id); + if (test) { + printf(" '%s: %s'\n", event->event->name, test); + free(test); + } + } + } + + free_events(events); + + return rtn; +} + +static void free_filter_type(struct tep_filter_type *filter_type) +{ + free_arg(filter_type->filter); +} + +/** + * tep_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 tep_filter_strerror(struct tep_event_filter *filter, enum tep_errno err, + char *buf, size_t buflen) +{ + if (err <= __TEP_ERRNO__START || err >= __TEP_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 tep_strerror(filter->tep, err, buf, buflen); +} + +/** + * tep_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 tep_filter_remove_event(struct tep_event_filter *filter, + int event_id) +{ + struct tep_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; +} + +/** + * tep_filter_reset - clear all filters in a filter + * @filter: the event filter to reset + * + * Removes all filters from a filter and resets it. + */ +void tep_filter_reset(struct tep_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 tep_filter_free(struct tep_event_filter *filter) +{ + tep_unref(filter->tep); + + tep_filter_reset(filter); + + free(filter); +} + +static char *arg_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg); + +static int copy_filter_type(struct tep_event_filter *filter, + struct tep_event_filter *source, + struct tep_filter_type *filter_type) +{ + struct tep_filter_arg *arg; + struct tep_event *event; + const char *sys; + const char *name; + char *str; + + /* Can't assume that the tep's are the same */ + sys = filter_type->event->system; + name = filter_type->event->name; + event = tep_find_event_by_name(filter->tep, 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) { + free(str); + return -1; + } + + arg->type = TEP_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) { + free(str); + free_arg(arg); + return -1; + } + + filter_type->filter = arg; + + free(str); + return 0; + } + + filter_event(filter, event, str, NULL); + free(str); + + return 0; +} + +/** + * tep_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 tep_filter_copy(struct tep_event_filter *dest, struct tep_event_filter *source) +{ + int ret = 0; + int i; + + tep_filter_reset(dest); + + for (i = 0; i < source->filters; i++) { + if (copy_filter_type(dest, source, &source->event_filters[i])) + ret = -1; + } + return ret; +} + +static int test_filter(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_errno *err); + +static const char * +get_comm(struct tep_event *event, struct tep_record *record) +{ + const char *comm; + int pid; + + pid = tep_data_pid(event->tep, record); + comm = tep_data_comm_from_pid(event->tep, pid); + return comm; +} + +static unsigned long long +get_value(struct tep_event *event, + struct tep_format_field *field, struct tep_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; + } + + /* Handle our dummy "cpu" field */ + if (field == &cpu) + return record->cpu; + + tep_read_number_field(field, record->data, &val); + + if (!(field->flags & TEP_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 tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_errno *err); + +static unsigned long long +get_exp_value(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_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 TEP_FILTER_EXP_ADD: + return lval + rval; + + case TEP_FILTER_EXP_SUB: + return lval - rval; + + case TEP_FILTER_EXP_MUL: + return lval * rval; + + case TEP_FILTER_EXP_DIV: + return lval / rval; + + case TEP_FILTER_EXP_MOD: + return lval % rval; + + case TEP_FILTER_EXP_RSHIFT: + return lval >> rval; + + case TEP_FILTER_EXP_LSHIFT: + return lval << rval; + + case TEP_FILTER_EXP_AND: + return lval & rval; + + case TEP_FILTER_EXP_OR: + return lval | rval; + + case TEP_FILTER_EXP_XOR: + return lval ^ rval; + + case TEP_FILTER_EXP_NOT: + default: + if (!*err) + *err = TEP_ERRNO__INVALID_EXP_TYPE; + } + return 0; +} + +static unsigned long long +get_arg_value(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_errno *err) +{ + switch (arg->type) { + case TEP_FILTER_ARG_FIELD: + return get_value(event, arg->field.field, record); + + case TEP_FILTER_ARG_VALUE: + if (arg->value.type != TEP_FILTER_NUMBER) { + if (!*err) + *err = TEP_ERRNO__NOT_A_NUMBER; + } + return arg->value.val; + + case TEP_FILTER_ARG_EXP: + return get_exp_value(event, arg, record, err); + + default: + if (!*err) + *err = TEP_ERRNO__INVALID_ARG_TYPE; + } + return 0; +} + +static int test_num(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_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 TEP_FILTER_CMP_EQ: + return lval == rval; + + case TEP_FILTER_CMP_NE: + return lval != rval; + + case TEP_FILTER_CMP_GT: + return lval > rval; + + case TEP_FILTER_CMP_LT: + return lval < rval; + + case TEP_FILTER_CMP_GE: + return lval >= rval; + + case TEP_FILTER_CMP_LE: + return lval <= rval; + + default: + if (!*err) + *err = TEP_ERRNO__ILLEGAL_INTEGER_CMP; + return 0; + } +} + +static const char *get_field_str(struct tep_filter_arg *arg, struct tep_record *record) +{ + struct tep_event *event; + struct tep_handle *tep; + unsigned long long addr; + const char *val = NULL; + unsigned int size; + char hex[64]; + + /* If the field is not a string convert it */ + if (arg->str.field->flags & TEP_FIELD_IS_STRING) { + val = record->data + arg->str.field->offset; + size = arg->str.field->size; + + if (arg->str.field->flags & TEP_FIELD_IS_DYNAMIC) { + addr = *(unsigned int *)val; + val = record->data + (addr & 0xffff); + size = addr >> 16; + } + + /* + * We need to copy the data since we can't be sure the field + * is null terminated. + */ + if (*(val + 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; + tep = event->tep; + addr = get_value(event, arg->str.field, record); + + if (arg->str.field->flags & (TEP_FIELD_IS_POINTER | TEP_FIELD_IS_LONG)) + /* convert to a kernel symbol */ + val = tep_find_function(tep, 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 tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_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 TEP_FILTER_CMP_MATCH: + return strcmp(val, arg->str.val) == 0; + + case TEP_FILTER_CMP_NOT_MATCH: + return strcmp(val, arg->str.val) != 0; + + case TEP_FILTER_CMP_REGEX: + /* Returns zero on match */ + return !regexec(&arg->str.reg, val, 0, NULL, 0); + + case TEP_FILTER_CMP_NOT_REGEX: + return regexec(&arg->str.reg, val, 0, NULL, 0); + + default: + if (!*err) + *err = TEP_ERRNO__ILLEGAL_STRING_CMP; + return 0; + } +} + +static int test_op(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_errno *err) +{ + switch (arg->op.type) { + case TEP_FILTER_OP_AND: + return test_filter(event, arg->op.left, record, err) && + test_filter(event, arg->op.right, record, err); + + case TEP_FILTER_OP_OR: + return test_filter(event, arg->op.left, record, err) || + test_filter(event, arg->op.right, record, err); + + case TEP_FILTER_OP_NOT: + return !test_filter(event, arg->op.right, record, err); + + default: + if (!*err) + *err = TEP_ERRNO__INVALID_OP_TYPE; + return 0; + } +} + +static int test_filter(struct tep_event *event, struct tep_filter_arg *arg, + struct tep_record *record, enum tep_errno *err) +{ + if (*err) { + /* + * There was an error, no need to process anymore. + */ + return 0; + } + + switch (arg->type) { + case TEP_FILTER_ARG_BOOLEAN: + /* easy case */ + return arg->boolean.value; + + case TEP_FILTER_ARG_OP: + return test_op(event, arg, record, err); + + case TEP_FILTER_ARG_NUM: + return test_num(event, arg, record, err); + + case TEP_FILTER_ARG_STR: + return test_str(event, arg, record, err); + + case TEP_FILTER_ARG_EXP: + case TEP_FILTER_ARG_VALUE: + case TEP_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 = TEP_ERRNO__INVALID_ARG_TYPE; + return 0; + } +} + +/** + * tep_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 tep_event_filtered(struct tep_event_filter *filter, int event_id) +{ + struct tep_filter_type *filter_type; + + if (!filter->filters) + return 0; + + filter_type = find_filter_type(filter, event_id); + + return filter_type ? 1 : 0; +} + +/** + * tep_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 TEP_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 tep_errno tep_filter_match(struct tep_event_filter *filter, + struct tep_record *record) +{ + struct tep_handle *tep = filter->tep; + struct tep_filter_type *filter_type; + int event_id; + int ret; + enum tep_errno err = 0; + + filter_init_error_buf(filter); + + if (!filter->filters) + return TEP_ERRNO__NO_FILTER; + + event_id = tep_data_type(tep, record); + + filter_type = find_filter_type(filter, event_id); + if (!filter_type) + return TEP_ERRNO__FILTER_NOT_FOUND; + + ret = test_filter(filter_type->event, filter_type->filter, record, &err); + if (err) + return err; + + return ret ? TEP_ERRNO__FILTER_MATCH : TEP_ERRNO__FILTER_MISS; +} + +static char *op_to_str(struct tep_event_filter *filter, struct tep_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 TEP_FILTER_OP_AND: + op = "&&"; + /* fall through */ + case TEP_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 == TEP_FILTER_OP_AND && !left_val) || + (arg->op.type == TEP_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 TEP_FILTER_OP_AND: + val = left_val && right_val; + break; + case TEP_FILTER_OP_OR: + val = left_val || right_val; + break; + default: + break; + } + if (asprintf(&str, val ? "TRUE" : "FALSE") < 0) + str = NULL; + break; + } + } + if (right_val >= 0) { + if ((arg->op.type == TEP_FILTER_OP_AND && !right_val) || + (arg->op.type == TEP_FILTER_OP_OR && right_val)) { + /* Just return right value */ + str = right; + right = NULL; + break; + } + /* The right value is meaningless */ + str = left; + left = NULL; + break; + } + + if (asprintf(&str, "(%s) %s (%s)", left, op, right) < 0) + str = NULL; + break; + + case TEP_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 */ + if (asprintf(&str, right_val ? "FALSE" : "TRUE") < 0) + str = NULL; + break; + } + if (asprintf(&str, "%s(%s)", op, right) < 0) + str = NULL; + break; + + default: + /* ?? */ + break; + } + free(left); + free(right); + return str; +} + +static char *val_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg) +{ + char *str = NULL; + + if (asprintf(&str, "%lld", arg->value.val) < 0) + str = NULL; + + return str; +} + +static char *field_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg) +{ + return strdup(arg->field.field->name); +} + +static char *exp_to_str(struct tep_event_filter *filter, struct tep_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 TEP_FILTER_EXP_ADD: + op = "+"; + break; + case TEP_FILTER_EXP_SUB: + op = "-"; + break; + case TEP_FILTER_EXP_MUL: + op = "*"; + break; + case TEP_FILTER_EXP_DIV: + op = "/"; + break; + case TEP_FILTER_EXP_MOD: + op = "%"; + break; + case TEP_FILTER_EXP_RSHIFT: + op = ">>"; + break; + case TEP_FILTER_EXP_LSHIFT: + op = "<<"; + break; + case TEP_FILTER_EXP_AND: + op = "&"; + break; + case TEP_FILTER_EXP_OR: + op = "|"; + break; + case TEP_FILTER_EXP_XOR: + op = "^"; + break; + default: + op = "[ERROR IN EXPRESSION TYPE]"; + break; + } + + if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0) + str = NULL; +out: + free(lstr); + free(rstr); + + return str; +} + +static char *num_to_str(struct tep_event_filter *filter, struct tep_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 TEP_FILTER_CMP_EQ: + op = "=="; + /* fall through */ + case TEP_FILTER_CMP_NE: + if (!op) + op = "!="; + /* fall through */ + case TEP_FILTER_CMP_GT: + if (!op) + op = ">"; + /* fall through */ + case TEP_FILTER_CMP_LT: + if (!op) + op = "<"; + /* fall through */ + case TEP_FILTER_CMP_GE: + if (!op) + op = ">="; + /* fall through */ + case TEP_FILTER_CMP_LE: + if (!op) + op = "<="; + + if (asprintf(&str, "%s %s %s", lstr, op, rstr) < 0) + str = NULL; + break; + + default: + /* ?? */ + break; + } + +out: + free(lstr); + free(rstr); + return str; +} + +static char *str_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg) +{ + char *str = NULL; + char *op = NULL; + + switch (arg->str.type) { + case TEP_FILTER_CMP_MATCH: + op = "=="; + /* fall through */ + case TEP_FILTER_CMP_NOT_MATCH: + if (!op) + op = "!="; + /* fall through */ + case TEP_FILTER_CMP_REGEX: + if (!op) + op = "=~"; + /* fall through */ + case TEP_FILTER_CMP_NOT_REGEX: + if (!op) + op = "!~"; + + if (asprintf(&str, "%s %s \"%s\"", + arg->str.field->name, op, arg->str.val) < 0) + str = NULL; + break; + + default: + /* ?? */ + break; + } + return str; +} + +static char *arg_to_str(struct tep_event_filter *filter, struct tep_filter_arg *arg) +{ + char *str = NULL; + + switch (arg->type) { + case TEP_FILTER_ARG_BOOLEAN: + if (asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE") < 0) + str = NULL; + return str; + + case TEP_FILTER_ARG_OP: + return op_to_str(filter, arg); + + case TEP_FILTER_ARG_NUM: + return num_to_str(filter, arg); + + case TEP_FILTER_ARG_STR: + return str_to_str(filter, arg); + + case TEP_FILTER_ARG_VALUE: + return val_to_str(filter, arg); + + case TEP_FILTER_ARG_FIELD: + return field_to_str(filter, arg); + + case TEP_FILTER_ARG_EXP: + return exp_to_str(filter, arg); + + default: + /* ?? */ + return NULL; + } + +} + +/** + * tep_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 * +tep_filter_make_string(struct tep_event_filter *filter, int event_id) +{ + struct tep_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); +} + +/** + * tep_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 tep_filter_compare(struct tep_event_filter *filter1, struct tep_event_filter *filter2) +{ + struct tep_filter_type *filter_type1; + struct tep_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; + /* 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 000000000..e99867111 --- /dev/null +++ b/tools/lib/traceevent/parse-utils.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#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("libtraceevent"); + 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/plugins/Build b/tools/lib/traceevent/plugins/Build new file mode 100644 index 000000000..dd4da823c --- /dev/null +++ b/tools/lib/traceevent/plugins/Build @@ -0,0 +1,12 @@ +plugin_jbd2-y += plugin_jbd2.o +plugin_hrtimer-y += plugin_hrtimer.o +plugin_kmem-y += plugin_kmem.o +plugin_kvm-y += plugin_kvm.o +plugin_mac80211-y += plugin_mac80211.o +plugin_sched_switch-y += plugin_sched_switch.o +plugin_function-y += plugin_function.o +plugin_futex-y += plugin_futex.o +plugin_xen-y += plugin_xen.o +plugin_scsi-y += plugin_scsi.o +plugin_cfg80211-y += plugin_cfg80211.o +plugin_tlb-y += plugin_tlb.o
\ No newline at end of file diff --git a/tools/lib/traceevent/plugins/Makefile b/tools/lib/traceevent/plugins/Makefile new file mode 100644 index 000000000..47e802553 --- /dev/null +++ b/tools/lib/traceevent/plugins/Makefile @@ -0,0 +1,225 @@ +# SPDX-License-Identifier: GPL-2.0 + +#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) +$(call allow-override,NM,$(CROSS_COMPILE)nm) +$(call allow-override,PKG_CONFIG,pkg-config) + +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))' + +LP64 := $(shell echo __LP64__ | ${CC} ${CFLAGS} -E -x c - | tail -n 1) +ifeq ($(LP64), 1) + libdir_relative_tmp = lib64 +else + libdir_relative_tmp = lib +endif + +libdir_relative ?= $(libdir_relative_tmp) +prefix ?= /usr/local +libdir = $(prefix)/$(libdir_relative) + +set_plugin_dir := 1 + +# Set plugin_dir to preffered global plugin location +# If we install under $HOME directory we go under +# $(HOME)/.local/lib/traceevent/plugins +# +# We dont set PLUGIN_DIR in case we install under $HOME +# directory, because by default the code looks under: +# $(HOME)/.local/lib/traceevent/plugins by default. +# +ifeq ($(plugin_dir),) +ifeq ($(prefix),$(HOME)) +override plugin_dir = $(HOME)/.local/lib/traceevent/plugins +set_plugin_dir := 0 +else +override plugin_dir = $(libdir)/traceevent/plugins +endif +endif + +ifeq ($(set_plugin_dir),1) +PLUGIN_DIR = -DPLUGIN_DIR="$(plugin_dir)" +PLUGIN_DIR_SQ = '$(subst ','\'',$(PLUGIN_DIR))' +endif + +include ../../../scripts/Makefile.include + +# copy a bit from Linux kbuild + +ifeq ("$(origin V)", "command line") + VERBOSE = $(V) +endif +ifndef VERBOSE + VERBOSE = 0 +endif + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +#$(info Determined 'srctree' to be $(srctree)) +endif + +export prefix libdir src obj + +# Shell quotes +plugin_dir_SQ = $(subst ','\'',$(plugin_dir)) + +CONFIG_INCLUDES = +CONFIG_LIBS = +CONFIG_FLAGS = + +OBJ = $@ +N = + +INCLUDES = -I. -I.. -I $(srctree)/tools/include $(CONFIG_INCLUDES) + +# Set compile option CFLAGS +ifdef EXTRA_CFLAGS + CFLAGS := $(EXTRA_CFLAGS) +else + CFLAGS := -g -Wall +endif + +# Append required CFLAGS +override CFLAGS += -fPIC +override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ) +override CFLAGS += $(udis86-flags) -D_GNU_SOURCE + +ifeq ($(VERBOSE),1) + Q = +else + Q = @ +endif + +# Disable command line variables (CFLAGS) override from top +# level Makefile (perf), otherwise build Makefile will get +# the same command line setup. +MAKEOVERRIDES= + +export srctree OUTPUT CC LD CFLAGS V + +build := -f $(srctree)/tools/build/Makefile.build dir=. obj + +DYNAMIC_LIST_FILE := $(OUTPUT)libtraceevent-dynamic-list + +PLUGINS = plugin_jbd2.so +PLUGINS += plugin_hrtimer.so +PLUGINS += plugin_kmem.so +PLUGINS += plugin_kvm.so +PLUGINS += plugin_mac80211.so +PLUGINS += plugin_sched_switch.so +PLUGINS += plugin_function.so +PLUGINS += plugin_futex.so +PLUGINS += plugin_xen.so +PLUGINS += plugin_scsi.so +PLUGINS += plugin_cfg80211.so +PLUGINS += plugin_tlb.so + +PLUGINS := $(addprefix $(OUTPUT),$(PLUGINS)) +PLUGINS_IN := $(PLUGINS:.so=-in.o) + +plugins: $(PLUGINS) $(DYNAMIC_LIST_FILE) + +__plugin_obj = $(notdir $@) + plugin_obj = $(__plugin_obj:-in.o=) + +$(PLUGINS_IN): force + $(Q)$(MAKE) $(build)=$(plugin_obj) + +$(OUTPUT)libtraceevent-dynamic-list: $(PLUGINS) + $(QUIET_GEN)$(call do_generate_dynamic_list_file, $(PLUGINS), $@) + +$(OUTPUT)%.so: $(OUTPUT)%-in.o + $(QUIET_LINK)$(CC) $(CFLAGS) -shared $(LDFLAGS) -nostartfiles -o $@ $^ + +define update_dir + (echo $1 > $@.tmp; \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + echo ' UPDATE $@'; \ + mv -f $@.tmp $@; \ + fi); +endef + +tags: force + $(RM) tags + find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \ + --regex-c++='/_PE\(([^,)]*).*/TEP_ERRNO__\1/' + +TAGS: force + $(RM) TAGS + find . -name '*.[ch]' | xargs etags \ + --regex='/_PE(\([^,)]*\).*/TEP_ERRNO__\1/' + +define do_install_mkdir + if [ ! -d '$(DESTDIR_SQ)$1' ]; then \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$1'; \ + fi +endef + +define do_install + $(call do_install_mkdir,$2); \ + $(INSTALL) $(if $3,-m $3,) $1 '$(DESTDIR_SQ)$2' +endef + +define do_install_plugins + for plugin in $1; do \ + $(call do_install,$$plugin,$(plugin_dir_SQ)); \ + done +endef + +define do_generate_dynamic_list_file + symbol_type=`$(NM) -u -D $1 | awk 'NF>1 {print $$1}' | \ + xargs echo "U w W" | tr 'w ' 'W\n' | sort -u | xargs echo`;\ + if [ "$$symbol_type" = "U W" ];then \ + (echo '{'; \ + $(NM) -u -D $1 | awk 'NF>1 {sub("@.*", "", $$2); print "\t"$$2";"}' | sort -u;\ + echo '};'; \ + ) > $2; \ + else \ + (echo Either missing one of [$1] or bad version of $(NM)) 1>&2;\ + fi +endef + +install: $(PLUGINS) + $(call QUIET_INSTALL, trace_plugins) \ + $(call do_install_plugins, $(PLUGINS)) + +clean: + $(call QUIET_CLEAN, trace_plugins) \ + $(RM) *.o *~ $(TARGETS) *.a *.so $(VERSION_FILES) .*.d .*.cmd; \ + $(RM) $(OUTPUT)libtraceevent-dynamic-list \ + $(RM) TRACEEVENT-CFLAGS tags TAGS; + +PHONY += force plugins +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/traceevent/plugins/plugin_cfg80211.c b/tools/lib/traceevent/plugins/plugin_cfg80211.c new file mode 100644 index 000000000..3d43b56a6 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_cfg80211.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include <endian.h> +#include "event-parse.h" + +/* + * From glibc endian.h, for older systems where it is not present, e.g.: RHEL5, + * Fedora6. + */ +#ifndef le16toh +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define le16toh(x) (x) +# else +# define le16toh(x) __bswap_16 (x) +# endif +#endif + + +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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_print_function(tep, + process___le16_to_cpup, + TEP_FUNC_ARG_INT, + "__le16_to_cpup", + TEP_FUNC_ARG_PTR, + TEP_FUNC_ARG_VOID); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_print_function(tep, process___le16_to_cpup, + "__le16_to_cpup"); +} diff --git a/tools/lib/traceevent/plugins/plugin_function.c b/tools/lib/traceevent/plugins/plugin_function.c new file mode 100644 index 000000000..807b16e1b --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_function.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "event-utils.h" +#include "trace-seq.h" + +static struct func_stack { + int size; + char **stack; +} *fstack; + +static int cpus = -1; + +#define STK_BLK 10 + +struct tep_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 = "offset", + .plugin_alias = "ftrace", + .description = + "Show function names as well as their offsets", + .set = 0, + }, + { + .name = NULL, + } +}; + +static struct tep_plugin_option *ftrace_parent = &plugin_options[0]; +static struct tep_plugin_option *ftrace_indent = &plugin_options[1]; +static struct tep_plugin_option *ftrace_offset = &plugin_options[2]; + +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 void show_function(struct trace_seq *s, struct tep_handle *tep, + const char *func, unsigned long long function) +{ + unsigned long long offset; + + trace_seq_printf(s, "%s", func); + if (ftrace_offset->set) { + offset = tep_find_function_address(tep, function); + trace_seq_printf(s, "+0x%x ", (int)(function - offset)); + } +} + +static int function_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_handle *tep = event->tep; + unsigned long long function; + unsigned long long pfunction; + const char *func; + const char *parent; + int index = 0; + + if (tep_get_field_val(s, event, "ip", record, &function, 1)) + return trace_seq_putc(s, '!'); + + func = tep_find_function(tep, function); + + if (tep_get_field_val(s, event, "parent_ip", record, &pfunction, 1)) + return trace_seq_putc(s, '!'); + + parent = tep_find_function(tep, pfunction); + + if (parent && ftrace_indent->set) + index = add_and_get_index(parent, func, record->cpu); + + trace_seq_printf(s, "%*s", index*3, ""); + + if (func) + show_function(s, tep, func, function); + else + trace_seq_printf(s, "0x%llx", function); + + if (ftrace_parent->set) { + trace_seq_printf(s, " <-- "); + if (parent) + show_function(s, tep, parent, pfunction); + else + trace_seq_printf(s, "0x%llx", pfunction); + } + + return 0; +} + +static int +trace_stack_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_format_field *field; + unsigned long long addr; + const char *func; + int long_size; + void *data = record->data; + + field = tep_find_any_field(event, "caller"); + if (!field) { + trace_seq_printf(s, "<CANT FIND FIELD %s>", "caller"); + return 0; + } + + trace_seq_puts(s, "<stack trace >\n"); + + long_size = tep_get_long_size(event->tep); + + for (data += field->offset; data < record->data + record->size; + data += long_size) { + addr = tep_read_number(event->tep, data, long_size); + + if ((long_size == 8 && addr == (unsigned long long)-1) || + ((int)addr == -1)) + break; + + func = tep_find_function(event->tep, addr); + if (func) + trace_seq_printf(s, "=> %s (%llx)\n", func, addr); + else + trace_seq_printf(s, "=> %llx\n", addr); + } + + return 0; +} + +static int +trace_raw_data_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_format_field *field; + unsigned long long id; + int long_size; + void *data = record->data; + + if (tep_get_field_val(s, event, "id", record, &id, 1)) + return trace_seq_putc(s, '!'); + + trace_seq_printf(s, "# %llx", id); + + field = tep_find_any_field(event, "buf"); + if (!field) { + trace_seq_printf(s, "<CANT FIND FIELD %s>", "buf"); + return 0; + } + + long_size = tep_get_long_size(event->tep); + + for (data += field->offset; data < record->data + record->size; + data += long_size) { + int size = sizeof(long); + int left = (record->data + record->size) - data; + int i; + + if (size > left) + size = left; + + for (i = 0; i < size; i++) + trace_seq_printf(s, " %02x", *(unsigned char *)(data + i)); + } + + return 0; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "ftrace", "function", + function_handler, NULL); + + tep_register_event_handler(tep, -1, "ftrace", "kernel_stack", + trace_stack_handler, NULL); + + tep_register_event_handler(tep, -1, "ftrace", "raw_data", + trace_raw_data_handler, NULL); + + tep_plugin_add_options("ftrace", plugin_options); + + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + int i, x; + + tep_unregister_event_handler(tep, -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); + } + + tep_plugin_remove_options(plugin_options); + + free(fstack); + fstack = NULL; + cpus = -1; +} diff --git a/tools/lib/traceevent/plugins/plugin_futex.c b/tools/lib/traceevent/plugins/plugin_futex.c new file mode 100644 index 000000000..eb7c9f8a8 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_futex.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2017 National Instruments Corp. + * + * Author: Julia Cartwright <julia@ni.com> + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <linux/futex.h> + +#include "event-parse.h" + +#define ARRAY_SIZE(_a) (sizeof(_a) / sizeof((_a)[0])) + +struct futex_args { + unsigned long long uaddr; + unsigned long long op; + unsigned long long val; + unsigned long long utime; /* or val2 */ + unsigned long long uaddr2; + unsigned long long val3; +}; + +struct futex_op { + const char *name; + const char *fmt_val; + const char *fmt_utime; + const char *fmt_uaddr2; + const char *fmt_val3; +}; + +static const struct futex_op futex_op_tbl[] = { + { "FUTEX_WAIT", " val=0x%08llx", " utime=0x%08llx", NULL, NULL }, + { "FUTEX_WAKE", " val=%llu", NULL, NULL, NULL }, + { "FUTEX_FD", " val=%llu", NULL, NULL, NULL }, + { "FUTEX_REQUEUE", " val=%llu", " val2=%llu", " uaddr2=0x%08llx", NULL }, + { "FUTEX_CMP_REQUEUE", " val=%llu", " val2=%llu", " uaddr2=0x%08llx", " val3=0x%08llx" }, + { "FUTEX_WAKE_OP", " val=%llu", " val2=%llu", " uaddr2=0x%08llx", " val3=0x%08llx" }, + { "FUTEX_LOCK_PI", NULL, " utime=0x%08llx", NULL, NULL }, + { "FUTEX_UNLOCK_PI", NULL, NULL, NULL, NULL }, + { "FUTEX_TRYLOCK_PI", NULL, NULL, NULL, NULL }, + { "FUTEX_WAIT_BITSET", " val=0x%08llx", " utime=0x%08llx", NULL, " val3=0x%08llx" }, + { "FUTEX_WAKE_BITSET", " val=%llu", NULL, NULL, " val3=0x%08llx" }, + { "FUTEX_WAIT_REQUEUE_PI", " val=0x%08llx", " utime=0x%08llx", " uaddr2=0x%08llx", " val3=0x%08llx" }, + { "FUTEX_CMP_REQUEUE_PI", " val=%llu", " val2=%llu", " uaddr2=0x%08llx", " val3=0x%08llx" }, +}; + + +static void futex_print(struct trace_seq *s, const struct futex_args *args, + const struct futex_op *fop) +{ + trace_seq_printf(s, " uaddr=0x%08llx", args->uaddr); + + if (fop->fmt_val) + trace_seq_printf(s, fop->fmt_val, args->val); + + if (fop->fmt_utime) + trace_seq_printf(s,fop->fmt_utime, args->utime); + + if (fop->fmt_uaddr2) + trace_seq_printf(s, fop->fmt_uaddr2, args->uaddr2); + + if (fop->fmt_val3) + trace_seq_printf(s, fop->fmt_val3, args->val3); +} + +static int futex_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + const struct futex_op *fop; + struct futex_args args; + unsigned long long cmd; + + if (tep_get_field_val(s, event, "uaddr", record, &args.uaddr, 1)) + return 1; + + if (tep_get_field_val(s, event, "op", record, &args.op, 1)) + return 1; + + if (tep_get_field_val(s, event, "val", record, &args.val, 1)) + return 1; + + if (tep_get_field_val(s, event, "utime", record, &args.utime, 1)) + return 1; + + if (tep_get_field_val(s, event, "uaddr2", record, &args.uaddr2, 1)) + return 1; + + if (tep_get_field_val(s, event, "val3", record, &args.val3, 1)) + return 1; + + cmd = args.op & FUTEX_CMD_MASK; + if (cmd >= ARRAY_SIZE(futex_op_tbl)) + return 1; + + fop = &futex_op_tbl[cmd]; + + trace_seq_printf(s, "op=%s", fop->name); + + if (args.op & FUTEX_PRIVATE_FLAG) + trace_seq_puts(s, "|FUTEX_PRIVATE_FLAG"); + + if (args.op & FUTEX_CLOCK_REALTIME) + trace_seq_puts(s, "|FUTEX_CLOCK_REALTIME"); + + futex_print(s, &args, fop); + return 0; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "syscalls", "sys_enter_futex", + futex_handler, NULL); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, "syscalls", "sys_enter_futex", + futex_handler, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_hrtimer.c b/tools/lib/traceevent/plugins/plugin_hrtimer.c new file mode 100644 index 000000000..d98466788 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_hrtimer.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "trace-seq.h" + +static int timer_expire_handler(struct trace_seq *s, + struct tep_record *record, + struct tep_event *event, void *context) +{ + trace_seq_printf(s, "hrtimer="); + + if (tep_print_num_field(s, "0x%llx", event, "timer", + record, 0) == -1) + tep_print_num_field(s, "0x%llx", event, "hrtimer", + record, 1); + + trace_seq_printf(s, " now="); + + tep_print_num_field(s, "%llu", event, "now", record, 1); + + tep_print_func_field(s, " function=%s", event, "function", + record, 0); + return 0; +} + +static int timer_start_handler(struct trace_seq *s, + struct tep_record *record, + struct tep_event *event, void *context) +{ + trace_seq_printf(s, "hrtimer="); + + if (tep_print_num_field(s, "0x%llx", event, "timer", + record, 0) == -1) + tep_print_num_field(s, "0x%llx", event, "hrtimer", + record, 1); + + tep_print_func_field(s, " function=%s", event, "function", + record, 0); + + trace_seq_printf(s, " expires="); + tep_print_num_field(s, "%llu", event, "expires", record, 1); + + trace_seq_printf(s, " softexpires="); + tep_print_num_field(s, "%llu", event, "softexpires", record, 1); + return 0; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, + "timer", "hrtimer_expire_entry", + timer_expire_handler, NULL); + + tep_register_event_handler(tep, -1, "timer", "hrtimer_start", + timer_start_handler, NULL); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, + "timer", "hrtimer_expire_entry", + timer_expire_handler, NULL); + + tep_unregister_event_handler(tep, -1, "timer", "hrtimer_start", + timer_start_handler, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_jbd2.c b/tools/lib/traceevent/plugins/plugin_jbd2.c new file mode 100644 index 000000000..69111a68d --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_jbd2.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "trace-seq.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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_print_function(tep, + process_jbd2_dev_to_name, + TEP_FUNC_ARG_STRING, + "jbd2_dev_to_name", + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_VOID); + + tep_register_print_function(tep, + process_jiffies_to_msecs, + TEP_FUNC_ARG_LONG, + "jiffies_to_msecs", + TEP_FUNC_ARG_LONG, + TEP_FUNC_ARG_VOID); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_print_function(tep, process_jbd2_dev_to_name, + "jbd2_dev_to_name"); + + tep_unregister_print_function(tep, process_jiffies_to_msecs, + "jiffies_to_msecs"); +} diff --git a/tools/lib/traceevent/plugins/plugin_kmem.c b/tools/lib/traceevent/plugins/plugin_kmem.c new file mode 100644 index 000000000..4b4f7f961 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_kmem.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "trace-seq.h" + +static int call_site_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_format_field *field; + unsigned long long val, addr; + void *data = record->data; + const char *func; + + field = tep_find_field(event, "call_site"); + if (!field) + return 1; + + if (tep_read_number_field(field, data, &val)) + return 1; + + func = tep_find_function(event->tep, val); + if (!func) + return 1; + + addr = tep_find_function_address(event->tep, val); + + trace_seq_printf(s, "(%s+0x%x) ", func, (int)(val - addr)); + return 1; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "kmem", "kfree", + call_site_handler, NULL); + + tep_register_event_handler(tep, -1, "kmem", "kmalloc", + call_site_handler, NULL); + + tep_register_event_handler(tep, -1, "kmem", "kmalloc_node", + call_site_handler, NULL); + + tep_register_event_handler(tep, -1, "kmem", "kmem_cache_alloc", + call_site_handler, NULL); + + tep_register_event_handler(tep, -1, "kmem", + "kmem_cache_alloc_node", + call_site_handler, NULL); + + tep_register_event_handler(tep, -1, "kmem", "kmem_cache_free", + call_site_handler, NULL); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, "kmem", "kfree", + call_site_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kmem", "kmalloc", + call_site_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kmem", "kmalloc_node", + call_site_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kmem", "kmem_cache_alloc", + call_site_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kmem", + "kmem_cache_alloc_node", + call_site_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kmem", "kmem_cache_free", + call_site_handler, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_kvm.c b/tools/lib/traceevent/plugins/plugin_kvm.c new file mode 100644 index 000000000..51ceeb914 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_kvm.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "event-parse.h" +#include "trace-seq.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) \ + _ER(PML_FULL, 62) \ + _ER(XSAVES, 63) \ + _ER(XRSTORS, 64) + +#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_DE, 0x040) \ + _ER(EXIT_EXCP_DB, 0x041) \ + _ER(EXIT_EXCP_BP, 0x043) \ + _ER(EXIT_EXCP_OF, 0x044) \ + _ER(EXIT_EXCP_BR, 0x045) \ + _ER(EXIT_EXCP_UD, 0x046) \ + _ER(EXIT_EXCP_NM, 0x047) \ + _ER(EXIT_EXCP_DF, 0x048) \ + _ER(EXIT_EXCP_TS, 0x04a) \ + _ER(EXIT_EXCP_NP, 0x04b) \ + _ER(EXIT_EXCP_SS, 0x04c) \ + _ER(EXIT_EXCP_GP, 0x04d) \ + _ER(EXIT_EXCP_PF, 0x04e) \ + _ER(EXIT_EXCP_MF, 0x050) \ + _ER(EXIT_EXCP_AC, 0x051) \ + _ER(EXIT_EXCP_MC, 0x052) \ + _ER(EXIT_EXCP_XF, 0x053) \ + _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_XSETBV, 0x08d) \ + _ER(EXIT_NPF, 0x400) \ + _ER(EXIT_AVIC_INCOMPLETE_IPI, 0x401) \ + _ER(EXIT_AVIC_UNACCELERATED_ACCESS, 0x402) \ + _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].str; i++) + if (strings[i].val == val) + break; + + return strings[i].str; +} + +static int print_exit_reason(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, const char *field) +{ + unsigned long long isa; + unsigned long long val; + const char *reason; + + if (tep_get_field_val(s, event, field, record, &val, 1) < 0) + return -1; + + if (tep_get_field_val(s, event, "isa", record, &isa, 0) < 0) + isa = 1; + + reason = find_exit_reason(isa, val); + if (reason) + trace_seq_printf(s, "reason %s", reason); + else + trace_seq_printf(s, "reason UNKNOWN (%llu)", val); + return 0; +} + +static int kvm_exit_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + unsigned long long info1 = 0, info2 = 0; + + if (print_exit_reason(s, record, event, "exit_reason") < 0) + return -1; + + tep_print_num_field(s, " rip 0x%lx", event, "guest_rip", record, 1); + + if (tep_get_field_val(s, event, "info1", record, &info1, 0) >= 0 + && tep_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 tep_record *record, + struct tep_event *event, void *context) +{ + unsigned long long rip, csbase, len, flags, failed; + int llen; + uint8_t *insn; + const char *disasm; + + if (tep_get_field_val(s, event, "rip", record, &rip, 1) < 0) + return -1; + + if (tep_get_field_val(s, event, "csbase", record, &csbase, 1) < 0) + return -1; + + if (tep_get_field_val(s, event, "len", record, &len, 1) < 0) + return -1; + + if (tep_get_field_val(s, event, "flags", record, &flags, 1) < 0) + return -1; + + if (tep_get_field_val(s, event, "failed", record, &failed, 1) < 0) + return -1; + + insn = tep_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; +} + + +static int kvm_nested_vmexit_inject_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + if (print_exit_reason(s, record, event, "exit_code") < 0) + return -1; + + tep_print_num_field(s, " info1 %llx", event, "exit_info1", record, 1); + tep_print_num_field(s, " info2 %llx", event, "exit_info2", record, 1); + tep_print_num_field(s, " int_info %llx", event, "exit_int_info", record, 1); + tep_print_num_field(s, " int_info_err %llx", event, "exit_int_info_err", record, 1); + + return 0; +} + +static int kvm_nested_vmexit_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + tep_print_num_field(s, "rip %llx ", event, "rip", record, 1); + + return kvm_nested_vmexit_inject_handler(s, record, event, context); +} + +union kvm_mmu_page_role { + unsigned word; + struct { + unsigned level:4; + unsigned cr4_pae:1; + unsigned quadrant:2; + unsigned direct:1; + unsigned access:3; + unsigned invalid:1; + unsigned nxe:1; + unsigned cr0_wp:1; + unsigned smep_and_not_wp:1; + unsigned smap_and_not_wp:1; + unsigned pad_for_nice_hex_output:8; + unsigned smm:8; + }; +}; + +static int kvm_mmu_print_role(struct trace_seq *s, struct tep_record *record, + struct tep_event *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 (tep_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 + * endianness. + */ + if (tep_is_file_bigendian(event->tep) == + tep_is_local_bigendian(event->tep)) { + + trace_seq_printf(s, "%u q%u%s %s%s %spae %snxe %swp%s%s%s", + role.level, + role.quadrant, + role.direct ? " direct" : "", + access_str[role.access], + role.invalid ? " invalid" : "", + role.cr4_pae ? "" : "!", + role.nxe ? "" : "!", + role.cr0_wp ? "" : "!", + role.smep_and_not_wp ? " smep" : "", + role.smap_and_not_wp ? " smap" : "", + role.smm ? " smm" : ""); + } else + trace_seq_printf(s, "WORD: %08x", role.word); + + tep_print_num_field(s, " root %u ", event, + "root_count", record, 1); + + if (tep_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 tep_record *record, + struct tep_event *event, void *context) +{ + unsigned long long val; + + if (tep_get_field_val(s, event, "created", record, &val, 1) < 0) + return -1; + + trace_seq_printf(s, "%s ", val ? "new" : "existing"); + + if (tep_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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + init_disassembler(); + + tep_register_event_handler(tep, -1, "kvm", "kvm_exit", + kvm_exit_handler, NULL); + + tep_register_event_handler(tep, -1, "kvm", "kvm_emulate_insn", + kvm_emulate_insn_handler, NULL); + + tep_register_event_handler(tep, -1, "kvm", "kvm_nested_vmexit", + kvm_nested_vmexit_handler, NULL); + + tep_register_event_handler(tep, -1, "kvm", "kvm_nested_vmexit_inject", + kvm_nested_vmexit_inject_handler, NULL); + + tep_register_event_handler(tep, -1, "kvmmmu", "kvm_mmu_get_page", + kvm_mmu_get_page_handler, NULL); + + tep_register_event_handler(tep, -1, "kvmmmu", "kvm_mmu_sync_page", + kvm_mmu_print_role, NULL); + + tep_register_event_handler(tep, -1, + "kvmmmu", "kvm_mmu_unsync_page", + kvm_mmu_print_role, NULL); + + tep_register_event_handler(tep, -1, "kvmmmu", "kvm_mmu_zap_page", + kvm_mmu_print_role, NULL); + + tep_register_event_handler(tep, -1, "kvmmmu", + "kvm_mmu_prepare_zap_page", kvm_mmu_print_role, + NULL); + + tep_register_print_function(tep, + process_is_writable_pte, + TEP_FUNC_ARG_INT, + "is_writable_pte", + TEP_FUNC_ARG_LONG, + TEP_FUNC_ARG_VOID); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, "kvm", "kvm_exit", + kvm_exit_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kvm", "kvm_emulate_insn", + kvm_emulate_insn_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kvm", "kvm_nested_vmexit", + kvm_nested_vmexit_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kvm", "kvm_nested_vmexit_inject", + kvm_nested_vmexit_inject_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kvmmmu", "kvm_mmu_get_page", + kvm_mmu_get_page_handler, NULL); + + tep_unregister_event_handler(tep, -1, "kvmmmu", "kvm_mmu_sync_page", + kvm_mmu_print_role, NULL); + + tep_unregister_event_handler(tep, -1, + "kvmmmu", "kvm_mmu_unsync_page", + kvm_mmu_print_role, NULL); + + tep_unregister_event_handler(tep, -1, "kvmmmu", "kvm_mmu_zap_page", + kvm_mmu_print_role, NULL); + + tep_unregister_event_handler(tep, -1, "kvmmmu", + "kvm_mmu_prepare_zap_page", kvm_mmu_print_role, + NULL); + + tep_unregister_print_function(tep, process_is_writable_pte, + "is_writable_pte"); +} diff --git a/tools/lib/traceevent/plugins/plugin_mac80211.c b/tools/lib/traceevent/plugins/plugin_mac80211.c new file mode 100644 index 000000000..f48071e3c --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_mac80211.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "trace-seq.h" + +#define INDENT 65 + +static void print_string(struct trace_seq *s, struct tep_event *event, + const char *name, const void *data) +{ + struct tep_format_field *f = tep_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 (tep_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) tep_print_num_field(s, fn ":%d", event, fn, record, 0) +#define SFX(fn) tep_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 tep_record *record, + struct tep_event *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); + tep_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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "mac80211", + "drv_bss_info_changed", + drv_bss_info_changed, NULL); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, "mac80211", + "drv_bss_info_changed", + drv_bss_info_changed, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_sched_switch.c b/tools/lib/traceevent/plugins/plugin_sched_switch.c new file mode 100644 index 000000000..e12fa1038 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_sched_switch.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" +#include "trace-seq.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 tep_format_field *field, + struct tep_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 */ + tep_register_comm(field->event->tep, comm, pid); +} + +static int sched_wakeup_handler(struct trace_seq *s, + struct tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_format_field *field; + unsigned long long val; + + if (tep_get_field_val(s, event, "pid", record, &val, 1)) + return trace_seq_putc(s, '!'); + + field = tep_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 (tep_get_field_val(s, event, "prio", record, &val, 0) == 0) + trace_seq_printf(s, " [%lld]", val); + + if (tep_get_field_val(s, event, "success", record, &val, 1) == 0) + trace_seq_printf(s, " success=%lld", val); + + if (tep_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 tep_record *record, + struct tep_event *event, void *context) +{ + struct tep_format_field *field; + unsigned long long val; + + if (tep_get_field_val(s, event, "prev_pid", record, &val, 1)) + return trace_seq_putc(s, '!'); + + field = tep_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 (tep_get_field_val(s, event, "prev_prio", record, &val, 0) == 0) + trace_seq_printf(s, "[%d] ", (int) val); + + if (tep_get_field_val(s, event, "prev_state", record, &val, 0) == 0) + write_state(s, val); + + trace_seq_puts(s, " ==> "); + + if (tep_get_field_val(s, event, "next_pid", record, &val, 1)) + return trace_seq_putc(s, '!'); + + field = tep_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 (tep_get_field_val(s, event, "next_prio", record, &val, 0) == 0) + trace_seq_printf(s, " [%d]", (int) val); + + return 0; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "sched", "sched_switch", + sched_switch_handler, NULL); + + tep_register_event_handler(tep, -1, "sched", "sched_wakeup", + sched_wakeup_handler, NULL); + + tep_register_event_handler(tep, -1, "sched", "sched_wakeup_new", + sched_wakeup_handler, NULL); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, "sched", "sched_switch", + sched_switch_handler, NULL); + + tep_unregister_event_handler(tep, -1, "sched", "sched_wakeup", + sched_wakeup_handler, NULL); + + tep_unregister_event_handler(tep, -1, "sched", "sched_wakeup_new", + sched_wakeup_handler, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_scsi.c b/tools/lib/traceevent/plugins/plugin_scsi.c new file mode 100644 index 000000000..5d0387a4b --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_scsi.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <string.h> +#include <inttypes.h> +#include "event-parse.h" +#include "trace-seq.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 SERVICE_ACTION_OUT_12 0xa9 +#define WRITE_12 0xaa +#define SERVICE_ACTION_IN_12 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_BIDIRECTIONAL 0x9d +#define SERVICE_ACTION_IN_16 0x9e +#define SERVICE_ACTION_OUT_16 0x9f +/* 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_16: + 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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_print_function(tep, + process_scsi_trace_parse_cdb, + TEP_FUNC_ARG_STRING, + "scsi_trace_parse_cdb", + TEP_FUNC_ARG_PTR, + TEP_FUNC_ARG_PTR, + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_VOID); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_print_function(tep, process_scsi_trace_parse_cdb, + "scsi_trace_parse_cdb"); +} diff --git a/tools/lib/traceevent/plugins/plugin_tlb.c b/tools/lib/traceevent/plugins/plugin_tlb.c new file mode 100644 index 000000000..43657fb60 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_tlb.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2015 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "event-parse.h" + +enum tlb_flush_reason { + TLB_FLUSH_ON_TASK_SWITCH, + TLB_REMOTE_SHOOTDOWN, + TLB_LOCAL_SHOOTDOWN, + TLB_LOCAL_MM_SHOOTDOWN, + NR_TLB_FLUSH_REASONS, +}; + +static int tlb_flush_handler(struct trace_seq *s, struct tep_record *record, + struct tep_event *event, void *context) +{ + unsigned long long val; + + trace_seq_printf(s, "pages="); + + tep_print_num_field(s, "%ld", event, "pages", record, 1); + + if (tep_get_field_val(s, event, "reason", record, &val, 1) < 0) + return -1; + + trace_seq_puts(s, " reason="); + + switch (val) { + case TLB_FLUSH_ON_TASK_SWITCH: + trace_seq_puts(s, "flush on task switch"); + break; + case TLB_REMOTE_SHOOTDOWN: + trace_seq_puts(s, "remote shootdown"); + break; + case TLB_LOCAL_SHOOTDOWN: + trace_seq_puts(s, "local shootdown"); + break; + case TLB_LOCAL_MM_SHOOTDOWN: + trace_seq_puts(s, "local mm shootdown"); + break; + } + + trace_seq_printf(s, " (%lld)", val); + + return 0; +} + +int TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_event_handler(tep, -1, "tlb", "tlb_flush", + tlb_flush_handler, NULL); + + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_event_handler(tep, -1, + "tlb", "tlb_flush", + tlb_flush_handler, NULL); +} diff --git a/tools/lib/traceevent/plugins/plugin_xen.c b/tools/lib/traceevent/plugins/plugin_xen.c new file mode 100644 index 000000000..993b208d0 --- /dev/null +++ b/tools/lib/traceevent/plugins/plugin_xen.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "event-parse.h" +#include "trace-seq.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 TEP_PLUGIN_LOADER(struct tep_handle *tep) +{ + tep_register_print_function(tep, + process_xen_hypercall_name, + TEP_FUNC_ARG_STRING, + "xen_hypercall_name", + TEP_FUNC_ARG_INT, + TEP_FUNC_ARG_VOID); + return 0; +} + +void TEP_PLUGIN_UNLOADER(struct tep_handle *tep) +{ + tep_unregister_print_function(tep, process_xen_hypercall_name, + "xen_hypercall_name"); +} diff --git a/tools/lib/traceevent/tep_strerror.c b/tools/lib/traceevent/tep_strerror.c new file mode 100644 index 000000000..4ac26445b --- /dev/null +++ b/tools/lib/traceevent/tep_strerror.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: LGPL-2.1 +#undef _GNU_SOURCE +#include <string.h> +#include <stdio.h> + +#include "event-parse.h" + +#undef _PE +#define _PE(code, str) str +static const char * const tep_error_str[] = { + TEP_ERRORS +}; +#undef _PE + +/* + * The tools so far have been using the strerror_r() GNU variant, that returns + * a string, be it the buffer passed or something else. + * + * But that, besides being tricky in cases where we expect that the function + * using strerror_r() returns the error formatted in a provided buffer (we have + * to check if it returned something else and copy that instead), breaks the + * build on systems not using glibc, like Alpine Linux, where musl libc is + * used. + * + * So, introduce yet another wrapper, str_error_r(), that has the GNU + * interface, but uses the portable XSI variant of strerror_r(), so that users + * rest asured that the provided buffer is used and it is what is returned. + */ +int tep_strerror(struct tep_handle *tep __maybe_unused, + enum tep_errno errnum, char *buf, size_t buflen) +{ + const char *msg; + int idx; + + if (!buflen) + return 0; + + if (errnum >= 0) { + int err = strerror_r(errnum, buf, buflen); + buf[buflen - 1] = 0; + return err; + } + + if (errnum <= __TEP_ERRNO__START || + errnum >= __TEP_ERRNO__END) + return -1; + + idx = errnum - __TEP_ERRNO__START - 1; + msg = tep_error_str[idx]; + snprintf(buf, buflen, "%s", msg); + + return 0; +} diff --git a/tools/lib/traceevent/trace-seq.c b/tools/lib/traceevent/trace-seq.c new file mode 100644 index 000000000..8d5ecd2bf --- /dev/null +++ b/tools/lib/traceevent/trace-seq.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ +#include "trace-seq.h" + +#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, the number of characters printed, or a negative + * value in case of an error. + * + * 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; + } + + if (ret > 0) + s->len += ret; + + return ret; +} + +/** + * trace_seq_vprintf - 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, the number of characters printed, or a negative + * value in case of an error. + * * + * 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; + } + + if (ret > 0) + s->len += ret; + + return ret; +} + +/** + * 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_fprintf(struct trace_seq *s, FILE *fp) +{ + TRACE_SEQ_CHECK(s); + + switch (s->state) { + case TRACE_SEQ__GOOD: + return fprintf(fp, "%.*s", s->len, s->buffer); + case TRACE_SEQ__BUFFER_POISONED: + fprintf(fp, "%s\n", "Usage of trace_seq after it was destroyed"); + break; + case TRACE_SEQ__MEM_ALLOC_FAILED: + fprintf(fp, "%s\n", "Can't allocate trace_seq buffer memory"); + break; + } + return -1; +} + +int trace_seq_do_printf(struct trace_seq *s) +{ + return trace_seq_do_fprintf(s, stdout); +} diff --git a/tools/lib/traceevent/trace-seq.h b/tools/lib/traceevent/trace-seq.h new file mode 100644 index 000000000..d68ec69f8 --- /dev/null +++ b/tools/lib/traceevent/trace-seq.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + */ + +#ifndef _TRACE_SEQ_H +#define _TRACE_SEQ_H + +#include <stdarg.h> +#include <stdio.h> + +/* ----------------------- trace_seq ----------------------- */ + +#ifndef TRACE_SEQ_BUF_SIZE +#define TRACE_SEQ_BUF_SIZE 4096 +#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_fprintf(struct trace_seq *s, FILE *fp); +extern int trace_seq_do_printf(struct trace_seq *s); + +#endif /* _TRACE_SEQ_H */ |