diff options
| author | Sergey Matveev <earthdok@google.com> | 2013-09-02 11:36:19 +0000 |
|---|---|---|
| committer | Sergey Matveev <earthdok@google.com> | 2013-09-02 11:36:19 +0000 |
| commit | 69931c584141cf4c3c251ae4fa6a843935ed79bf (patch) | |
| tree | ee777f9c6c356d06f54c53758c274cb8ac776f75 | |
| parent | 2bbc79039df65ffb68d9529c2e2c39e572be89f3 (diff) | |
| download | bcm5719-llvm-69931c584141cf4c3c251ae4fa6a843935ed79bf.tar.gz bcm5719-llvm-69931c584141cf4c3c251ae4fa6a843935ed79bf.zip | |
[sanitizer_common] Add internal_clone().
Add a wrapper for the clone syscall for use in StopTheWorld. We
implement it only for x86_64, so stop building StopTheWorld for other platforms
(no one uses it outside x86_64 anyway).
See https://code.google.com/p/address-sanitizer/issues/detail?id=214 for why we
can't use the glibc clone() wrapper.
llvm-svn: 189753
4 files changed, 82 insertions, 14 deletions
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc index c7691d3a4ed..7e786c27b45 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc @@ -679,6 +679,69 @@ void ForEachMappedRegion(link_map *map, void (*cb)(const void *, uptr)) { } #endif +#if defined(__x86_64__) +// We cannot use glibc's clone wrapper, because it messes with the child +// task's TLS. It writes the PID and TID of the child task to its thread +// descriptor, but in our case the child task shares the thread descriptor with +// the parent (because we don't know how to allocate a new thread +// descriptor to keep glibc happy). So the stock version of clone(), when +// used with CLONE_VM, would end up corrupting the parent's thread descriptor. +uptr internal_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, + int *parent_tidptr, void *newtls, int *child_tidptr) { + long long res; + if (!fn || !child_stack) + return -EINVAL; + CHECK_EQ(0, (uptr)child_stack % 16); + child_stack = (char *)child_stack - 2 * sizeof(void *); + ((void **)child_stack)[0] = (void *)(uptr)fn; + ((void **)child_stack)[1] = arg; + __asm__ __volatile__( + /* %rax = syscall(%rax = __NR_clone, + * %rdi = flags, + * %rsi = child_stack, + * %rdx = parent_tidptr, + * %r8 = new_tls, + * %r10 = child_tidptr) + */ + "movq %6,%%r8\n" + "movq %7,%%r10\n" + ".cfi_endproc\n" + "syscall\n" + + /* if (%rax != 0) + * return; + */ + "testq %%rax,%%rax\n" + "jnz 1f\n" + + /* In the child. Terminate unwind chain. */ + ".cfi_startproc\n" + ".cfi_undefined %%rip;\n" + "xorq %%rbp,%%rbp\n" + + /* Call "fn(arg)". */ + "popq %%rax\n" + "popq %%rdi\n" + "call *%%rax\n" + + /* Call _exit(%rax). */ + "movq %%rax,%%rdi\n" + "movq %2,%%rax\n" + "syscall\n" + + /* Return to parent. */ + "1:\n" + : "=a" (res) + : "a"(__NR_clone), "i"(__NR_exit), + "S"(child_stack), + "D"(flags), + "d"(parent_tidptr), + "r"(newtls), + "r"(child_tidptr) + : "rsp", "memory", "r8", "r10", "r11", "rcx"); + return res; +} +#endif // defined(__x86_64__) } // namespace __sanitizer #endif // SANITIZER_LINUX diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux.h b/compiler-rt/lib/sanitizer_common/sanitizer_linux.h index edb95fecb8c..c0fba48da13 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_linux.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux.h @@ -29,6 +29,10 @@ uptr internal_getdents(fd_t fd, struct linux_dirent *dirp, unsigned int count); uptr internal_prctl(int option, uptr arg2, uptr arg3, uptr arg4, uptr arg5); uptr internal_sigaltstack(const struct sigaltstack* ss, struct sigaltstack* oss); +#ifdef __x86_64__ +uptr internal_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, + int *parent_tidptr, void *newtls, int *child_tidptr); +#endif // This class reads thread IDs from /proc/<pid>/task using only syscalls. class ThreadLister { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc index bcaf12a2f2d..3c650be5c57 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc @@ -14,12 +14,12 @@ #include "sanitizer_platform.h" -#if SANITIZER_LINUX +#if SANITIZER_LINUX && defined(__x86_64__) #include "sanitizer_stoptheworld.h" #include <errno.h> -#include <sched.h> // for clone +#include <sched.h> // for CLONE_* definitions #include <stddef.h> #include <sys/prctl.h> // for PR_* definitions #include <sys/ptrace.h> // for PTRACE_* definitions @@ -71,7 +71,6 @@ // after it has exited. The following functions are used in this manner: // sigdelset() // sigprocmask() -// clone() COMPILER_CHECK(sizeof(SuspendedThreadID) == sizeof(pid_t)); @@ -371,11 +370,14 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) { // Block the execution of TracerThread until after we have set ptrace // permissions. tracer_thread_argument.mutex.Lock(); - pid_t tracer_pid = clone(TracerThread, tracer_stack.Bottom(), - CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED, - &tracer_thread_argument); - if (tracer_pid < 0) { - Report("Failed spawning a tracer thread (errno %d).\n", errno); + uptr tracer_pid = internal_clone( + TracerThread, tracer_stack.Bottom(), + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_UNTRACED, + &tracer_thread_argument, 0 /* parent_tidptr */, 0 /* newtls */, 0 + /* child_tidptr */); + int local_errno = 0; + if (internal_iserror(tracer_pid, &local_errno)) { + Report("Failed spawning a tracer thread (errno %d).\n", local_errno); tracer_thread_argument.mutex.Unlock(); } else { // On some systems we have to explicitly declare that we want to be traced @@ -390,9 +392,8 @@ void StopTheWorld(StopTheWorldCallback callback, void *argument) { // At this point, any signal will either be blocked or kill us, so waitpid // should never return (and set errno) while the tracer thread is alive. uptr waitpid_status = internal_waitpid(tracer_pid, NULL, __WALL); - int wperrno; - if (internal_iserror(waitpid_status, &wperrno)) - Report("Waiting on the tracer thread failed (errno %d).\n", wperrno); + if (internal_iserror(waitpid_status, &local_errno)) + Report("Waiting on the tracer thread failed (errno %d).\n", local_errno); } } @@ -448,4 +449,4 @@ uptr SuspendedThreadsList::RegisterCount() { } } // namespace __sanitizer -#endif // SANITIZER_LINUX +#endif // SANITIZER_LINUX && defined(__x86_64__) diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc b/compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc index a5f8516df57..b6786ba8b01 100644 --- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cc @@ -12,7 +12,7 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_platform.h" -#if SANITIZER_LINUX +#if SANITIZER_LINUX && defined(__x86_64__) #include "sanitizer_common/sanitizer_stoptheworld.h" #include "gtest/gtest.h" @@ -191,4 +191,4 @@ TEST(StopTheWorld, SuspendThreadsAdvanced) { } // namespace __sanitizer -#endif // SANITIZER_LINUX +#endif // SANITIZER_LINUX && defined(__x86_64__) |

