Add tests for rseq(2)

Add a decent set of syscall tests for rseq(2). These are a bit awkward because
of issues with library integration. libc may register rseq on thread start
(including before main on the initial thread), precluding much testing. Thus we
run tests in a libc-free subprocess.

Support for rseq(2) in gVisor will come in a later commit.

PiperOrigin-RevId: 284595994
This commit is contained in:
Michael Pratt 2019-12-09 11:21:01 -08:00 committed by gVisor bot
parent 01eadf51ea
commit 498595d543
12 changed files with 985 additions and 0 deletions

View File

@ -376,6 +376,8 @@ syscall_test(
syscall_test(test = "//test/syscalls/linux:rlimits_test")
syscall_test(test = "//test/syscalls/linux:rseq_test")
syscall_test(test = "//test/syscalls/linux:rtsignal_test")
syscall_test(test = "//test/syscalls/linux:sched_test")

View File

@ -1852,6 +1852,22 @@ cc_binary(
],
)
cc_binary(
name = "rseq_test",
testonly = 1,
srcs = ["rseq.cc"],
data = ["//test/syscalls/linux/rseq"],
linkstatic = 1,
deps = [
"//test/syscalls/linux/rseq:lib",
"//test/util:logging",
"//test/util:multiprocess_util",
"//test/util:test_main",
"//test/util:test_util",
"@com_google_googletest//:gtest",
],
)
cc_binary(
name = "rtsignal_test",
testonly = 1,

198
test/syscalls/linux/rseq.cc Normal file
View File

@ -0,0 +1,198 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gtest/gtest.h"
#include "test/syscalls/linux/rseq/test.h"
#include "test/syscalls/linux/rseq/uapi.h"
#include "test/util/logging.h"
#include "test/util/multiprocess_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
namespace {
// Syscall test for rseq (restartable sequences).
//
// We must be very careful about how these tests are written. Each thread may
// only have one struct rseq registration, which may be done automatically at
// thread start (as of 2019-11-13, glibc does *not* support rseq and thus does
// not do so).
//
// Testing of rseq is thus done primarily in a child process with no
// registration. This means exec'ing a nostdlib binary, as rseq registration can
// only be cleared by execve (or knowing the old rseq address), and glibc (based
// on the current unmerged patches) register rseq before calling main()).
int RSeq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t sig) {
return syscall(kRseqSyscall, rseq, rseq_len, flags, sig);
}
// Returns true if this kernel supports the rseq syscall.
PosixErrorOr<bool> RSeqSupported() {
// We have to be careful here, there are three possible cases:
//
// 1. rseq is not supported -> ENOSYS
// 2. rseq is supported and not registered -> success, but we should
// unregister.
// 3. rseq is supported and registered -> EINVAL (most likely).
// The only validation done on new registrations is that rseq is aligned and
// writable.
rseq rseq = {};
int ret = RSeq(&rseq, sizeof(rseq), 0, 0);
if (ret == 0) {
// Successfully registered, rseq is supported. Unregister.
ret = RSeq(&rseq, sizeof(rseq), kRseqFlagUnregister, 0);
if (ret != 0) {
return PosixError(errno);
}
return true;
}
switch (errno) {
case ENOSYS:
// Not supported.
return false;
case EINVAL:
// Supported, but already registered. EINVAL returned because we provided
// a different address.
return true;
default:
// Unknown error.
return PosixError(errno);
}
}
constexpr char kRseqBinary[] = "test/syscalls/linux/rseq/rseq";
void RunChildTest(std::string test_case, int want_status) {
std::string path = RunfilePath(kRseqBinary);
pid_t child_pid = -1;
int execve_errno = 0;
auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
ForkAndExec(path, {path, test_case}, {}, &child_pid, &execve_errno));
ASSERT_GT(child_pid, 0);
ASSERT_EQ(execve_errno, 0);
int status = 0;
ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
ASSERT_EQ(status, want_status);
}
// Test that rseq must be aligned.
TEST(RseqTest, Unaligned) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestUnaligned, 0);
}
// Sanity test that registration works.
TEST(RseqTest, Register) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestRegister, 0);
}
// Registration can't be done twice.
TEST(RseqTest, DoubleRegister) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestDoubleRegister, 0);
}
// Registration can be done again after unregister.
TEST(RseqTest, RegisterUnregister) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestRegisterUnregister, 0);
}
// The pointer to rseq must match on register/unregister.
TEST(RseqTest, UnregisterDifferentPtr) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestUnregisterDifferentPtr, 0);
}
// The signature must match on register/unregister.
TEST(RseqTest, UnregisterDifferentSignature) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestUnregisterDifferentSignature, 0);
}
// The CPU ID is initialized.
TEST(RseqTest, CPU) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestCPU, 0);
}
// Critical section is eventually aborted.
TEST(RseqTest, Abort) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestAbort, 0);
}
// Abort may be before the critical section.
TEST(RseqTest, AbortBefore) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestAbortBefore, 0);
}
// Signature must match.
TEST(RseqTest, AbortSignature) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestAbortSignature, SIGSEGV);
}
// Abort must not be in the critical section.
TEST(RseqTest, AbortPreCommit) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestAbortPreCommit, SIGSEGV);
}
// rseq.rseq_cs is cleared on abort.
TEST(RseqTest, AbortClearsCS) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestAbortClearsCS, 0);
}
// rseq.rseq_cs is cleared on abort outside of critical section.
TEST(RseqTest, InvalidAbortClearsCS) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(RSeqSupported()));
RunChildTest(kRseqTestInvalidAbortClearsCS, 0);
}
} // namespace
} // namespace testing
} // namespace gvisor

