tsan: intercept setjmp/longjmp
authorDmitry Vyukov <dvyukov@google.com>
Mon, 25 Mar 2013 10:10:44 +0000 (10:10 +0000)
committerDmitry Vyukov <dvyukov@google.com>
Mon, 25 Mar 2013 10:10:44 +0000 (10:10 +0000)
llvm-svn: 177858

compiler-rt/lib/tsan/lit_tests/longjmp.cc [new file with mode: 0644]
compiler-rt/lib/tsan/lit_tests/longjmp2.cc [new file with mode: 0644]
compiler-rt/lib/tsan/lit_tests/longjmp3.cc [new file with mode: 0644]
compiler-rt/lib/tsan/lit_tests/longjmp4.cc [new file with mode: 0644]
compiler-rt/lib/tsan/rtl/tsan_interceptors.cc
compiler-rt/lib/tsan/rtl/tsan_mman.h
compiler-rt/lib/tsan/rtl/tsan_rtl.cc
compiler-rt/lib/tsan/rtl/tsan_rtl.h
compiler-rt/lib/tsan/rtl/tsan_rtl_amd64.S
compiler-rt/lib/tsan/rtl/tsan_vector.h

diff --git a/compiler-rt/lib/tsan/lit_tests/longjmp.cc b/compiler-rt/lib/tsan/lit_tests/longjmp.cc
new file mode 100644 (file)
index 0000000..d9ca4ca
--- /dev/null
@@ -0,0 +1,22 @@
+// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+int foo(jmp_buf env) {
+  longjmp(env, 42);
+}
+
+int main() {
+  jmp_buf env;
+  if (setjmp(env) == 42) {
+    printf("JUMPED\n");
+    return 0;
+  }
+  foo(env);
+  printf("FAILED\n");
+  return 0;
+}
+
+// CHECK-NOT: FAILED
+// CHECK: JUMPED
diff --git a/compiler-rt/lib/tsan/lit_tests/longjmp2.cc b/compiler-rt/lib/tsan/lit_tests/longjmp2.cc
new file mode 100644 (file)
index 0000000..0d551fa
--- /dev/null
@@ -0,0 +1,24 @@
+// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+int foo(sigjmp_buf env) {
+  printf("env=%p\n", env);
+  siglongjmp(env, 42);
+}
+
+int main() {
+  sigjmp_buf env;
+  printf("env=%p\n", env);
+  if (sigsetjmp(env, 1) == 42) {
+    printf("JUMPED\n");
+    return 0;
+  }
+  foo(env);
+  printf("FAILED\n");
+  return 0;
+}
+
+// CHECK-NOT: FAILED
+// CHECK: JUMPED
diff --git a/compiler-rt/lib/tsan/lit_tests/longjmp3.cc b/compiler-rt/lib/tsan/lit_tests/longjmp3.cc
new file mode 100644 (file)
index 0000000..87fabd0
--- /dev/null
@@ -0,0 +1,48 @@
+// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+
+void bar(jmp_buf env) {
+  volatile int x = 42;
+  longjmp(env, 42);
+  x++;
+}
+
+void foo(jmp_buf env) {
+  volatile int x = 42;
+  bar(env);
+  x++;
+}
+
+void badguy() {
+  pthread_mutex_t mtx;
+  pthread_mutex_init(&mtx, 0);
+  pthread_mutex_lock(&mtx);
+  pthread_mutex_destroy(&mtx);
+}
+
+void mymain() {
+  jmp_buf env;
+  if (setjmp(env) == 42) {
+    badguy();
+    return;
+  }
+  foo(env);
+  printf("FAILED\n");
+}
+
+int main() {
+  volatile int x = 42;
+  mymain();
+  return x;
+}
+
+// CHECK-NOT: FAILED
+// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex
+// CHECK:   #0 pthread_mutex_destroy
+// CHECK:   #1 badguy
+// CHECK:   #2 mymain
+// CHECK:   #3 main
+
diff --git a/compiler-rt/lib/tsan/lit_tests/longjmp4.cc b/compiler-rt/lib/tsan/lit_tests/longjmp4.cc
new file mode 100644 (file)
index 0000000..a8764dd
--- /dev/null
@@ -0,0 +1,51 @@
+// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+
+void bar(jmp_buf env) {
+  volatile int x = 42;
+  jmp_buf env2;
+  memcpy(env2, env, sizeof(jmp_buf));
+  longjmp(env2, 42);
+  x++;
+}
+
+void foo(jmp_buf env) {
+  volatile int x = 42;
+  bar(env);
+  x++;
+}
+
+void badguy() {
+  pthread_mutex_t mtx;
+  pthread_mutex_init(&mtx, 0);
+  pthread_mutex_lock(&mtx);
+  pthread_mutex_destroy(&mtx);
+}
+
+void mymain() {
+  jmp_buf env;
+  if (setjmp(env) == 42) {
+    badguy();
+    return;
+  }
+  foo(env);
+  printf("FAILED\n");
+}
+
+int main() {
+  volatile int x = 42;
+  mymain();
+  return x;
+}
+
+// CHECK-NOT: FAILED
+// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex
+// CHECK:   #0 pthread_mutex_destroy
+// CHECK:   #1 badguy
+// CHECK:   #2 mymain
+// CHECK:   #3 main
+
index f32b666..240005e 100644 (file)
@@ -319,16 +319,98 @@ TSAN_INTERCEPTOR(int, __cxa_atexit, void (*f)(void *a), void *arg, void *dso) {
   return atexit_ctx->atexit(thr, pc, false, (void(*)())f, arg);
 }
 
