Skip to content

Commit

Permalink
Add test case for #3831
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Keno committed Sep 21, 2024
1 parent d4449e9 commit 2457b4b
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 48 deletions.
22 changes: 20 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1519,6 +1519,7 @@ set(TESTS_WITH_PROGRAM
exclusion_region
exec_failed
exec_many
exec_shared_as
execve_loop
exit_codes
exit_group
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions src/test/exec_shared_as.c
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 6 additions & 0 deletions src/test/exec_shared_as.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source `dirname $0`/util.sh

save_exe exit_fast$bitness
record $TESTNAME exit_fast$bitness-$nonce
replay
check EXIT-SUCCESS
7 changes: 7 additions & 0 deletions src/test/exit_fast.c
Original file line number Diff line number Diff line change
@@ -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);
}
48 changes: 2 additions & 46 deletions src/test/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,10 @@
#include <x86intrin.h>
#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 <rr/rr.h>

#include "util_syscall.h"

typedef unsigned char uint8_t;

#define ALEN(_a) (sizeof(_a) / sizeof(_a[0]))
Expand Down Expand Up @@ -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))
Expand Down
53 changes: 53 additions & 0 deletions src/test/util_syscall.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#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

0 comments on commit 2457b4b

Please sign in to comment.