View File

@ -0,0 +1,59 @@
# This package contains a standalone rseq test binary. This binary must not
# depend on libc, which might use rseq itself.
load("@bazel_tools//tools/cpp:cc_flags_supplier.bzl", "cc_flags_supplier")
load("@rules_cc//cc:defs.bzl", "cc_library")
package(licenses = ["notice"])
genrule(
name = "rseq_binary",
srcs = [
"critical.h",
"critical.S",
"rseq.cc",
"syscalls.h",
"start.S",
"test.h",
"types.h",
"uapi.h",
],
outs = ["rseq"],
cmd = " ".join([
"$(CC)",
"$(CC_FLAGS) ",
"-I.",
"-Wall",
"-Werror",
"-O2",
"-std=c++17",
"-static",
"-nostdlib",
"-ffreestanding",
"-o",
"$(location rseq)",
"$(location critical.S)",
"$(location rseq.cc)",
"$(location start.S)",
]),
toolchains = [
":no_pie_cc_flags",
"@bazel_tools//tools/cpp:current_cc_toolchain",
],
visibility = ["//:sandbox"],
)
cc_flags_supplier(
name = "no_pie_cc_flags",
features = ["-pie"],
)
cc_library(
name = "lib",
testonly = 1,
hdrs = [
"test.h",
"uapi.h",
],
visibility = ["//:sandbox"],
)

View File

@ -0,0 +1,66 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Restartable sequences critical sections.
// Loops continuously until aborted.
//
// void rseq_loop(struct rseq* r, struct rseq_cs* cs)
.text
.globl rseq_loop
.type rseq_loop, @function
rseq_loop:
jmp begin
// Abort block before the critical section.
// Abort signature is 4 nops for simplicity.
.byte 0x90, 0x90, 0x90, 0x90
.globl rseq_loop_early_abort
rseq_loop_early_abort:
ret
begin:
// r->rseq_cs = cs
movq %rsi, 8(%rdi)
// N.B. rseq_cs will be cleared by any preempt, even outside the critical
// section. Thus it must be set in or immediately before the critical section
// to ensure it is not cleared before the section begins.
.globl rseq_loop_start
rseq_loop_start:
jmp rseq_loop_start
// "Pre-commit": extra instructions inside the critical section. These are
// used as the abort point in TestAbortPreCommit, which is not valid.
.globl rseq_loop_pre_commit
rseq_loop_pre_commit:
// Extra abort signature + nop for TestAbortPostCommit.
.byte 0x90, 0x90, 0x90, 0x90
nop
// "Post-commit": never reached in this case.
.globl rseq_loop_post_commit
rseq_loop_post_commit:
// Abort signature is 4 nops for simplicity.
.byte 0x90, 0x90, 0x90, 0x90
.globl rseq_loop_abort
rseq_loop_abort:
ret
.size rseq_loop,.-rseq_loop
.section .note.GNU-stack,"",@progbits

View File