-TSAN_INTERCEPTOR(void, longjmp, void *env, int val) {
-  SCOPED_TSAN_INTERCEPTOR(longjmp, env, val);
-  Printf("ThreadSanitizer: longjmp() is not supported\n");
-  Die();
+// Cleanup old bufs.
+static void JmpBufGarbageCollect(ThreadState *thr, uptr sp) {
+  for (uptr i = 0; i < thr->jmp_bufs.Size(); i++) {
+    JmpBuf *buf = &thr->jmp_bufs[i];
+    if (buf->sp <= sp) {
+      uptr sz = thr->jmp_bufs.Size();
+      thr->jmp_bufs[i] = thr->jmp_bufs[sz - 1];
+      thr->jmp_bufs.PopBack();
+      i--;
+    }
+  }
 }
 
-TSAN_INTERCEPTOR(void, siglongjmp, void *env, int val) {
-  SCOPED_TSAN_INTERCEPTOR(siglongjmp, env, val);
-  Printf("ThreadSanitizer: siglongjmp() is not supported\n");
-  Die();
+static void SetJmp(ThreadState *thr, uptr sp, uptr mangled_sp) {
+  if (thr->shadow_stack_pos == 0)  // called from libc guts during bootstrap
+    return;
+  // Cleanup old bufs.
+  JmpBufGarbageCollect(thr, sp);
+  // Remember the buf.
+  JmpBuf *buf = thr->jmp_bufs.PushBack();
+  buf->sp = sp;
+  buf->mangled_sp = mangled_sp;
+  buf->shadow_stack_pos = thr->shadow_stack_pos;
+}
+
+static void LongJmp(ThreadState *thr, uptr *env) {
+  uptr mangled_sp = env[6];
+  // Find the saved buf by mangled_sp.
+  for (uptr i = 0; i < thr->jmp_bufs.Size(); i++) {
+    JmpBuf *buf = &thr->jmp_bufs[i];
+    if (buf->mangled_sp == mangled_sp) {
+      CHECK_GE(thr->shadow_stack_pos, buf->shadow_stack_pos);
+      // Unwind the stack.
+      while (thr->shadow_stack_pos > buf->shadow_stack_pos)
+        FuncExit(thr);
+      JmpBufGarbageCollect(thr, buf->sp - 1);  // do not collect buf->sp
+      return;
+    }
+  }
+  Printf("ThreadSanitizer: can't find longjmp buf\n");
+  CHECK(0);
+}
+
+extern "C" void __tsan_setjmp(uptr sp, uptr mangled_sp) {
+  ScopedInRtl in_rtl;
+  SetJmp(cur_thread(), sp, mangled_sp);
+}
+
+// Not called.  Merely to satisfy TSAN_INTERCEPT().
+extern "C" int __interceptor_setjmp(void *env) {
+  CHECK(0);
+  return 0;
+}
+
+extern "C" int __interceptor__setjmp(void *env) {
+  CHECK(0);
+  return 0;
+}
+
+extern "C" int __interceptor_sigsetjmp(void *env) {
+  CHECK(0);
+  return 0;
+}
+
+extern "C" int __interceptor___sigsetjmp(void *env) {
+  CHECK(0);
+  return 0;
+}
+
+extern "C" int setjmp(void *env);
+extern "C" int _setjmp(void *env);
+extern "C" int sigsetjmp(void *env);
+extern "C" int __sigsetjmp(void *env);
+DEFINE_REAL(int, setjmp, void *env)
+DEFINE_REAL(int, _setjmp, void *env)
+DEFINE_REAL(int, sigsetjmp, void *env)
+DEFINE_REAL(int, __sigsetjmp, void *env)
+
+TSAN_INTERCEPTOR(void, longjmp, uptr *env, int val) {
+  {
+    SCOPED_TSAN_INTERCEPTOR(longjmp, env, val);
+  }
+  LongJmp(cur_thread(), env);
+  REAL(longjmp)(env, val);
+}
+
+TSAN_INTERCEPTOR(void, siglongjmp, uptr *env, int val) {
+  {
+    SCOPED_TSAN_INTERCEPTOR(siglongjmp, env, val);
+  }
+  LongJmp(cur_thread(), env);
+  REAL(siglongjmp)(env, val);
 }
 
 TSAN_INTERCEPTOR(void*, malloc, uptr size) {
@@ -1853,6 +1935,10 @@ void InitializeInterceptors() {
 
   SANITIZER_COMMON_INTERCEPTORS_INIT;
 
+  TSAN_INTERCEPT(setjmp);
+  TSAN_INTERCEPT(_setjmp);
+  TSAN_INTERCEPT(sigsetjmp);
+  TSAN_INTERCEPT(__sigsetjmp);
   TSAN_INTERCEPT(longjmp);
   TSAN_INTERCEPT(siglongjmp);
 
index 4a9240f..19d5554 100644 (file)
@@ -63,6 +63,7 @@ enum MBlockType {
   MBlockExpectRace,
   MBlockSignal,
   MBlockFD,
+  MBlockJmpBuf,
 
   // This must be the last.
   MBlockTypeCount
index 19722d0..d67c912 100644 (file)
@@ -88,6 +88,9 @@ ThreadState::ThreadState(Context *ctx, int tid, int unique_id, u64 epoch,
   // , fast_ignore_writes()
   // , in_rtl()
   , shadow_stack_pos(&shadow_stack[0])
+#ifndef TSAN_GO
+  , jmp_bufs(MBlockJmpBuf)
+#endif
   , tid(tid)
   , unique_id(unique_id)
   , stk_addr(stk_addr)
index 3af0402..80ec302 100644 (file)
@@ -384,6 +384,12 @@ class Shadow : public FastState {
 
 struct SignalContext;
 
+struct JmpBuf {
+  uptr sp;
+  uptr mangled_sp;
+  uptr *shadow_stack_pos;
+};
+
 // This struct is stored in TLS.
 struct ThreadState {
   FastState fast_state;
@@ -418,6 +424,7 @@ struct ThreadState {
   ThreadClock clock;
 #ifndef TSAN_GO
   AllocatorCache alloc_cache;
+  Vector<JmpBuf> jmp_bufs;
 #endif
   u64 stat[StatCnt];
   const int tid;
index af87856..11c75c7 100644 (file)
@@ -160,6 +160,143 @@ __tsan_report_race_thunk:
   ret
   .cfi_endproc
 
+.hidden __tsan_setjmp
+.comm _ZN14__interception11real_setjmpE,8,8
+.globl setjmp
+.type setjmp, @function
+setjmp:
+  .cfi_startproc
+  // save env parameter
+  push %rdi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rdi, 0
+  // obtain %rsp
+  lea 16(%rsp), %rdi
+  mov %rdi, %rsi
+  xor %fs:0x30, %rsi  // magic mangling of rsp (see libc setjmp)
+  rol $0x11, %rsi
+  // call tsan interceptor
+  call __tsan_setjmp
+  // restore env parameter
+  pop %rdi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rdi
+  // tail jump to libc setjmp
+  movl $0, %eax
+  movq _ZN14__interception11real_setjmpE@GOTPCREL(%rip), %rdx
+  jmp *(%rdx)
+  .cfi_endproc
+.size setjmp, .-setjmp
+
+.comm _ZN14__interception12real__setjmpE,8,8
+.globl _setjmp
+.type _setjmp, @function
+_setjmp:
+  .cfi_startproc
+  // save env parameter
+  push %rdi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rdi, 0
+  // obtain %rsp
+  lea 16(%rsp), %rdi
+  mov %rdi, %rsi
+  xor %fs:0x30, %rsi  // magic mangling of rsp (see libc setjmp)
+  rol $0x11, %rsi
+  // call tsan interceptor
+  call __tsan_setjmp
+  // restore env parameter
+  pop %rdi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rdi
+  // tail jump to libc setjmp
+  movl $0, %eax
+  movq _ZN14__interception12real__setjmpE@GOTPCREL(%rip), %rdx
+  jmp *(%rdx)
+  .cfi_endproc
+.size _setjmp, .-_setjmp
+
+.comm _ZN14__interception14real_sigsetjmpE,8,8
+.globl sigsetjmp
+.type sigsetjmp, @function
+sigsetjmp:
+  .cfi_startproc
+  // save env parameter
+  push %rdi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rdi, 0
+  // save savesigs parameter
+  push %rsi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rsi, 0
+  // align stack frame
+  sub $8, %rsp
+  .cfi_adjust_cfa_offset 8
+  // obtain %rsp
+  lea 32(%rsp), %rdi
+  mov %rdi, %rsi
+  xor %fs:0x30, %rsi  // magic mangling of rsp (see libc setjmp)
+  rol $0x11, %rsi
+  // call tsan interceptor
+  call __tsan_setjmp
+  // unalign stack frame
+  add $8, %rsp
+  .cfi_adjust_cfa_offset -8
+  // restore savesigs parameter
+  pop %rsi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rsi
+  // restore env parameter
+  pop %rdi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rdi
+  // tail jump to libc sigsetjmp
+  movl $0, %eax
+  movq _ZN14__interception14real_sigsetjmpE@GOTPCREL(%rip), %rdx
+  jmp *(%rdx)
+  .cfi_endproc
+.size sigsetjmp, .-sigsetjmp
+
+.comm _ZN14__interception16real___sigsetjmpE,8,8
+.globl __sigsetjmp
+.type __sigsetjmp, @function
+__sigsetjmp:
+  .cfi_startproc
+  // save env parameter
+  push %rdi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rdi, 0
+  // save savesigs parameter
+  push %rsi
+  .cfi_adjust_cfa_offset 8
+  .cfi_rel_offset %rsi, 0
+  // align stack frame
+  sub $8, %rsp
+  .cfi_adjust_cfa_offset 8
+  // obtain %rsp
+  lea 32(%rsp), %rdi
+  mov %rdi, %rsi
+  xor %fs:0x30, %rsi  // magic mangling of rsp (see libc setjmp)
+  rol $0x11, %rsi
+  // call tsan interceptor
+  call __tsan_setjmp
+  // unalign stack frame
+  add $8, %rsp
+  .cfi_adjust_cfa_offset -8
+  // restore savesigs parameter
+  pop %rsi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rsi
+  // restore env parameter
+  pop %rdi
+  .cfi_adjust_cfa_offset -8
+  .cfi_restore %rdi
+  // tail jump to libc sigsetjmp
+  movl $0, %eax
+  movq _ZN14__interception16real___sigsetjmpE@GOTPCREL(%rip), %rdx
+  jmp *(%rdx)
+  .cfi_endproc
+.size __sigsetjmp, .-__sigsetjmp
+
 #ifdef __linux__
 /* We do not need executable stack.  */
 .section        .note.GNU-stack,"",@progbits
index 64328d0..fa236b1 100644 (file)
@@ -64,6 +64,11 @@ class Vector {
     return &end_[-1];
   }
 
+  void PopBack() {
+    DCHECK_GT(end_, begin_);
+    end_--;
+  }
+
   void Resize(uptr size) {
     uptr old_size = Size();
     EnsureSize(size);