blob: 49e135c6d820dadcc8be1eb2eefa312b35e3cbcd [file] [log] [blame]
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "util_internal.h"
char path1[PATH_MAX];
char path2[PATH_MAX];
void do_rdtsc(void) {
#ifdef __x86_64__
asm volatile ("rdtsc\n\t"
"mov %%rax,%%rcx\n\t" /* make it bufferable */
::: "eax", "edx");
#endif
/* We don't buffer RDTSC on i386 so nothing to test there */
}
int main(int argc, char **argv) {
if (argc == 2) {
test_assert(strcmp(argv[1], "--inner") == 0);
return 0;
}
test_assert(argc == 1);
pid_t pid = fork();
if (pid == 0) {
readlink("/proc/self/exe", path1, sizeof(path1));
// Check that MAP_GROWSDOWN doesn't get erased
size_t page_size = sysconf(_SC_PAGESIZE);
// Map a shared page
char filename[] = "/dev/shm/rr-test-XXXXXX";
int shmemfd = mkstemp(filename);
test_assert(shmemfd >= 0);
ftruncate(shmemfd, page_size);
int* wpage = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, shmemfd, 0);
int* rpage = mmap(NULL, page_size, PROT_READ, MAP_SHARED, shmemfd, 0);
*wpage = 1;
munmap(wpage, page_size);
// Let the kernel find us a 512 page gap that's free
char *pbase = mmap(NULL, page_size * 512, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
test_assert(pbase != MAP_FAILED);
munmap(pbase, 512*page_size);
volatile char *p = (char *)mmap(pbase + 509 * page_size, page_size * 3, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);
test_assert(p != MAP_FAILED);
// Check that /proc/self/mem gets remapped
uintptr_t newval = 1;
volatile uintptr_t stackval = 0;
int fd = open("/proc/self/mem", O_RDWR);
test_assert(fd >= 0);
test_assert(sizeof(stackval) == pwrite64(fd, &newval, sizeof(stackval), (off64_t)(uintptr_t)&stackval));
test_assert(stackval == 1);
// Check that rlimits survive
struct rlimit lim1, lim2;
test_assert(0 == getrlimit(RLIMIT_STACK, &lim1));
if (lim1.rlim_cur == RLIM_INFINITY) {
lim1.rlim_cur = 2 * 1024 * 1024; // 2 MiB
}
lim1.rlim_cur = 2 * lim1.rlim_cur;
if (lim1.rlim_max != RLIM_INFINITY && lim1.rlim_cur > lim1.rlim_max) {
lim1.rlim_cur = lim1.rlim_max;
}
test_assert(0 == setrlimit(RLIMIT_STACK, &lim1));
// Check that rdtsc gets unpatched
do_rdtsc();
if (running_under_rr()) {
rr_detach_teleport();
}
test_assert(0 == getrlimit(RLIMIT_STACK, &lim2));
test_assert(0 == memcmp(&lim1, &lim2, sizeof(struct rlimit)));
do_rdtsc();
newval = 2;
test_assert(sizeof(stackval) == pwrite64(fd, &newval, sizeof(stackval), (off64_t)(uintptr_t)&stackval));
test_assert(stackval == 2);
// Validate that rr reset the scheduler affinity mask.
// Unfortunately, we can't just check that it matches that from
// before the detach, because rr does not yet emulate the correct
// scheduler mask after it changes it. XXX: Adjust this test when
// that is fixed.
cpu_set_t cpus;
test_assert(0 == sched_getaffinity(0, sizeof(cpu_set_t), &cpus));
if (CPU_COUNT(&cpus) == 1) {
// If only one CPU is set either rr messed with us, or only one
// cpu is available for environmental reasons (e.g. only one CPU
// is online). To see if that is the case, try resetting the
// affinity ourselves and if still only one CPU is allowed, let
// that through.
memset(&cpus, 0xFF, sizeof(cpu_set_t));
test_assert(0 == sched_setaffinity(0, sizeof(cpu_set_t), &cpus));
test_assert(0 == sched_getaffinity(0, sizeof(cpu_set_t), &cpus));
test_assert(CPU_COUNT(&cpus) == 1);
}
// The kernel is picky about MAP_GROWSDOWN. Whether our setup above
// works depends on the stack_guard_gap cmdline parameter as well as
// kernel versions (prior to 5.0 MAP_GROWSDOWN was disallowed for
// access more than 64k bytes beyond the kernel pointer), so check
// whether we can expect it to work by explicitly allocating a
// MAP_GROWSDOWN page in a subprocess.
if (0 == fork()) {
test_assert(MAP_FAILED !=
(char *)mmap(pbase + 509 * page_size, page_size * 3, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0));
*p = 1;
*(p - 1) = 1;
return 0;
}
int status;
wait(&status);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
*p = 1;
*(p - 1) = 1;
}
// Don't print anything if this fails, because none of this is going to happen
// during replay and a printed message will cause the test to fail with apparent
// divergence.
readlink("/proc/self/exe", path2, sizeof(path2));
test_assert(0 == strcmp(path1, path2));
// Check that we can exec without having to clear LD_PRELOAD
if (fork() == 0) {
// NULL here drops LD_PRELOAD
char* execv_argv[] = {"/proc/self/exe", "--inner", NULL};
execve("/proc/self/exe", execv_argv, NULL);
test_assert(0 && "Exec should not have failed");
}
wait(&status);
test_assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
test_assert(*rpage == 1);
unlink(filename);
return 0;
}
int status;
wait(&status);
test_assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
atomic_puts("EXIT-SUCCESS");
return 0;
}