Tests: Add callstack contexts tests
authorFrancis Deslauriers <francis.deslauriers@efficios.com>
Thu, 8 Jun 2017 21:15:06 +0000 (17:15 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 11 Jun 2018 19:21:44 +0000 (15:21 -0400)
Tests callstack-user and callstack-kernel contexts by tracing with those
contexts an application that calls multiple functions in chain before
executing a system call.

callstack-user addresses are tested against the output of coreutils'
addr2line.
callstack-kernel addresses are tested against the addresses of kernel
symbols available in the /proc/kallsyms procfile.

Both these tests need to be run by root because those contexts are made
available by the kernel tracer. The callstack-kernel test also need to
access /proc/kallsyms.

Signed-off-by: Francis Deslauriers <francis.deslauriers@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
.gitignore
configure.ac
tests/regression/kernel/test_callstack [new file with mode: 0755]
tests/root_regression
tests/utils/parse-callstack.py [new file with mode: 0755]
tests/utils/testapp/Makefile.am
tests/utils/testapp/gen-syscall-events-callstack/Makefile.am [new file with mode: 0644]
tests/utils/testapp/gen-syscall-events-callstack/gen-syscall-events-callstack.c [new file with mode: 0644]

index 3f766e3a8e9076b2fd60a7eaba611f3daf3a0205..1ad59422bdcd46b5219b91bf7058ee63c8725a19 100644 (file)
@@ -112,6 +112,9 @@ health_check
 /tests/regression/ust/ust-dl/test_ust-dl
 /tests/regression/ust/multi-lib/exec-with-callsites
 /tests/regression/ust/multi-lib/exec-without-callsites
+/tests/utils/testapp/gen-syscall-events-callstack/gen-syscall-events-callstack
+/tests/utils/testapp/gen-ust-events/gen-ust-events
+/tests/utils/testapp/gen-ust-nevents-str/gen-ust-nevents-str
 /tests/utils/testapp/gen-ust-nevents/gen-ust-nevents
 /tests/utils/testapp/gen-ust-tracef/gen-ust-tracef
 /tests/utils/testapp/gen-syscall-events/gen-syscall-events
@@ -120,7 +123,6 @@ health_check
 /tests/perf/find_event
 /tests/perf/test_perf_raw
 /tests/unit/test_string_utils
-/tests/utils/testapp/gen-ust-nevents-str/gen-ust-nevents-str
 
 # man pages
 /doc/man/*.1
index 6a26e49982ffa0286184c9131577d0ee83bef0ad..acae225ad707860220725f6a6632253ec9a2bd7b 100644 (file)
@@ -1152,6 +1152,7 @@ AC_CONFIG_FILES([
        tests/utils/tap/Makefile
        tests/utils/testapp/Makefile
        tests/utils/testapp/gen-ust-events/Makefile
+       tests/utils/testapp/gen-syscall-events-callstack/Makefile
        tests/utils/testapp/gen-ust-nevents/Makefile
        tests/utils/testapp/gen-ust-nevents-str/Makefile
        tests/utils/testapp/gen-syscall-events/Makefile
diff --git a/tests/regression/kernel/test_callstack b/tests/regression/kernel/test_callstack
new file mode 100755 (executable)
index 0000000..6cb20bc
--- /dev/null
@@ -0,0 +1,167 @@
+#!/bin/bash
+#
+# Copyright (C) - 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License, version 2 only, as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+TEST_DESC="Kernel tracer - Callstack context"
+
+CURDIR=$(dirname "$0")/
+TESTDIR=$CURDIR/../..
+NUM_TESTS=11
+TEST_APP_USERSPACE="$TESTDIR/utils/testapp/gen-syscall-events-callstack/gen-syscall-events-callstack"
+TEST_APP_KERNELSPACE="$TESTDIR/utils/testapp/gen-syscall-events/gen-syscall-events"
+PARSE_CALLSTACK="$TESTDIR/utils/parse-callstack.py"
+
+SESSION_NAME="callstack"
+CHANNEL_NAME="chan0"
+
+source "$TESTDIR/utils/utils.sh"
+
+function lttng_untrack_all()
+{
+       lttng_untrack 0 "-s $SESSION_NAME --all --pid -k"
+}
+
+function lttng_track_pid()
+{
+       local PID=$1
+       lttng_track 0 "-s $SESSION_NAME -k --pid=$PID"
+}
+
+function run_workload()
+{
+       local TEST_APP=$1
+       local start_file_sync
+       start_file_sync=$(mktemp -u)
+
+       lttng_untrack_all
+
+       ./"$TEST_APP" "$start_file_sync" &
+       PID=$!
+       lttng_track_pid $PID
+
+       start_lttng_tracing_ok
+
+       # Create start file to launch the execution of the syscall call by the
+       # test app.
+       touch "$start_file_sync"
+
+       wait $PID
+
+       stop_lttng_tracing_ok
+
+       # Clean up the synchronization file.
+       rm -f "$start_file_sync"
+}
+
+function test_user_callstack()
+{
+       TRACE_PATH=$(mktemp -d)
+       # This is the expected userspace callstack. (see gen-syscall-events-callstack.c)
+       USER_CS_EXPECTED="main fct_a fct_b fct_c my_gettid"
+       EVENT_NAME="gettid"
+
+       diag "Userspace callstack test"
+       create_lttng_session_ok $SESSION_NAME "$TRACE_PATH"
+       lttng_enable_kernel_channel_ok "$SESSION_NAME" "$CHANNEL_NAME"
+
+       lttng_enable_kernel_syscall_ok "$SESSION_NAME" "$EVENT_NAME" "$CHANNEL_NAME"
+       add_context_kernel_ok "$SESSION_NAME" "$CHANNEL_NAME" "callstack-user"
+
+       run_workload $TEST_APP_USERSPACE
+
+       destroy_lttng_session_ok "$SESSION_NAME"
+
+       "$BABELTRACE_BIN" "$TRACE_PATH" | grep $EVENT_NAME | ./"$PARSE_CALLSTACK" --user "$TEST_APP_USERSPACE" $USER_CS_EXPECTED
+       ok $? "Validate userspace callstack"
+
+       rm -rf "$TRACE_PATH"
+}
+
+function test_kernel_callstack()
+{
+       TRACE_PATH=$(mktemp -d)
+       # Those are symbol expected to be present in the kernel callstack. This
+       # is not an exhaustive list since it's kernel dependent.
+
+       # FIXME: we used to test for the following symbols as well:
+       # save_stack_trace, lttng_callstack_get_size, but they were removed
+       # because:
+       #       1. kernel commit 77072f09 make it so that save_stack_trace is
+       #        omitted from the callstack itself, and
+       #
+       #       2. the code (of this commit) can trigger Tail Call Optimization
+       #       which mess up with the stacktrace by omiting the wrong address
+       #       from the stacktrace.
+       # When this is fixed, we should add both save_stack_trace and
+       # lttng_callstack_get_size symbols back in the list of expected
+       # addresses.
+       KERNEL_CS_EXPECTED="lttng_event_reserve"
+       EVENT_NAME="read"
+
+       diag "Kernel callstack test"
+       create_lttng_session_ok $SESSION_NAME "$TRACE_PATH"
+       lttng_enable_kernel_channel_ok "$SESSION_NAME" "$CHANNEL_NAME"
+
+       lttng_enable_kernel_syscall_ok "$SESSION_NAME" "$EVENT_NAME" "$CHANNEL_NAME"
+       add_context_kernel_ok "$SESSION_NAME" "$CHANNEL_NAME" "callstack-kernel"
+
+       run_workload $TEST_APP_KERNELSPACE
+
+       destroy_lttng_session_ok "$SESSION_NAME"
+
+       "$BABELTRACE_BIN" "$TRACE_PATH" | grep $EVENT_NAME | ./"$PARSE_CALLSTACK" --kernel $KERNEL_CS_EXPECTED
+       ok $? "Validate kernel callstack"
+
+       rm -rf "$TRACE_PATH"
+}
+
+# Only run userspace callstack test on x86
+uname -m | grep -E "x86" >/dev/null 2>&1
+if test $? == 0; then
+       NUM_TESTS=$((NUM_TESTS+11))
+       RUN_USERSPACE_TEST=1
+else
+       RUN_USERSPACE_TEST=0
+fi
+
+# MUST set TESTDIR before calling those functions
+plan_tests $NUM_TESTS
+
+print_test_banner "$TEST_DESC"
+
+if [ "$(id -u)" == "0" ]; then
+       isroot=1
+else
+       isroot=0
+fi
+
+skip $isroot "Root access is needed. Skipping all tests." "$NUM_TESTS" ||
+{
+       which "$BABELTRACE_BIN" > /dev/null
+       test $? -ne 0
+       skip $? "Babeltrace binary not found. Skipping callstack tests" "$NUM_TESTS" ||
+       {
+               start_lttng_sessiond
+
+               if test $RUN_USERSPACE_TEST == 1; then
+                       test_user_callstack
+               fi
+
+               test_kernel_callstack
+
+               stop_lttng_sessiond
+       }
+}
index f17ac977f82d10d13afb69041fd93e47652d2700..40bb7a59a2345bd0c97ffe9677778d7d47e245f4 100644 (file)
@@ -5,6 +5,7 @@ regression/kernel/test_clock_override
 regression/kernel/test_rotation_destroy_flush
 regression/kernel/test_select_poll_epoll
 regression/kernel/test_lttng_logger
+regression/kernel/test_callstack
 regression/tools/live/test_kernel
 regression/tools/live/test_lttng_kernel
 regression/tools/streaming/test_high_throughput_limits
diff --git a/tests/utils/parse-callstack.py b/tests/utils/parse-callstack.py
new file mode 100755 (executable)
index 0000000..da0bab6
--- /dev/null
@@ -0,0 +1,170 @@
+#! /usr/bin/python3
+
+# Copyright (C) - 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation; version 2.1 of the License.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import sys
+import bisect
+import subprocess
+import re
+
+def addr2line(executable, addr):
+    """
+        Uses binutils' addr2line to get function containing a given address
+    """
+    cmd =['addr2line']
+
+    cmd += ['-e', executable]
+
+    # Print function names
+    cmd += ['--functions']
+
+    # Expand inlined functions
+    cmd += ['--addresses', addr]
+
+    addr2line_output = subprocess.getoutput(' '.join(cmd))
+
+    # Omit the last 2 lines as the caller of main can not be determine
+    fcts = [addr2line_output.split()[-2]]
+
+    fcts = [ f for f in fcts if '??' not in f]
+
+    return fcts
+
+def extract_user_func_names(executable, raw_callstack):
+    """
+        Given a callstack from the Babeltrace CLI output, returns a set
+        containing the name of the functions. This assumes that the binary have
+        not changed since the execution.
+    """
+    recorded_callstack = set()
+
+    # Remove commas and split on spaces
+    for index, addr in enumerate(raw_callstack.replace(',', '').split(' ')):
+        # Consider only the elements starting with '0x' which are the
+        # addresses recorded in the callstack
+        if '0x' in addr[:2]:
+            funcs = addr2line(executable, addr)
+            recorded_callstack.update(funcs)
+
+    return recorded_callstack
+
+def extract_kernel_func_names(raw_callstack):
+    """
+        Given a callstack from the Babeltrace CLI output, returns a set
+        containing the name of the functions.
+        Uses the /proc/kallsyms procfile to find the symbol associated with an
+        address. This function should only be used if the user is root or has
+        access to /proc/kallsyms.
+    """
+    recorded_callstack = set()
+    syms=[]
+    addresses=[]
+    # We read kallsyms file and save the output
+    with open('/proc/kallsyms') as kallsyms_f:
+        for line in kallsyms_f:
+            line_tokens = line.split()
+            addr = line_tokens[0]
+            symbol = line_tokens[2]
+            addresses.append(int(addr, 16))
+            syms.append({'addr':int(addr, 16), 'symbol':symbol})
+
+    # Save the address and symbol in a sorted list of tupple
+    syms = sorted(syms, key=lambda k:k['addr'])
+    # We save the list of addresses in a seperate sorted list to easily bisect
+    # the closer address of a symbol.
+    addresses = sorted(addresses)
+
+    # Remove commas and split on spaces
+    for addr in raw_callstack.replace(',', '').split(' '):
+        if '0x' in addr[:2]:
+            # Search the location of the address in the addresses list and
+            # deference this location in the syms list to add the associated
+            # symbol.
+            loc = bisect.bisect(addresses, int(addr, 16))
+            recorded_callstack.add(syms[loc-1]['symbol'])
+
+    return recorded_callstack
+
+# Regex capturing the callstack_user and callstack_kernel context
+user_cs_rexp='.*callstack_user\ \=\ \[(.*)\]\ .*\}, \{.*\}'
+kernel_cs_rexp='.*callstack_kernel\ \=\ \[(.*)\]\ .*\}, \{.*\}'
+
+def main():
+    """
+        Reads a line from stdin and expect it to be a wellformed Babeltrace CLI
+        output containing containing a callstack context of the domain passed
+        as argument.
+    """
+    expected_callstack = set()
+    recorded_callstack = set()
+    cs_type=None
+
+    if len(sys.argv) <= 2:
+        print(sys.argv)
+        raise ValueError('USAGE: ./{} (--kernel|--user EXE) FUNC-NAMES'.format(sys.argv[0]))
+
+    # If the `--user` option is passed, save the next argument as the path
+    # to the executable
+    argc=1
+    executable=None
+    if sys.argv[argc] in '--kernel':
+        rexp = kernel_cs_rexp
+        cs_type='kernel'
+    elif sys.argv[argc] in '--user':
+        rexp = user_cs_rexp
+        cs_type='user'
+        argc+=1
+        executable = sys.argv[argc]
+    else:
+        raise Exception('Unknown domain')
+
+    argc+=1
+
+    # Extract the function names that are expected to be found call stack of
+    # the current events
+    for func in sys.argv[argc:]:
+        expected_callstack.add(func)
+
+    # Read the tested line for STDIN
+    event_line = None
+    for line in sys.stdin:
+        event_line = line
+        break
+
+    # Extract the userspace callstack context of the event
+    m = re.match(rexp, event_line)
+
+    # If there is no match, exit with error
+    if m is None:
+        raise re.error('Callstack not found in event line')
+    else:
+        raw_callstack = str(m.group(1))
+        if cs_type in 'user':
+            recorded_callstack=extract_user_func_names(executable, raw_callstack)
+        elif cs_type in 'kernel':
+            recorded_callstack=extract_kernel_func_names(raw_callstack)
+        else:
+            raise Exception('Unknown domain')
+
+    # Verify that all expected function are present in the callstack
+    for e in expected_callstack:
+        if e not in recorded_callstack:
+            raise Exception('Expected function name not found in recorded callstack')
+
+    sys.exit(0)
+
+if __name__ == '__main__':
+    main()
index 596591265c1799567c5e95a44f2e737169582e00..9bfe53666b260d8c6d252a652353b10e31e7213a 100644 (file)
@@ -1,3 +1,11 @@
-SUBDIRS = gen-ust-events gen-ust-nevents gen-ust-nevents-str gen-ust-tracef gen-syscall-events
+SUBDIRS = gen-ust-events \
+         gen-ust-nevents \
+         gen-ust-nevents-str \
+         gen-ust-tracef \
+         gen-syscall-events
+
+if HAVE_MODULES_USERSPACE_CALLSTACK_CONTEXT
+SUBDIRS += gen-syscall-events-callstack
+endif # HAVE_MODULES_USERSPACE_CALLSTACK_CONTEXT
 
 noinst_HEADERS = signal-helper.h
diff --git a/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am b/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am
new file mode 100644 (file)
index 0000000..6e075a0
--- /dev/null
@@ -0,0 +1,6 @@
+AM_CFLAGS += -I$(top_srcdir)/tests/utils/
+AM_CFLAGS += -fno-omit-frame-pointer -no-pie
+
+noinst_PROGRAMS = gen-syscall-events-callstack
+gen_syscall_events_callstack_SOURCES = gen-syscall-events-callstack.c
+gen_syscall_events_callstack_LDADD = $(top_builddir)/tests/utils/libtestutils.la
diff --git a/tests/utils/testapp/gen-syscall-events-callstack/gen-syscall-events-callstack.c b/tests/utils/testapp/gen-syscall-events-callstack/gen-syscall-events-callstack.c
new file mode 100644 (file)
index 0000000..48210fa
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) - 2017 Francis Deslauriers <francis.deslauriers@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+/**
+ * The process waits for the creation of a file passed as argument from an
+ * external processes to execute a syscall and exiting. This is useful for tests
+ * in combinaison with LTTng's PID tracker feature where we can trace the kernel
+ * events generated by our test process only.
+ */
+
+volatile int val = 0;
+
+long __attribute__ ((noinline))
+my_gettid(void)
+{
+    long ret;
+#ifdef __x86_64
+    asm volatile
+    (
+        "syscall"
+        : "=a" (ret)
+        : "0"(__NR_gettid)
+        : "cc", "rcx", "r11", "memory"
+    );
+#elif __i386
+    asm volatile
+    (
+        "int $0x80"
+        : "=a" (ret)
+        : "0"(__NR_gettid)
+        : "cc", "edi", "esi", "memory"
+    );
+#else
+#error "Userspace callstack test not supported for this architecture."
+#endif
+    return ret;
+}
+
+int __attribute__ ((noinline))
+fct_c(void)
+{
+       return my_gettid();
+}
+
+int __attribute__ ((noinline))
+fct_b(void)
+{
+       val += fct_c();
+       return val;
+}
+
+int __attribute__ ((noinline))
+fct_a(void)
+{
+       val += fct_b();
+       return val;
+}
+
+int main(int argc, char **argv)
+{
+       int ret = 0;
+       char *start_file;
+
+       if (argc != 2) {
+               fprintf(stderr, "Error: Missing argument\n");
+               fprintf(stderr, "USAGE: %s PATH_WAIT_FILE\n", argv[0]);
+               ret = -1;
+               goto error;
+       }
+
+       start_file = argv[1];
+
+       /*
+        * Wait for the start_file to be created by an external process
+        * (typically the test script) before executing the syscall
+        */
+       ret = wait_on_file(start_file);
+       if (ret != 0) {
+               goto error;
+       }
+
+       /* Start the callchain to the syscall */
+       ret = fct_a();
+
+       /* Return success */
+       if (ret >= 0) {
+               ret = 0;
+       }
+
+error:
+       return ret;
+}
This page took 0.04695 seconds and 4 git commands to generate.