@ -0,0 +1,39 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_
#include "test/syscalls/linux/rseq/types.h"
#include "test/syscalls/linux/rseq/uapi.h"
constexpr uint32_t kRseqSignature = 0x90909090;
extern "C" {
extern void rseq_loop(struct rseq* r, struct rseq_cs* cs);
extern void* rseq_loop_early_abort;
extern void* rseq_loop_start;
extern void* rseq_loop_pre_commit;
extern void* rseq_loop_post_commit;
extern void* rseq_loop_abort;
extern int rseq_getpid(struct rseq* r, struct rseq_cs* cs);
extern void* rseq_getpid_start;
extern void* rseq_getpid_post_commit;
extern void* rseq_getpid_abort;
} // extern "C"
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_CRITICAL_H_

View File

@ -0,0 +1,366 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/syscalls/linux/rseq/critical.h"
#include "test/syscalls/linux/rseq/syscalls.h"
#include "test/syscalls/linux/rseq/test.h"
#include "test/syscalls/linux/rseq/types.h"
#include "test/syscalls/linux/rseq/uapi.h"
namespace gvisor {
namespace testing {
extern "C" int main(int argc, char** argv, char** envp);
// Standalone initialization before calling main().
extern "C" void __init(uintptr_t* sp) {
int argc = sp[0];
char** argv = reinterpret_cast<char**>(&sp[1]);
char** envp = &argv[argc + 1];
// Call main() and exit.
sys_exit_group(main(argc, argv, envp));
// sys_exit_group does not return
}
int strcmp(const char* s1, const char* s2) {
const unsigned char* p1 = reinterpret_cast<const unsigned char*>(s1);
const unsigned char* p2 = reinterpret_cast<const unsigned char*>(s2);
while (*p1 == *p2) {
if (!*p1) {
return 0;
}
++p1;
++p2;
}
return static_cast<int>(*p1) - static_cast<int>(*p2);
}
int sys_rseq(struct rseq* rseq, uint32_t rseq_len, int flags, uint32_t sig) {
return raw_syscall(kRseqSyscall, rseq, rseq_len, flags, sig);
}
// Test that rseq must be aligned.
int TestUnaligned() {
constexpr uintptr_t kRequiredAlignment = alignof(rseq);
char buf[2 * kRequiredAlignment] = {};
uintptr_t ptr = reinterpret_cast<uintptr_t>(&buf[0]);
if ((ptr & (kRequiredAlignment - 1)) == 0) {
// buf is already aligned. Misalign it.
ptr++;
}
int ret = sys_rseq(reinterpret_cast<rseq*>(ptr), sizeof(rseq), 0, 0);
if (sys_errno(ret) != EINVAL) {
return 1;
}
return 0;
}
// Sanity test that registration works.
int TestRegister() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
return 0;
};
// Registration can't be done twice.
int TestDoubleRegister() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != EBUSY) {
return 1;
}
return 0;
};
// Registration can be done again after unregister.
int TestRegisterUnregister() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
if (int ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, 0);
sys_errno(ret) != 0) {
return 1;
}
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
return 0;
};
// The pointer to rseq must match on register/unregister.
int TestUnregisterDifferentPtr() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
struct rseq r2 = {};
if (int ret = sys_rseq(&r2, sizeof(r2), kRseqFlagUnregister, 0);
sys_errno(ret) != EINVAL) {
return 1;
}
return 0;
};
// The signature must match on register/unregister.
int TestUnregisterDifferentSignature() {
constexpr int kSignature = 0;
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kSignature); sys_errno(ret) != 0) {
return 1;
}
if (int ret = sys_rseq(&r, sizeof(r), kRseqFlagUnregister, kSignature + 1);
sys_errno(ret) != EPERM) {
return 1;
}
return 0;
};
// The CPU ID is initialized.
int TestCPU() {
struct rseq r = {};
r.cpu_id = kRseqCPUIDUninitialized;
if (int ret = sys_rseq(&r, sizeof(r), 0, 0); sys_errno(ret) != 0) {
return 1;
}
if (__atomic_load_n(&r.cpu_id, __ATOMIC_RELAXED) < 0) {
return 1;
}
if (__atomic_load_n(&r.cpu_id_start, __ATOMIC_RELAXED) < 0) {
return 1;
}
return 0;
};
// Critical section is eventually aborted.
int TestAbort() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort);
// Loops until abort. If this returns then abort occurred.
rseq_loop(&r, &cs);
return 0;
};
// Abort may be before the critical section.
int TestAbortBefore() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_early_abort);
// Loops until abort. If this returns then abort occurred.
rseq_loop(&r, &cs);
return 0;
};
// Signature must match.
int TestAbortSignature() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort);
// Loops until abort. This should SIGSEGV on abort.
rseq_loop(&r, &cs);
return 1;
};
// Abort must not be in the critical section.
int TestAbortPreCommit() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature + 1);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_pre_commit);
// Loops until abort. This should SIGSEGV on abort.
rseq_loop(&r, &cs);
return 1;
};
// rseq.rseq_cs is cleared on abort.
int TestAbortClearsCS() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort);
// Loops until abort. If this returns then abort occurred.
rseq_loop(&r, &cs);
if (__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) {
return 1;
}
return 0;
};
// rseq.rseq_cs is cleared on abort outside of critical section.
int TestInvalidAbortClearsCS() {
struct rseq r = {};
if (int ret = sys_rseq(&r, sizeof(r), 0, kRseqSignature);
sys_errno(ret) != 0) {
return 1;
}
struct rseq_cs cs = {};
cs.version = 0;
cs.flags = 0;
cs.start_ip = reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.post_commit_offset = reinterpret_cast<uint64_t>(&rseq_loop_post_commit) -
reinterpret_cast<uint64_t>(&rseq_loop_start);
cs.abort_ip = reinterpret_cast<uint64_t>(&rseq_loop_abort);
__atomic_store_n(&r.rseq_cs, &cs, __ATOMIC_RELAXED);
// When the next abort condition occurs, the kernel will clear cs once it
// determines we aren't in the critical section.
while (1) {
if (!__atomic_load_n(&r.rseq_cs, __ATOMIC_RELAXED)) {
break;
}
}
return 0;
};
// Exit codes:
// 0 - Pass
// 1 - Fail
// 2 - Missing argument
// 3 - Unknown test case
extern "C" int main(int argc, char** argv, char** envp) {
if (argc != 2) {
// Usage: rseq <test case>
return 2;
}
if (strcmp(argv[1], kRseqTestUnaligned) == 0) {
return TestUnaligned();
}
if (strcmp(argv[1], kRseqTestRegister) == 0) {
return TestRegister();
}
if (strcmp(argv[1], kRseqTestDoubleRegister) == 0) {
return TestDoubleRegister();
}
if (strcmp(argv[1], kRseqTestRegisterUnregister) == 0) {
return TestRegisterUnregister();
}
if (strcmp(argv[1], kRseqTestUnregisterDifferentPtr) == 0) {
return TestUnregisterDifferentPtr();
}
if (strcmp(argv[1], kRseqTestUnregisterDifferentSignature) == 0) {
return TestUnregisterDifferentSignature();
}
if (strcmp(argv[1], kRseqTestCPU) == 0) {
return TestCPU();
}
if (strcmp(argv[1], kRseqTestAbort) == 0) {
return TestAbort();
}
if (strcmp(argv[1], kRseqTestAbortBefore) == 0) {
return TestAbortBefore();
}
if (strcmp(argv[1], kRseqTestAbortSignature) == 0) {
return TestAbortSignature();
}
if (strcmp(argv[1], kRseqTestAbortPreCommit) == 0) {
return TestAbortPreCommit();
}
if (strcmp(argv[1], kRseqTestAbortClearsCS) == 0) {
return TestAbortClearsCS();
}
if (strcmp(argv[1], kRseqTestInvalidAbortClearsCS) == 0) {
return TestInvalidAbortClearsCS();
}
return 3;
}
} // namespace testing
} // namespace gvisor

