blob: 39b52ef4317aa53d1cca1df44c06274c7a58aa8e [file] [log] [blame]
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#define RR_IMPLEMENT_PRELOAD
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "preload_interface.h"
#include "syscallbuf.h"
#define PTHREAD_MUTEX_PRIO_INHERIT_NP 32
#define DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE 1
#ifdef __GLIBC_PREREQ
#if __GLIBC_PREREQ(2, 34)
#undef DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE
#endif
#endif
static int (*real_pthread_mutex_init)(void* mutex, const void* attr);
static int (*real_pthread_mutex_lock)(void* mutex);
static int (*real_pthread_mutex_trylock)(void* mutex);
static int (*real_pthread_mutex_timedlock)(void* mutex,
const struct timespec* abstime);
static void __attribute__((constructor)) init_override(void) {
real_pthread_mutex_init = dlsym(RTLD_NEXT, "pthread_mutex_init");
real_pthread_mutex_lock = dlsym(RTLD_NEXT, "pthread_mutex_lock");
real_pthread_mutex_trylock = dlsym(RTLD_NEXT, "pthread_mutex_trylock");
real_pthread_mutex_timedlock = dlsym(RTLD_NEXT, "pthread_mutex_timedlock");
}
static void fix_mutex_kind(pthread_mutex_t* mutex) {
/* Disable priority inheritance. */
mutex->__data.__kind &= ~PTHREAD_MUTEX_PRIO_INHERIT_NP;
}
#ifdef DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE
/*
* We need to able to call directly to __pthread_mutex_lock and
* __pthread_mutex_trylock because setting up indirect function pointers
* in init_process requires calls to dlsym which itself can call
* pthread_mutex_lock (e.g. via application code overriding malloc/calloc
* to use a pthreads-based implementation). So before our pointers are set
* up, call these.
*/
extern int __pthread_mutex_init(pthread_mutex_t* mutex,
const pthread_mutexattr_t* attr);
extern int __pthread_mutex_lock(pthread_mutex_t* mutex);
extern int __pthread_mutex_trylock(pthread_mutex_t* mutex);
#endif
int pthread_mutex_init(pthread_mutex_t* mutex,
const pthread_mutexattr_t* attr) {
int ret;
pthread_mutexattr_t realattr;
if (attr) {
/* We wish to enforce the use of plain (no PI) mutex to avoid
* needing to handle PI futex() operations.
* We also wish to ensure that pthread_mutexattr_getprotocol()
* still returns the requested protocol.
* So we copy the attribute and force PTHREAD_PRIO_NONE.
*/
memcpy(&realattr, attr, sizeof(realattr));
ret = pthread_mutexattr_setprotocol(&realattr, PTHREAD_PRIO_NONE);
if (ret) {
return ret;
}
attr = &realattr;
}
if (!real_pthread_mutex_init) {
#ifdef DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE
return __pthread_mutex_init(mutex, attr);
#else
real_pthread_mutex_init = dlsym(RTLD_NEXT, "pthread_mutex_init");
#endif
}
return real_pthread_mutex_init(mutex, attr);
}
/* Prevent use of lock elision; Haswell's TSX/RTM features used by
lock elision increment the rbc perf counter for instructions which
are later rolled back if the transaction fails. */
int pthread_mutex_lock(pthread_mutex_t* mutex) {
fix_mutex_kind(mutex);
if (!real_pthread_mutex_lock) {
#ifdef DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE
return __pthread_mutex_lock(mutex);
#else
real_pthread_mutex_lock = dlsym(RTLD_NEXT, "pthread_mutex_lock");
#endif
}
return real_pthread_mutex_lock(mutex);
}
int pthread_mutex_timedlock(pthread_mutex_t* mutex,
const struct timespec* abstime) {
fix_mutex_kind(mutex);
/* No __pthread_mutex_timedlock stub exists, so we have to use the
* indirect call no matter what.
*/
if (!real_pthread_mutex_timedlock) {
real_pthread_mutex_timedlock = dlsym(RTLD_NEXT, "pthread_mutex_timedlock");
}
return real_pthread_mutex_timedlock(mutex, abstime);
}
int pthread_mutex_trylock(pthread_mutex_t* mutex) {
fix_mutex_kind(mutex);
if (!real_pthread_mutex_trylock) {
#ifdef DOUBLE_UNDERSCORE_PTHREAD_LOCK_AVAILABLE
return __pthread_mutex_trylock(mutex);
#else
real_pthread_mutex_trylock = dlsym(RTLD_NEXT, "pthread_mutex_trylock");
#endif
}
return real_pthread_mutex_trylock(mutex);
}
typedef void* Dlopen(const char* filename, int flags);
void* dlopen(const char* filename, int flags) {
// Give up our timeslice now. This gives us a full timeslice to
// execute the dlopen(), reducing the chance we'll hit
// https://sourceware.org/bugzilla/show_bug.cgi?id=19329.
Dlopen* f_ptr = (Dlopen*)dlsym(RTLD_NEXT, "dlopen");
sched_yield();
return f_ptr(filename, flags);
}
/** Disable XShm since rr doesn't work with it */
int XShmQueryExtension(__attribute__((unused)) void* dpy) { return 0; }
/** Make sure XShmCreateImage returns null in case an application doesn't do
extension checks first. */
void* XShmCreateImage(__attribute__((unused)) register void* dpy,
__attribute__((unused)) register void* visual,
__attribute__((unused)) unsigned int depth,
__attribute__((unused)) int format,
__attribute__((unused)) char* data,
__attribute__((unused)) void* shminfo,
__attribute__((unused)) unsigned int width,
__attribute__((unused)) unsigned int height) {
return 0;
}
RR_HIDDEN char impose_syscall_delay;
RR_HIDDEN char impose_spurious_desched;
/**
* This is for testing purposes only.
*/
void delayed_syscall(struct syscall_info* info) {
impose_syscall_delay = 1;
/* Make sure 'result' is used so it's not optimized out! */
syscall(info->no, info->args[0], info->args[1], info->args[2], info->args[3],
info->args[4], info->args[5]);
impose_syscall_delay = 0;
}
/**
* This is for testing purposes only.
* Note that this must be defined outside of the syscallbuf code.
* Otherwise, the signal recording code may expect exit from this function
* to trigger the syscallbuf exit breakpoint.
*/
void* syscallbuf_ptr(void) {
return ((struct preload_thread_locals*)PRELOAD_THREAD_LOCALS_ADDR)->buffer;
}
/**
* This is for testing purposes only.
*/
void spurious_desched_syscall(struct syscall_info* info) {
impose_spurious_desched = 1;
/* Make sure 'result' is used so it's not optimized out! */
syscall(info->no, info->args[0], info->args[1], info->args[2], info->args[3],
info->args[4], info->args[5]);
impose_spurious_desched = 0;
}
#ifndef __aarch64__
/**
* glibc geteuid() can be compiled to instructions ending in "syscall; ret"
* which sometimes can't be hooked. So override it here with something that
* can be hooked.
* This is not an issue on aarch64 since we only need to patch a single instruction.
*/
uid_t geteuid(void) {
#ifdef __i386__
return syscall(SYS_geteuid32);
#else
return syscall(SYS_geteuid);
#endif
}
/**
* clang's LeakSanitizer has regular threads call sched_yield() in a loop while
* a helper thread ptrace-attaches to them. If we let sched_yield() enter the
* syscallbuf, the helper thread sees that the regular thread SP register
* is pointing to the syscallbuf alt-stack, outside the stack region it
* expects, which causes it to freak out.
* So, override sched_yield() to perform the syscall in a way that can't
* be syscall-buffered. (We have no syscall hook for `syscall` followed by
* `inc %ecx`).
*/
int sched_yield(void) {
int trash;
#ifdef __i386__
asm volatile ("int $0x80; inc %0" : "=c"(trash) : "a"(SYS_sched_yield));
#elif defined(__x86_64__)
asm volatile ("syscall; inc %0" : "=c"(trash) : "a"(SYS_sched_yield));
#else
#error "Unknown architecture"
#endif
return 0;
}
/**
* libstdc++3 uses RDRAND. Bypass that with this incredible hack.
*/
void _ZNSt13random_device7_M_initERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE(
void* this, __attribute__((unused)) void* token) {
static void (*assign_string)(void *, char*) = NULL;
static void (*random_init)(void *, void*) = NULL;
if (!assign_string) {
assign_string = (void (*)(void *, char*))dlsym(RTLD_NEXT,
"_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6assignEPKc");
}
assign_string(token, "/dev/urandom");
if (!random_init) {
random_init = (void (*)(void *, void*))dlsym(RTLD_NEXT, __func__);
}
random_init(this, token);
}
/**
* gcc 4.8.4 in Ubuntu 14.04-32
*/
void _ZNSt13random_device7_M_initERKSs(void* this,
__attribute__((unused)) void* token) {
static void (*assign_string)(void *, char*) = NULL;
static void (*random_init)(void *, void*) = NULL;
if (!assign_string) {
assign_string = (void (*)(void *, char*))dlsym(RTLD_NEXT,
"_ZNSs6assignEPKc");
}
assign_string(token, "/dev/urandom");
if (!random_init) {
random_init = (void (*)(void *, void*))dlsym(RTLD_NEXT, __func__);
}
random_init(this, token);
}
#endif