From 2457b4b57ee7914167565d37534f7dc0354e60e2 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 21 Sep 2024 00:54:57 +0000 Subject: [PATCH] Add test case for #3831 This one depends on the timing of a SIGCHLD (needs to happen right when resuming the parent task, so that it gets delivered in the AutoRemoteSyscall for the syscallbuf cleanup), so it's somewhat non-deterministic. However, but exiting fast enough (and the dynamic linker is not), it's possible to trigger this condition quite reliably, so add an executable that does that. --- CMakeLists.txt | 22 +++++++++++++-- src/test/exec_shared_as.c | 44 ++++++++++++++++++++++++++++++ src/test/exec_shared_as.run | 6 +++++ src/test/exit_fast.c | 7 +++++ src/test/util.h | 48 ++------------------------------- src/test/util_syscall.h | 53 +++++++++++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 src/test/exec_shared_as.c create mode 100644 src/test/exec_shared_as.run create mode 100644 src/test/exit_fast.c create mode 100644 src/test/util_syscall.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5322ce509da..1f2c77e90fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1519,6 +1519,7 @@ set(TESTS_WITH_PROGRAM exclusion_region exec_failed exec_many + exec_shared_as execve_loop exit_codes exit_group @@ -1898,6 +1899,14 @@ if(BUILD_TESTS) endif() endif() + # Add exit_fast test executable + add_executable(exit_fast src/test/exit_fast.c) + set_target_properties(exit_fast + PROPERTIES LINK_FLAGS "-static -nostartfiles -nodefaultlibs ${LINKER_FLAGS}") + post_build_executable(exit_fast) + set_source_files_properties(src/test/exit_fast.c + COMPILE_FLAGS "-fno-stack-protector") + # Check if we're running on KNL. If so, we allot more time to tests, due to # reduced single-core performance. execute_process(COMMAND cat /proc/cpuinfo OUTPUT_VARIABLE CPUINFO) @@ -1920,6 +1929,8 @@ if(BUILD_TESTS) LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rr) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/bin/test-monitor DESTINATION ${CMAKE_INSTALL_LIBDIR}/rr/testsuite/obj/bin) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/bin/exit_fast + DESTINATION ${CMAKE_INSTALL_LIBDIR}/rr/testsuite/obj/bin) if (x86ish) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/bin/cpuid DESTINATION ${CMAKE_INSTALL_LIBDIR}/rr/testsuite/obj/bin) @@ -1960,13 +1971,13 @@ if(BUILD_TESTS) # This sucks but I can't find a better way to get CMake to build # the same source file in two different ways. if(rr_32BIT AND rr_64BIT) - foreach(header util.h nsutils.h ptrace_util.h util_internal.h) + foreach(header util.h nsutils.h ptrace_util.h util_syscall.h util_internal.h) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/test/${header}" "${CMAKE_CURRENT_BINARY_DIR}/32/${header}" COPYONLY) endforeach(header) - foreach(test ${BASIC_TESTS} ${TESTS_WITH_PROGRAM} x86/cpuid test_lib tick0 watchpoint_unaligned2) + foreach(test ${BASIC_TESTS} ${TESTS_WITH_PROGRAM} x86/cpuid test_lib tick0 watchpoint_unaligned2 exit_fast) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/test/${test}.c" "${CMAKE_CURRENT_BINARY_DIR}/32/${test}.c" COPYONLY) @@ -2024,6 +2035,13 @@ if(BUILD_TESTS) PROPERTIES LINK_FLAGS "-m32 -static -nostdlib -nodefaultlibs" COMPILE_FLAGS "-m32 -static -nostdlib -nodefaultlibs -O3 -g2") + # Add exit_fast test executable + add_executable(exit_fast_32 "${CMAKE_CURRENT_BINARY_DIR}/32/exit_fast.c") + set_target_properties(exit_fast_32 + PROPERTIES LINK_FLAGS "-static -nostartfiles -nodefaultlibs ${LINKER_FLAGS}") + post_build_executable(exit_fast_32) + set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/32/exit_fast.c" COMPILE_FLAGS "-fno-stack-protector") + add_executable(watchpoint_unaligned2_32 "${CMAKE_CURRENT_BINARY_DIR}/32/watchpoint_unaligned2.c") post_build_executable(watchpoint_unaligned2_32) set_target_properties(watchpoint_unaligned2_32 diff --git a/src/test/exec_shared_as.c b/src/test/exec_shared_as.c new file mode 100644 index 00000000000..5515191235c --- /dev/null +++ b/src/test/exec_shared_as.c @@ -0,0 +1,44 @@ +/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ + +#include "util.h" + +static pid_t child_tid; +static char *exe; + +static int do_child(__attribute__((unused)) void* p) { + char* argv[] = { exe, NULL }; + child_tid = sys_gettid(); // Force the syscallbuf to be allocated + execve(exe, argv, environ); + test_assert(0 && "Failed exec!"); + return 0; +} + +int main(int argc, char** argv) { + int i; + pid_t child; + int status; + + test_assert(argc == 2); + exe = argv[1]; + + const size_t stack_size = 1 << 20; + void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + // This test has a slight timing dependency. We want the SIGCHLD from the child process + // exiting to be delivered exactly when the parent process resumes for the first time. + // Our exit_fast executable makes this happen fairly reliably, but we run it a few times, + // just to make sure. + for (i = 0; i < 10; ++i) { + child = clone(do_child, stack + stack_size, + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VFORK | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, + NULL, &child_tid, NULL, &child_tid); + test_assert(child != -1); + test_assert(child == waitpid(child, &status, 0)); + test_assert(WIFEXITED(status) && WEXITSTATUS(status) == 77); + } + + atomic_puts("EXIT-SUCCESS"); + return 0; +} diff --git a/src/test/exec_shared_as.run b/src/test/exec_shared_as.run new file mode 100644 index 00000000000..5de2e9a3b35 --- /dev/null +++ b/src/test/exec_shared_as.run @@ -0,0 +1,6 @@ +source `dirname $0`/util.sh + +save_exe exit_fast$bitness +record $TESTNAME exit_fast$bitness-$nonce +replay +check EXIT-SUCCESS diff --git a/src/test/exit_fast.c b/src/test/exit_fast.c new file mode 100644 index 00000000000..a794bd436f3 --- /dev/null +++ b/src/test/exit_fast.c @@ -0,0 +1,7 @@ +/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ + +#include "util_syscall.h" + +void _start(void) { + unbufferable_syscall(RR_exit, 77, 0, 0); +} diff --git a/src/test/util.h b/src/test/util.h index 41060bb29f3..5fc24a66a5c 100644 --- a/src/test/util.h +++ b/src/test/util.h @@ -120,18 +120,10 @@ #include #endif -#if defined(__i386__) -#include "SyscallEnumsForTestsX86.generated" -#elif defined(__x86_64__) -#include "SyscallEnumsForTestsX64.generated" -#elif defined(__aarch64__) -#include "SyscallEnumsForTestsGeneric.generated" -#else -#error Unknown architecture -#endif - #include +#include "util_syscall.h" + typedef unsigned char uint8_t; #define ALEN(_a) (sizeof(_a) / sizeof(_a[0])) @@ -400,42 +392,6 @@ inline static SyscallWrapper get_spurious_desched_syscall(void) { return ret ? ret : default_syscall_wrapper; } -static inline uintptr_t unbufferable_syscall(uintptr_t syscall, uintptr_t arg1, - uintptr_t arg2, - uintptr_t arg3) { - uintptr_t ret; -#ifdef __x86_64__ - __asm__ volatile("syscall\n\t" - /* Make sure we don't patch this syscall for syscall buffering */ - "cmp $0x77,%%rax\n\t" - : "=a"(ret) - : "a"(syscall), "D"(arg1), "S"(arg2), "d"(arg3) - : "flags"); -#elif defined(__i386__) - __asm__ volatile("xchg %%esi,%%edi\n\t" - "int $0x80\n\t" - "xchg %%esi,%%edi\n\t" - : "=a"(ret) - : "a"(syscall), "b"(arg1), "c"(arg2), "d"(arg3)); -#elif defined(__aarch64__) - register long x8 __asm__("x8") = syscall; - register long x0 __asm__("x0") = (long)arg1; - register long x1 __asm__("x1") = (long)arg2; - register long x2 __asm__("x2") = (long)arg3; - __asm__ volatile("b 1f\n\t" - "mov x8, 0xdc\n" - "1:\n\t" - "svc #0\n\t" - : "+r"(x0) - : "r"(x1), "r"(x2), "r"(x8)); - ret = x0; -#else -#error define syscall here -#endif - return ret; -} - - /* Old systems don't have these functions, re-define using the syscall */ #define tgkill(tgid, tid, sig) \ syscall(SYS_tgkill, (int)(tgid), (int)(tid), (int)(sig)) diff --git a/src/test/util_syscall.h b/src/test/util_syscall.h new file mode 100644 index 00000000000..cc581047a09 --- /dev/null +++ b/src/test/util_syscall.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ + +#ifndef RRUTIL_SYSCALL_H +#define RRUTIL_SYSCLAL_H + +#include + +#if defined(__i386__) +#include "SyscallEnumsForTestsX86.generated" +#elif defined(__x86_64__) +#include "SyscallEnumsForTestsX64.generated" +#elif defined(__aarch64__) +#include "SyscallEnumsForTestsGeneric.generated" +#else +#error Unknown architecture +#endif + +static inline uintptr_t unbufferable_syscall(uintptr_t syscall, uintptr_t arg1, + uintptr_t arg2, + uintptr_t arg3) { + uintptr_t ret; +#ifdef __x86_64__ + __asm__ volatile("syscall\n\t" + /* Make sure we don't patch this syscall for syscall buffering */ + "cmp $0x77,%%rax\n\t" + : "=a"(ret) + : "a"(syscall), "D"(arg1), "S"(arg2), "d"(arg3) + : "flags"); +#elif defined(__i386__) + __asm__ volatile("xchg %%esi,%%edi\n\t" + "int $0x80\n\t" + "xchg %%esi,%%edi\n\t" + : "=a"(ret) + : "a"(syscall), "b"(arg1), "c"(arg2), "d"(arg3)); +#elif defined(__aarch64__) + register long x8 __asm__("x8") = syscall; + register long x0 __asm__("x0") = (long)arg1; + register long x1 __asm__("x1") = (long)arg2; + register long x2 __asm__("x2") = (long)arg3; + __asm__ volatile("b 1f\n\t" + "mov x8, 0xdc\n" + "1:\n\t" + "svc #0\n\t" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x8)); + ret = x0; +#else +#error define syscall here +#endif + return ret; +} + +#endif