tracing: Automatic syscall tracing

When generating syscall wrappers, call a tracing macro with the id,
name, and all parameters of the syscall as params when entering and
leaving the syscall. This can be disabled in certain call sites
by defining DISABLE_SYSCALL_TRACING which is useful for certain
tracing implementations which require syscalls themselves to work.

Notably some syscalls *cannot* be automatically traced this way and
headers where exclusions are set are in the gen_syscall.py as notracing.

Includes a systemview and test format implementation.

Tested with systemview, usb, and uart backends with the string
formatter using the tracing sample app.

Debugging the trace wrapper can be aided by setting the TRACE_DIAGNOSTIC
env var and rebuilding from scratch, a warning is issued for every
instance a syscall is traced.

Automatically generating a name mapping for SYSVIEW_Zephyr.txt is a
future item as is documenting how to capture and use the tracing data
generated.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
Tom Burdick 2021-10-13 12:16:39 -05:00 committed by Anas Nashif
commit 97dc88bb6d
15 changed files with 533 additions and 172 deletions

View file

@ -29,6 +29,12 @@ import argparse
import os
import json
# Some kernel headers cannot include automated tracing without causing unintended recursion or
# other serious issues.
# These headers typically already have very specific tracing hooks for all relevant things
# written by hand so are excluded.
notracing = ["kernel.h", "errno_private.h"]
types64 = ["int64_t", "uint64_t"]
# The kernel linkage is complicated. These functions from
@ -73,7 +79,9 @@ list_template = """
syscall_template = """
/* auto-generated by gen_syscalls.py, don't edit */
%s
{include_guard}
{tracing_include}
#ifndef _ASMLANGUAGE
@ -82,6 +90,7 @@ syscall_template = """
#include <linker/sections.h>
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
@ -94,13 +103,13 @@ syscall_template = """
#endif
#ifdef __cplusplus
extern "C" {
extern "C" {{
#endif
%s
{invocations}
#ifdef __cplusplus
}
}}
#endif
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
@ -122,6 +131,36 @@ uintptr_t %s(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3,
uintptr_t arg4, uintptr_t arg5, uintptr_t arg6, void *ssf);
"""
# defines a macro wrapper which supercedes the syscall when used
# and provides tracing enter/exit hooks while allowing per compilation unit
# enable/disable of syscall tracing. Used for returning functions
# Note that the last argument to the exit macro is the return value.
syscall_tracer_with_return_template = """
#ifndef DISABLE_SYSCALL_TRACING
{trace_diagnostic}
#define {func_name}({argnames}) ({{ \
{func_type} retval; \
sys_port_trace_syscall_enter({syscall_id}, {func_name}{trace_argnames}); \
retval = {func_name}({argnames}); \
sys_port_trace_syscall_exit({syscall_id}, {func_name}{trace_argnames}, retval); \
retval; \
}})
#endif
"""
# defines a macro wrapper which supercedes the syscall when used
# and provides tracing enter/exit hooks while allowing per compilation unit
# enable/disable of syscall tracing. Used for non-returning (void) functions
syscall_tracer_void_template = """
#ifndef DISABLE_SYSCALL_TRACING
{trace_diagnostic}
#define {func_name}({argnames}) do {{ \
sys_port_trace_syscall_enter({syscall_id}, {func_name}{trace_argnames}); \
{func_name}({argnames}); \
sys_port_trace_syscall_exit({syscall_id}, {func_name}{trace_argnames}); \
}} while(false)
#endif
"""
typename_regex = re.compile(r'(.*?)([A-Za-z0-9_]+)$')
@ -156,7 +195,7 @@ def need_split(argtype):
def union_decl(type):
return "union { struct { uintptr_t lo, hi; } split; %s val; }" % type
def wrapper_defs(func_name, func_type, args):
def wrapper_defs(func_name, func_type, args, fn):
ret64 = need_split(func_type)
mrsh_args = [] # List of rvalue expressions for the marshalled invocation
split_args = []
@ -174,6 +213,7 @@ def wrapper_defs(func_name, func_type, args):
mrsh_args.append("(uintptr_t)&ret64")
decl_arglist = ", ".join([" ".join(argrec) for argrec in args]) or "void"
syscall_id = "K_SYSCALL_" + func_name.upper()
wrap = "extern %s z_impl_%s(%s);\n" % (func_type, func_name, decl_arglist)
wrap += "\n"
@ -195,7 +235,6 @@ def wrapper_defs(func_name, func_type, args):
wrap += "\t\t" + "};\n"
mrsh_args[5:] = ["(uintptr_t) &more"]
syscall_id = "K_SYSCALL_" + func_name.upper()
invoke = ("arch_syscall_invoke%d(%s)"
% (len(mrsh_args),
", ".join(mrsh_args + [syscall_id])))
@ -230,6 +269,23 @@ def wrapper_defs(func_name, func_type, args):
wrap += "}\n"
if fn not in notracing:
argnames = ", ".join([f"{argname}" for _, argname in args])
trace_argnames = ""
if len(args) > 0:
trace_argnames = ", " + argnames
trace_diagnostic = ""
if os.getenv('TRACE_DIAGNOSTICS'):
trace_diagnostic = f"#warning Tracing {func_name}"
if func_type != "void":
wrap += syscall_tracer_with_return_template.format(func_type=func_type, func_name=func_name,
argnames=argnames, trace_argnames=trace_argnames,
syscall_id=syscall_id, trace_diagnostic=trace_diagnostic)
else:
wrap += syscall_tracer_void_template.format(func_type=func_type, func_name=func_name,
argnames=argnames, trace_argnames=trace_argnames,
syscall_id=syscall_id, trace_diagnostic=trace_diagnostic)
return wrap
# Returns an expression for the specified (zero-indexed!) marshalled
@ -316,7 +372,7 @@ def marshall_defs(func_name, func_type, args):
return mrsh, mrsh_name
def analyze_fn(match_group):
def analyze_fn(match_group, fn):
func, args = match_group
try:
@ -334,7 +390,7 @@ def analyze_fn(match_group):
marshaller = None
marshaller, handler = marshall_defs(func_name, func_type, args)
invocation = wrapper_defs(func_name, func_type, args)
invocation = wrapper_defs(func_name, func_type, args, fn)
# Entry in _k_syscall_table
table_entry = "[%s] = %s" % (sys_id, handler)
@ -380,7 +436,7 @@ def main():
handlers = []
for match_group, fn in syscalls:
handler, inv, mrsh, sys_id, entry = analyze_fn(match_group)
handler, inv, mrsh, sys_id, entry = analyze_fn(match_group, fn)
if fn not in invocations:
invocations[fn] = []
@ -426,7 +482,10 @@ def main():
ig = re.sub("[^a-zA-Z0-9]", "_", "Z_INCLUDE_SYSCALLS_" + fn).upper()
include_guard = "#ifndef %s\n#define %s\n" % (ig, ig)
header = syscall_template % (include_guard, "\n\n".join(invo_list))
tracing_include = ""
if fn not in notracing:
tracing_include = "#include <tracing/tracing_syscall.h>"
header = syscall_template.format(include_guard=include_guard, tracing_include=tracing_include, invocations="\n\n".join(invo_list))
with open(out_fn, "w") as fp:
fp.write(header)