Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
1
tools/testing/kunit/.gitattributes
vendored
1
tools/testing/kunit/.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
test_data/* binary
|
||||
@@ -11,7 +11,6 @@ import argparse
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
|
||||
from collections import namedtuple
|
||||
from enum import Enum, auto
|
||||
@@ -44,11 +43,6 @@ class KunitStatus(Enum):
|
||||
BUILD_FAILURE = auto()
|
||||
TEST_FAILURE = auto()
|
||||
|
||||
def create_default_kunitconfig():
|
||||
if not os.path.exists(kunit_kernel.kunitconfig_path):
|
||||
shutil.copyfile('arch/um/configs/kunit_defconfig',
|
||||
kunit_kernel.kunitconfig_path)
|
||||
|
||||
def get_kernel_root_path():
|
||||
parts = sys.argv[0] if not __file__ else __file__
|
||||
parts = os.path.realpath(parts).split('tools/testing/kunit')
|
||||
@@ -61,7 +55,6 @@ def config_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
kunit_parser.print_with_timestamp('Configuring KUnit Kernel ...')
|
||||
|
||||
config_start = time.time()
|
||||
create_default_kunitconfig()
|
||||
success = linux.build_reconfig(request.build_dir, request.make_options)
|
||||
config_end = time.time()
|
||||
if not success:
|
||||
@@ -262,12 +255,12 @@ def main(argv, linux=None):
|
||||
if not os.path.exists(cli_args.build_dir):
|
||||
os.mkdir(cli_args.build_dir)
|
||||
|
||||
if not os.path.exists(kunit_kernel.kunitconfig_path):
|
||||
create_default_kunitconfig()
|
||||
|
||||
if not linux:
|
||||
linux = kunit_kernel.LinuxSourceTree()
|
||||
|
||||
linux.create_kunitconfig(cli_args.build_dir)
|
||||
linux.read_kunitconfig(cli_args.build_dir)
|
||||
|
||||
request = KunitRequest(cli_args.raw_output,
|
||||
cli_args.timeout,
|
||||
cli_args.jobs,
|
||||
@@ -283,12 +276,12 @@ def main(argv, linux=None):
|
||||
not os.path.exists(cli_args.build_dir)):
|
||||
os.mkdir(cli_args.build_dir)
|
||||
|
||||
if not os.path.exists(kunit_kernel.kunitconfig_path):
|
||||
create_default_kunitconfig()
|
||||
|
||||
if not linux:
|
||||
linux = kunit_kernel.LinuxSourceTree()
|
||||
|
||||
linux.create_kunitconfig(cli_args.build_dir)
|
||||
linux.read_kunitconfig(cli_args.build_dir)
|
||||
|
||||
request = KunitConfigRequest(cli_args.build_dir,
|
||||
cli_args.make_options)
|
||||
result = config_tests(linux, request)
|
||||
@@ -301,6 +294,9 @@ def main(argv, linux=None):
|
||||
if not linux:
|
||||
linux = kunit_kernel.LinuxSourceTree()
|
||||
|
||||
linux.create_kunitconfig(cli_args.build_dir)
|
||||
linux.read_kunitconfig(cli_args.build_dir)
|
||||
|
||||
request = KunitBuildRequest(cli_args.jobs,
|
||||
cli_args.build_dir,
|
||||
cli_args.alltests,
|
||||
@@ -315,6 +311,9 @@ def main(argv, linux=None):
|
||||
if not linux:
|
||||
linux = kunit_kernel.LinuxSourceTree()
|
||||
|
||||
linux.create_kunitconfig(cli_args.build_dir)
|
||||
linux.read_kunitconfig(cli_args.build_dir)
|
||||
|
||||
exec_request = KunitExecRequest(cli_args.timeout,
|
||||
cli_args.build_dir,
|
||||
cli_args.alltests)
|
||||
@@ -337,7 +336,7 @@ def main(argv, linux=None):
|
||||
kunit_output = f.read().splitlines()
|
||||
request = KunitParseRequest(cli_args.raw_output,
|
||||
kunit_output,
|
||||
cli_args.build_dir,
|
||||
None,
|
||||
cli_args.json)
|
||||
result = parse_tests(request)
|
||||
if result.status != KunitStatus.SUCCESS:
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
# Author: Felix Guo <felixguoxiuping@gmail.com>
|
||||
# Author: Brendan Higgins <brendanhiggins@google.com>
|
||||
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
|
||||
from contextlib import ExitStack
|
||||
@@ -18,8 +18,10 @@ import kunit_config
|
||||
import kunit_parser
|
||||
|
||||
KCONFIG_PATH = '.config'
|
||||
kunitconfig_path = '.kunitconfig'
|
||||
KUNITCONFIG_PATH = '.kunitconfig'
|
||||
DEFAULT_KUNITCONFIG_PATH = 'arch/um/configs/kunit_defconfig'
|
||||
BROKEN_ALLCONFIG_PATH = 'tools/testing/kunit/configs/broken_on_uml.config'
|
||||
OUTFILE_PATH = 'test.log'
|
||||
|
||||
class ConfigError(Exception):
|
||||
"""Represents an error trying to configure the Linux kernel."""
|
||||
@@ -82,36 +84,51 @@ class LinuxSourceTreeOperations(object):
|
||||
if build_dir:
|
||||
command += ['O=' + build_dir]
|
||||
try:
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
proc = subprocess.Popen(command,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.DEVNULL)
|
||||
except OSError as e:
|
||||
raise BuildError('Could not call execute make: ' + str(e))
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise BuildError(e.output.decode())
|
||||
raise BuildError('Could not call make command: ' + str(e))
|
||||
_, stderr = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise BuildError(stderr.decode())
|
||||
if stderr: # likely only due to build warnings
|
||||
print(stderr.decode())
|
||||
|
||||
def linux_bin(self, params, timeout, build_dir, outfile):
|
||||
def linux_bin(self, params, timeout, build_dir):
|
||||
"""Runs the Linux UML binary. Must be named 'linux'."""
|
||||
linux_bin = './linux'
|
||||
if build_dir:
|
||||
linux_bin = os.path.join(build_dir, 'linux')
|
||||
outfile = get_outfile_path(build_dir)
|
||||
with open(outfile, 'w') as output:
|
||||
process = subprocess.Popen([linux_bin] + params,
|
||||
stdout=output,
|
||||
stderr=subprocess.STDOUT)
|
||||
process.wait(timeout)
|
||||
|
||||
|
||||
def get_kconfig_path(build_dir):
|
||||
kconfig_path = KCONFIG_PATH
|
||||
if build_dir:
|
||||
kconfig_path = os.path.join(build_dir, KCONFIG_PATH)
|
||||
return kconfig_path
|
||||
|
||||
def get_kunitconfig_path(build_dir):
|
||||
kunitconfig_path = KUNITCONFIG_PATH
|
||||
if build_dir:
|
||||
kunitconfig_path = os.path.join(build_dir, KUNITCONFIG_PATH)
|
||||
return kunitconfig_path
|
||||
|
||||
def get_outfile_path(build_dir):
|
||||
outfile_path = OUTFILE_PATH
|
||||
if build_dir:
|
||||
outfile_path = os.path.join(build_dir, OUTFILE_PATH)
|
||||
return outfile_path
|
||||
|
||||
class LinuxSourceTree(object):
|
||||
"""Represents a Linux kernel source tree with KUnit tests."""
|
||||
|
||||
def __init__(self):
|
||||
self._kconfig = kunit_config.Kconfig()
|
||||
self._kconfig.read_from_file(kunitconfig_path)
|
||||
self._ops = LinuxSourceTreeOperations()
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
|
||||
@@ -123,6 +140,16 @@ class LinuxSourceTree(object):
|
||||
return False
|
||||
return True
|
||||
|
||||
def create_kunitconfig(self, build_dir, defconfig=DEFAULT_KUNITCONFIG_PATH):
|
||||
kunitconfig_path = get_kunitconfig_path(build_dir)
|
||||
if not os.path.exists(kunitconfig_path):
|
||||
shutil.copyfile(defconfig, kunitconfig_path)
|
||||
|
||||
def read_kunitconfig(self, build_dir):
|
||||
kunitconfig_path = get_kunitconfig_path(build_dir)
|
||||
self._kconfig = kunit_config.Kconfig()
|
||||
self._kconfig.read_from_file(kunitconfig_path)
|
||||
|
||||
def validate_config(self, build_dir):
|
||||
kconfig_path = get_kconfig_path(build_dir)
|
||||
validated_kconfig = kunit_config.Kconfig()
|
||||
@@ -178,8 +205,8 @@ class LinuxSourceTree(object):
|
||||
|
||||
def run_kernel(self, args=[], build_dir='', timeout=None):
|
||||
args.extend(['mem=1G'])
|
||||
outfile = 'test.log'
|
||||
self._ops.linux_bin(args, timeout, build_dir, outfile)
|
||||
self._ops.linux_bin(args, timeout, build_dir)
|
||||
outfile = get_outfile_path(build_dir)
|
||||
subprocess.call(['stty', 'sane'])
|
||||
with open(outfile, 'r') as file:
|
||||
for line in file:
|
||||
|
||||
@@ -12,7 +12,7 @@ from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from enum import Enum, auto
|
||||
from functools import reduce
|
||||
from typing import List
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
TestResult = namedtuple('TestResult', ['status','suites','log'])
|
||||
|
||||
@@ -54,6 +54,7 @@ kunit_end_re = re.compile('(List of all partitions:|'
|
||||
def isolate_kunit_output(kernel_output):
|
||||
started = False
|
||||
for line in kernel_output:
|
||||
line = line.rstrip() # line always has a trailing \n
|
||||
if kunit_start_re.search(line):
|
||||
prefix_len = len(line.split('TAP version')[0])
|
||||
started = True
|
||||
@@ -65,7 +66,7 @@ def isolate_kunit_output(kernel_output):
|
||||
|
||||
def raw_output(kernel_output):
|
||||
for line in kernel_output:
|
||||
print(line)
|
||||
print(line.rstrip())
|
||||
|
||||
DIVIDER = '=' * 60
|
||||
|
||||
@@ -151,7 +152,7 @@ def parse_diagnostic(lines: List[str], test_case: TestCase) -> bool:
|
||||
else:
|
||||
return False
|
||||
|
||||
def parse_test_case(lines: List[str]) -> TestCase:
|
||||
def parse_test_case(lines: List[str]) -> Optional[TestCase]:
|
||||
test_case = TestCase()
|
||||
save_non_diagnositic(lines, test_case)
|
||||
while parse_diagnostic(lines, test_case):
|
||||
@@ -163,7 +164,7 @@ def parse_test_case(lines: List[str]) -> TestCase:
|
||||
|
||||
SUBTEST_HEADER = re.compile(r'^[\s]+# Subtest: (.*)$')
|
||||
|
||||
def parse_subtest_header(lines: List[str]) -> str:
|
||||
def parse_subtest_header(lines: List[str]) -> Optional[str]:
|
||||
consume_non_diagnositic(lines)
|
||||
if not lines:
|
||||
return None
|
||||
@@ -176,7 +177,7 @@ def parse_subtest_header(lines: List[str]) -> str:
|
||||
|
||||
SUBTEST_PLAN = re.compile(r'[\s]+[0-9]+\.\.([0-9]+)')
|
||||
|
||||
def parse_subtest_plan(lines: List[str]) -> int:
|
||||
def parse_subtest_plan(lines: List[str]) -> Optional[int]:
|
||||
consume_non_diagnositic(lines)
|
||||
match = SUBTEST_PLAN.match(lines[0])
|
||||
if match:
|
||||
@@ -230,7 +231,7 @@ def bubble_up_test_case_errors(test_suite: TestSuite) -> TestStatus:
|
||||
max_test_case_status = bubble_up_errors(lambda x: x.status, test_suite.cases)
|
||||
return max_status(max_test_case_status, test_suite.status)
|
||||
|
||||
def parse_test_suite(lines: List[str], expected_suite_index: int) -> TestSuite:
|
||||
def parse_test_suite(lines: List[str], expected_suite_index: int) -> Optional[TestSuite]:
|
||||
if not lines:
|
||||
return None
|
||||
consume_non_diagnositic(lines)
|
||||
@@ -271,7 +272,7 @@ def parse_tap_header(lines: List[str]) -> bool:
|
||||
|
||||
TEST_PLAN = re.compile(r'[0-9]+\.\.([0-9]+)')
|
||||
|
||||
def parse_test_plan(lines: List[str]) -> int:
|
||||
def parse_test_plan(lines: List[str]) -> Optional[int]:
|
||||
consume_non_diagnositic(lines)
|
||||
match = TEST_PLAN.match(lines[0])
|
||||
if match:
|
||||
@@ -310,7 +311,7 @@ def parse_test_result(lines: List[str]) -> TestResult:
|
||||
else:
|
||||
return TestResult(TestStatus.NO_TESTS, [], lines)
|
||||
|
||||
def print_and_count_results(test_result: TestResult) -> None:
|
||||
def print_and_count_results(test_result: TestResult) -> Tuple[int, int, int]:
|
||||
total_tests = 0
|
||||
failed_tests = 0
|
||||
crashed_tests = 0
|
||||
|
||||
@@ -102,7 +102,7 @@ class KUnitParserTest(unittest.TestCase):
|
||||
'test_data/test_output_isolated_correctly.log')
|
||||
file = open(log_path)
|
||||
result = kunit_parser.isolate_kunit_output(file.readlines())
|
||||
self.assertContains('TAP version 14\n', result)
|
||||
self.assertContains('TAP version 14', result)
|
||||
self.assertContains(' # Subtest: example', result)
|
||||
self.assertContains(' 1..2', result)
|
||||
self.assertContains(' ok 1 - example_simple_test', result)
|
||||
@@ -115,7 +115,7 @@ class KUnitParserTest(unittest.TestCase):
|
||||
'test_data/test_pound_sign.log')
|
||||
with open(log_path) as file:
|
||||
result = kunit_parser.isolate_kunit_output(file.readlines())
|
||||
self.assertContains('TAP version 14\n', result)
|
||||
self.assertContains('TAP version 14', result)
|
||||
self.assertContains(' # Subtest: kunit-resource-test', result)
|
||||
self.assertContains(' 1..5', result)
|
||||
self.assertContains(' ok 1 - kunit_resource_test_init_resources', result)
|
||||
|
||||
@@ -33,6 +33,7 @@ typedef unsigned long dma_addr_t;
|
||||
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
|
||||
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
|
||||
#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
|
||||
#define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a))
|
||||
|
||||
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@ int main(void)
|
||||
{
|
||||
const unsigned int sgmax = SCATTERLIST_MAX_SEGMENT;
|
||||
struct test *test, tests[] = {
|
||||
{ -EINVAL, 1, pfn(0), PAGE_SIZE, PAGE_SIZE + 1, 1 },
|
||||
{ -EINVAL, 1, pfn(0), PAGE_SIZE, 0, 1 },
|
||||
{ -EINVAL, 1, pfn(0), PAGE_SIZE, sgmax + 1, 1 },
|
||||
{ 0, 1, pfn(0), PAGE_SIZE, PAGE_SIZE + 1, 1 },
|
||||
{ 0, 1, pfn(0), PAGE_SIZE, sgmax + 1, 1 },
|
||||
{ 0, 1, pfn(0), PAGE_SIZE, sgmax, 1 },
|
||||
{ 0, 1, pfn(0), 1, sgmax, 1 },
|
||||
{ 0, 2, pfn(0, 1), 2 * PAGE_SIZE, sgmax, 1 },
|
||||
|
||||
71
tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
Normal file
71
tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <test_progs.h>
|
||||
#include "test_probe_read_user_str.skel.h"
|
||||
|
||||
static const char str1[] = "mestring";
|
||||
static const char str2[] = "mestringalittlebigger";
|
||||
static const char str3[] = "mestringblubblubblubblubblub";
|
||||
|
||||
static int test_one_str(struct test_probe_read_user_str *skel, const char *str,
|
||||
size_t len)
|
||||
{
|
||||
int err, duration = 0;
|
||||
char buf[256];
|
||||
|
||||
/* Ensure bytes after string are ones */
|
||||
memset(buf, 1, sizeof(buf));
|
||||
memcpy(buf, str, len);
|
||||
|
||||
/* Give prog our userspace pointer */
|
||||
skel->bss->user_ptr = buf;
|
||||
|
||||
/* Trigger tracepoint */
|
||||
usleep(1);
|
||||
|
||||
/* Did helper fail? */
|
||||
if (CHECK(skel->bss->ret < 0, "prog_ret", "prog returned: %ld\n",
|
||||
skel->bss->ret))
|
||||
return 1;
|
||||
|
||||
/* Check that string was copied correctly */
|
||||
err = memcmp(skel->bss->buf, str, len);
|
||||
if (CHECK(err, "memcmp", "prog copied wrong string"))
|
||||
return 1;
|
||||
|
||||
/* Now check that no extra trailing bytes were copied */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
err = memcmp(skel->bss->buf + len, buf, sizeof(buf) - len);
|
||||
if (CHECK(err, "memcmp", "trailing bytes were not stripped"))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_probe_read_user_str(void)
|
||||
{
|
||||
struct test_probe_read_user_str *skel;
|
||||
int err, duration = 0;
|
||||
|
||||
skel = test_probe_read_user_str__open_and_load();
|
||||
if (CHECK(!skel, "test_probe_read_user_str__open_and_load",
|
||||
"skeleton open and load failed\n"))
|
||||
return;
|
||||
|
||||
/* Give pid to bpf prog so it doesn't read from anyone else */
|
||||
skel->bss->pid = getpid();
|
||||
|
||||
err = test_probe_read_user_str__attach(skel);
|
||||
if (CHECK(err, "test_probe_read_user_str__attach",
|
||||
"skeleton attach failed: %d\n", err))
|
||||
goto out;
|
||||
|
||||
if (test_one_str(skel, str1, sizeof(str1)))
|
||||
goto out;
|
||||
if (test_one_str(skel, str2, sizeof(str2)))
|
||||
goto out;
|
||||
if (test_one_str(skel, str3, sizeof(str3)))
|
||||
goto out;
|
||||
|
||||
out:
|
||||
test_probe_read_user_str__destroy(skel);
|
||||
}
|
||||
@@ -138,7 +138,8 @@ static int run_getsockopt_test(struct bpf_object *obj, int cg_parent,
|
||||
*/
|
||||
|
||||
buf = 0x40;
|
||||
if (setsockopt(sock_fd, SOL_IP, IP_TOS, &buf, 1) < 0) {
|
||||
err = setsockopt(sock_fd, SOL_IP, IP_TOS, &buf, 1);
|
||||
if (err < 0) {
|
||||
log_err("Failed to call setsockopt(IP_TOS)");
|
||||
goto detach;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <test_progs.h>
|
||||
#include <time.h>
|
||||
#include "test_subprogs.skel.h"
|
||||
#include "test_subprogs_unused.skel.h"
|
||||
|
||||
static int duration;
|
||||
|
||||
void test_subprogs(void)
|
||||
{
|
||||
struct test_subprogs *skel;
|
||||
struct test_subprogs_unused *skel2;
|
||||
int err;
|
||||
|
||||
skel = test_subprogs__open_and_load();
|
||||
@@ -26,6 +28,10 @@ void test_subprogs(void)
|
||||
CHECK(skel->bss->res3 != 19, "res3", "got %d, exp %d\n", skel->bss->res3, 19);
|
||||
CHECK(skel->bss->res4 != 36, "res4", "got %d, exp %d\n", skel->bss->res4, 36);
|
||||
|
||||
skel2 = test_subprogs_unused__open_and_load();
|
||||
ASSERT_OK_PTR(skel2, "unused_progs_skel");
|
||||
test_subprogs_unused__destroy(skel2);
|
||||
|
||||
cleanup:
|
||||
test_subprogs__destroy(skel);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ void test_test_global_funcs(void)
|
||||
{ "test_global_func5.o" , "expected pointer to ctx, but got PTR" },
|
||||
{ "test_global_func6.o" , "modified ctx ptr R2" },
|
||||
{ "test_global_func7.o" , "foo() doesn't return scalar" },
|
||||
{ "test_global_func8.o" },
|
||||
};
|
||||
libbpf_print_fn_t old_print_fn = NULL;
|
||||
int err, i, duration = 0;
|
||||
|
||||
19
tools/testing/selftests/bpf/progs/test_global_func8.c
Normal file
19
tools/testing/selftests/bpf/progs/test_global_func8.c
Normal file
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
#include <stddef.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
__noinline int foo(struct __sk_buff *skb)
|
||||
{
|
||||
return bpf_get_prandom_u32();
|
||||
}
|
||||
|
||||
SEC("cgroup_skb/ingress")
|
||||
int test_cls(struct __sk_buff *skb)
|
||||
{
|
||||
if (!foo(skb))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
25
tools/testing/selftests/bpf/progs/test_probe_read_user_str.c
Normal file
25
tools/testing/selftests/bpf/progs/test_probe_read_user_str.c
Normal file
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
pid_t pid = 0;
|
||||
long ret = 0;
|
||||
void *user_ptr = 0;
|
||||
char buf[256] = {};
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_nanosleep")
|
||||
int on_write(void *ctx)
|
||||
{
|
||||
if (pid != (bpf_get_current_pid_tgid() >> 32))
|
||||
return 0;
|
||||
|
||||
ret = bpf_probe_read_user_str(buf, sizeof(buf), user_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
21
tools/testing/selftests/bpf/progs/test_subprogs_unused.c
Normal file
21
tools/testing/selftests/bpf/progs/test_subprogs_unused.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
const char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
__attribute__((unused)) __noinline int unused1(int x)
|
||||
{
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) __noinline int unused2(int x)
|
||||
{
|
||||
return x + 2;
|
||||
}
|
||||
|
||||
SEC("raw_tp/sys_enter")
|
||||
int main_prog(void *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -42,6 +42,11 @@ int perf_event_enable(int fd);
|
||||
int perf_event_disable(int fd);
|
||||
int perf_event_reset(int fd);
|
||||
|
||||
struct perf_event_read {
|
||||
__u64 nr;
|
||||
__u64 l1d_misses;
|
||||
};
|
||||
|
||||
#if !defined(__GLIBC_PREREQ) || !__GLIBC_PREREQ(2, 30)
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
rfi_flush
|
||||
entry_flush
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
TEST_GEN_PROGS := rfi_flush spectre_v2
|
||||
TEST_GEN_PROGS := rfi_flush entry_flush spectre_v2
|
||||
top_srcdir = ../../../../..
|
||||
|
||||
CFLAGS += -I../../../../../usr/include
|
||||
@@ -11,3 +11,5 @@ $(TEST_GEN_PROGS): ../harness.c ../utils.c
|
||||
|
||||
$(OUTPUT)/spectre_v2: CFLAGS += -m64
|
||||
$(OUTPUT)/spectre_v2: ../pmu/event.c branch_loops.S
|
||||
$(OUTPUT)/rfi_flush: flush_utils.c
|
||||
$(OUTPUT)/entry_flush: flush_utils.c
|
||||
|
||||
139
tools/testing/selftests/powerpc/security/entry_flush.c
Normal file
139
tools/testing/selftests/powerpc/security/entry_flush.c
Normal file
@@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
int entry_flush_test(void)
|
||||
{
|
||||
char *p;
|
||||
int repetitions = 10;
|
||||
int fd, passes = 0, iter, rc = 0;
|
||||
struct perf_event_read v;
|
||||
__u64 l1d_misses_total = 0;
|
||||
unsigned long iterations = 100000, zero_size = 24 * 1024;
|
||||
unsigned long l1d_misses_expected;
|
||||
int rfi_flush_orig;
|
||||
int entry_flush, entry_flush_orig;
|
||||
|
||||
SKIP_IF(geteuid() != 0);
|
||||
|
||||
// The PMU event we use only works on Power7 or later
|
||||
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
|
||||
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/rfi_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/entry_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
if (rfi_flush_orig != 0) {
|
||||
if (write_debugfs_file("powerpc/rfi_flush", 0) < 0) {
|
||||
perror("error writing to powerpc/rfi_flush debugfs file");
|
||||
FAIL_IF(1);
|
||||
}
|
||||
}
|
||||
|
||||
entry_flush = entry_flush_orig;
|
||||
|
||||
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
|
||||
FAIL_IF(fd < 0);
|
||||
|
||||
p = (char *)memalign(zero_size, CACHELINE_SIZE);
|
||||
|
||||
FAIL_IF(perf_event_enable(fd));
|
||||
|
||||
// disable L1 prefetching
|
||||
set_dscr(1);
|
||||
|
||||
iter = repetitions;
|
||||
|
||||
/*
|
||||
* We expect to see l1d miss for each cacheline access when entry_flush
|
||||
* is set. Allow a small variation on this.
|
||||
*/
|
||||
l1d_misses_expected = iterations * (zero_size / CACHELINE_SIZE - 2);
|
||||
|
||||
again:
|
||||
FAIL_IF(perf_event_reset(fd));
|
||||
|
||||
syscall_loop(p, iterations, zero_size);
|
||||
|
||||
FAIL_IF(read(fd, &v, sizeof(v)) != sizeof(v));
|
||||
|
||||
if (entry_flush && v.l1d_misses >= l1d_misses_expected)
|
||||
passes++;
|
||||
else if (!entry_flush && v.l1d_misses < (l1d_misses_expected / 2))
|
||||
passes++;
|
||||
|
||||
l1d_misses_total += v.l1d_misses;
|
||||
|
||||
while (--iter)
|
||||
goto again;
|
||||
|
||||
if (passes < repetitions) {
|
||||
printf("FAIL (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d failures]\n",
|
||||
entry_flush, l1d_misses_total, entry_flush ? '<' : '>',
|
||||
entry_flush ? repetitions * l1d_misses_expected :
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
repetitions - passes, repetitions);
|
||||
rc = 1;
|
||||
} else {
|
||||
printf("PASS (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d pass]\n",
|
||||
entry_flush, l1d_misses_total, entry_flush ? '>' : '<',
|
||||
entry_flush ? repetitions * l1d_misses_expected :
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
passes, repetitions);
|
||||
}
|
||||
|
||||
if (entry_flush == entry_flush_orig) {
|
||||
entry_flush = !entry_flush_orig;
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush) < 0) {
|
||||
perror("error writing to powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
iter = repetitions;
|
||||
l1d_misses_total = 0;
|
||||
passes = 0;
|
||||
goto again;
|
||||
}
|
||||
|
||||
perf_event_disable(fd);
|
||||
close(fd);
|
||||
|
||||
set_dscr(0);
|
||||
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return test_harness(entry_flush_test, "entry_flush_test");
|
||||
}
|
||||
70
tools/testing/selftests/powerpc/security/flush_utils.c
Normal file
70
tools/testing/selftests/powerpc/security/flush_utils.c
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
static inline __u64 load(void *addr)
|
||||
{
|
||||
__u64 tmp;
|
||||
|
||||
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size)
|
||||
{
|
||||
for (unsigned long i = 0; i < iterations; i++) {
|
||||
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
|
||||
load(p + j);
|
||||
getppid();
|
||||
}
|
||||
}
|
||||
|
||||
static void sigill_handler(int signr, siginfo_t *info, void *unused)
|
||||
{
|
||||
static int warned;
|
||||
ucontext_t *ctx = (ucontext_t *)unused;
|
||||
unsigned long *pc = &UCONTEXT_NIA(ctx);
|
||||
|
||||
/* mtspr 3,RS to check for move to DSCR below */
|
||||
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
|
||||
if (!warned++)
|
||||
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
|
||||
*pc += 4;
|
||||
} else {
|
||||
printf("SIGILL at %p\n", pc);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void set_dscr(unsigned long val)
|
||||
{
|
||||
static int init;
|
||||
struct sigaction sa;
|
||||
|
||||
if (!init) {
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = sigill_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(SIGILL, &sa, NULL))
|
||||
perror("sigill_handler");
|
||||
init = 1;
|
||||
}
|
||||
|
||||
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
|
||||
}
|
||||
17
tools/testing/selftests/powerpc/security/flush_utils.h
Normal file
17
tools/testing/selftests/powerpc/security/flush_utils.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#ifndef _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
|
||||
#define _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
|
||||
|
||||
#define CACHELINE_SIZE 128
|
||||
|
||||
void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size);
|
||||
|
||||
void set_dscr(unsigned long val);
|
||||
|
||||
#endif /* _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H */
|
||||
@@ -10,71 +10,12 @@
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
#define CACHELINE_SIZE 128
|
||||
|
||||
struct perf_event_read {
|
||||
__u64 nr;
|
||||
__u64 l1d_misses;
|
||||
};
|
||||
|
||||
static inline __u64 load(void *addr)
|
||||
{
|
||||
__u64 tmp;
|
||||
|
||||
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size)
|
||||
{
|
||||
for (unsigned long i = 0; i < iterations; i++) {
|
||||
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
|
||||
load(p + j);
|
||||
getppid();
|
||||
}
|
||||
}
|
||||
|
||||
static void sigill_handler(int signr, siginfo_t *info, void *unused)
|
||||
{
|
||||
static int warned = 0;
|
||||
ucontext_t *ctx = (ucontext_t *)unused;
|
||||
unsigned long *pc = &UCONTEXT_NIA(ctx);
|
||||
|
||||
/* mtspr 3,RS to check for move to DSCR below */
|
||||
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
|
||||
if (!warned++)
|
||||
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
|
||||
*pc += 4;
|
||||
} else {
|
||||
printf("SIGILL at %p\n", pc);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void set_dscr(unsigned long val)
|
||||
{
|
||||
static int init = 0;
|
||||
struct sigaction sa;
|
||||
|
||||
if (!init) {
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = sigill_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(SIGILL, &sa, NULL))
|
||||
perror("sigill_handler");
|
||||
init = 1;
|
||||
}
|
||||
|
||||
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
|
||||
}
|
||||
|
||||
int rfi_flush_test(void)
|
||||
{
|
||||
@@ -85,19 +26,33 @@ int rfi_flush_test(void)
|
||||
__u64 l1d_misses_total = 0;
|
||||
unsigned long iterations = 100000, zero_size = 24 * 1024;
|
||||
unsigned long l1d_misses_expected;
|
||||
int rfi_flush_org, rfi_flush;
|
||||
int rfi_flush_orig, rfi_flush;
|
||||
int have_entry_flush, entry_flush_orig;
|
||||
|
||||
SKIP_IF(geteuid() != 0);
|
||||
|
||||
// The PMU event we use only works on Power7 or later
|
||||
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
|
||||
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_org)) {
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/rfi_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
rfi_flush = rfi_flush_org;
|
||||
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
|
||||
have_entry_flush = 0;
|
||||
} else {
|
||||
have_entry_flush = 1;
|
||||
|
||||
if (entry_flush_orig != 0) {
|
||||
if (write_debugfs_file("powerpc/entry_flush", 0) < 0) {
|
||||
perror("error writing to powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rfi_flush = rfi_flush_orig;
|
||||
|
||||
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
|
||||
FAIL_IF(fd < 0);
|
||||
@@ -106,6 +61,7 @@ int rfi_flush_test(void)
|
||||
|
||||
FAIL_IF(perf_event_enable(fd));
|
||||
|
||||
// disable L1 prefetching
|
||||
set_dscr(1);
|
||||
|
||||
iter = repetitions;
|
||||
@@ -147,8 +103,8 @@ again:
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
passes, repetitions);
|
||||
|
||||
if (rfi_flush == rfi_flush_org) {
|
||||
rfi_flush = !rfi_flush_org;
|
||||
if (rfi_flush == rfi_flush_orig) {
|
||||
rfi_flush = !rfi_flush_orig;
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush) < 0) {
|
||||
perror("error writing to powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
@@ -164,11 +120,19 @@ again:
|
||||
|
||||
set_dscr(0);
|
||||
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_org) < 0) {
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (have_entry_flush) {
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/entry_flush "
|
||||
"debugfs file");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user