View File

@ -0,0 +1,45 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
.text
.align 4
.type _start,@function
.globl _start
_start:
movq %rsp,%rdi
call __init
hlt
.size _start,.-_start
.section .note.GNU-stack,"",@progbits
.text
.globl raw_syscall
.type raw_syscall, @function
raw_syscall:
mov %rdi,%rax // syscall #
mov %rsi,%rdi // arg0
mov %rdx,%rsi // arg1
mov %rcx,%rdx // arg2
mov %r8,%r10 // arg3 (goes in r10 instead of rcx for system calls)
mov %r9,%r8 // arg4
mov 0x8(%rsp),%r9 // arg5
syscall
ret
.size raw_syscall,.-raw_syscall
.section .note.GNU-stack,"",@progbits

View File

@ -0,0 +1,66 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_
#include "test/syscalls/linux/rseq/types.h"
#ifdef __x86_64__
// Syscall numbers.
constexpr int kGetpid = 39;
constexpr int kExitGroup = 231;
#else
#error "Unknown architecture"
#endif
namespace gvisor {
namespace testing {
// Standalone system call interfaces.
// Note that these are all "raw" system call interfaces which encode
// errors by setting the return value to a small negative number.
// Use sys_errno() to check system call return values for errors.
// Maximum Linux error number.
constexpr int kMaxErrno = 4095;
// Errno values.
#define EPERM 1
#define EFAULT 14
#define EBUSY 16
#define EINVAL 22
// Get the error number from a raw system call return value.
// Returns a positive error number or 0 if there was no error.
static inline int sys_errno(uintptr_t rval) {
if (rval >= static_cast<uintptr_t>(-kMaxErrno)) {
return -static_cast<int>(rval);
}
return 0;
}
extern "C" uintptr_t raw_syscall(int number, ...);
static inline void sys_exit_group(int status) {
raw_syscall(kExitGroup, status);
}
static inline int sys_getpid() {
return static_cast<int>(raw_syscall(kGetpid));
}
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_SYSCALLS_H_

View File

@ -0,0 +1,43 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_
namespace gvisor {
namespace testing {
// Test cases supported by rseq binary.
inline constexpr char kRseqTestUnaligned[] = "unaligned";
inline constexpr char kRseqTestRegister[] = "register";
inline constexpr char kRseqTestDoubleRegister[] = "double-register";
inline constexpr char kRseqTestRegisterUnregister[] = "register-unregister";
inline constexpr char kRseqTestUnregisterDifferentPtr[] =
"unregister-different-ptr";
inline constexpr char kRseqTestUnregisterDifferentSignature[] =
"unregister-different-signature";
inline constexpr char kRseqTestCPU[] = "cpu";
inline constexpr char kRseqTestAbort[] = "abort";
inline constexpr char kRseqTestAbortBefore[] = "abort-before";
inline constexpr char kRseqTestAbortSignature[] = "abort-signature";
inline constexpr char kRseqTestAbortPreCommit[] = "abort-precommit";
inline constexpr char kRseqTestAbortClearsCS[] = "abort-clears-cs";
inline constexpr char kRseqTestInvalidAbortClearsCS[] =
"invalid-abort-clears-cs";
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TEST_H_

View File

@ -0,0 +1,31 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_
using size_t = __SIZE_TYPE__;
using uintptr_t = __UINTPTR_TYPE__;
using uint8_t = __UINT8_TYPE__;
using uint16_t = __UINT16_TYPE__;
using uint32_t = __UINT32_TYPE__;
using uint64_t = __UINT64_TYPE__;
using int8_t = __INT8_TYPE__;
using int16_t = __INT16_TYPE__;
using int32_t = __INT32_TYPE__;
using int64_t = __INT64_TYPE__;
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_TYPES_H_

View File

@ -0,0 +1,54 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_
#define GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_
// User-kernel ABI for restartable sequences.
// Standard types.
//
// N.B. This header will be included in targets that do have the standard
// library, so we can't shadow the standard type names.
using __u32 = __UINT32_TYPE__;
using __u64 = __UINT64_TYPE__;
#ifdef __x86_64__
// Syscall numbers.
constexpr int kRseqSyscall = 334;
#else
#error "Unknown architecture"
#endif // __x86_64__
struct rseq_cs {
__u32 version;
__u32 flags;
__u64 start_ip;
__u64 post_commit_offset;
__u64 abort_ip;
} __attribute__((aligned(4 * sizeof(__u64))));
// N.B. alignment is enforced by the kernel.
struct rseq {
__u32 cpu_id_start;
__u32 cpu_id;
struct rseq_cs* rseq_cs;
__u32 flags;
} __attribute__((aligned(4 * sizeof(__u64))));
constexpr int kRseqFlagUnregister = 1 << 0;
constexpr int kRseqCPUIDUninitialized = -1;
#endif // GVISOR_TEST_SYSCALLS_LINUX_RSEQ_UAPI_H_