Merge "Fix lookup logic for linked namespaces"
diff --git a/libc/SECCOMP_WHITELIST.TXT b/libc/SECCOMP_WHITELIST.TXT
index f0e99e3..ab6ce0b 100644
--- a/libc/SECCOMP_WHITELIST.TXT
+++ b/libc/SECCOMP_WHITELIST.TXT
@@ -100,3 +100,6 @@
 
 # b/36449658
 int	rename(const char *oldpath, const char *newpath)	arm,x86,mips
+
+# b/36726183. Note arm does not support mmap
+void*	mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)	x86,mips
diff --git a/libc/bionic/libc_init_common.cpp b/libc/bionic/libc_init_common.cpp
index e5654c3..9094fc5 100644
--- a/libc/bionic/libc_init_common.cpp
+++ b/libc/bionic/libc_init_common.cpp
@@ -62,7 +62,11 @@
 char** environ;
 
 #if defined(__i386__)
-__LIBC_HIDDEN__ void* __libc_sysinfo = nullptr;
+__attribute__((__naked__)) static void __libc_int0x80() {
+  __asm__ volatile("int $0x80; ret");
+}
+
+__LIBC_HIDDEN__ void* __libc_sysinfo = reinterpret_cast<void*>(__libc_int0x80);
 
 __LIBC_HIDDEN__ void __libc_init_sysinfo(KernelArgumentBlock& args) {
   __libc_sysinfo = reinterpret_cast<void*>(args.getauxval(AT_SYSINFO));
diff --git a/libc/include/math.h b/libc/include/math.h
index 8bf6fb5..7dd1539 100644
--- a/libc/include/math.h
+++ b/libc/include/math.h
@@ -153,6 +153,20 @@
 long	lrint(double);
 long	lround(double);
 
+/*
+ * https://code.google.com/p/android/issues/detail?id=271629
+ * To be fully compliant with C++, we need to not define these (C doesn't
+ * specify them either). Exposing these means that isinf and isnan will have a
+ * return type of int in C++ rather than bool like they're supposed to be.
+ *
+ * GNU libstdc++ 4.9 isn't able to handle a standard compliant C library. Its
+ * <cmath> will `#undef isnan` from math.h and only adds the function overloads
+ * to the std namespace, making it impossible to use both <cmath> (which gets
+ * included by a lot of other standard headers) and ::isnan.
+ */
+int(isinf)(double) __attribute_const__ __INTRODUCED_IN(21);
+int	(isnan)(double) __attribute_const__;
+
 double nan(const char*) __attribute_const__ __INTRODUCED_IN_ARM(13) __INTRODUCED_IN_MIPS(13)
     __INTRODUCED_IN_X86(9);
 
diff --git a/libc/seccomp/mips_policy.cpp b/libc/seccomp/mips_policy.cpp
index 01323ce..d775b66 100644
--- a/libc/seccomp/mips_policy.cpp
+++ b/libc/seccomp/mips_policy.cpp
@@ -44,11 +44,11 @@
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4074, 1, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4072, 71, 70), //setreuid|setregid
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4082, 70, 69), //sethostname|setrlimit|getrlimit|getrusage|gettimeofday|settimeofday|getgroups|setgroups
-BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4091, 3, 0),
+BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4090, 3, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4087, 1, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4086, 67, 66), //readlink
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4089, 66, 65), //swapon|reboot
-BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4093, 65, 64), //munmap|truncate
+BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4093, 65, 64), //mmap|munmap|truncate
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4118, 5, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4114, 3, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 4103, 1, 0),
diff --git a/libc/seccomp/x86_policy.cpp b/libc/seccomp/x86_policy.cpp
index d9ee17b..e29f8bf 100644
--- a/libc/seccomp/x86_policy.cpp
+++ b/libc/seccomp/x86_policy.cpp
@@ -44,11 +44,11 @@
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 77, 1, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 76, 73, 72), //sethostname|setrlimit
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 80, 72, 71), //getrusage|gettimeofday|settimeofday
-BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 91, 3, 0),
+BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 90, 3, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 87, 1, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 86, 69, 68), //readlink
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 89, 68, 67), //swapon|reboot
-BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 93, 67, 66), //munmap|truncate
+BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 93, 67, 66), //mmap|munmap|truncate
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 118, 7, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 102, 3, 0),
 BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 96, 1, 0),
diff --git a/libc/tzcode/localtime.c b/libc/tzcode/localtime.c
index 723e4f2..8d5cd07 100644
--- a/libc/tzcode/localtime.c
+++ b/libc/tzcode/localtime.c
@@ -2353,9 +2353,9 @@
 #include <stdint.h>
 #include <arpa/inet.h> // For ntohl(3).
 
-static int __bionic_open_tzdata_path(const char* path_prefix_variable, const char* path_suffix,
-                                     const char* olson_id,
-                                     int32_t* entry_length) {
+#if !defined(__ANDROID__)
+static char* make_path(const char* path_prefix_variable,
+                       const char* path_suffix) {
   const char* path_prefix = getenv(path_prefix_variable);
   if (path_prefix == NULL) {
     fprintf(stderr, "%s: %s not set!\n", __FUNCTION__, path_prefix_variable);
@@ -2368,9 +2368,15 @@
     return -1;
   }
   snprintf(path, path_length, "%s/%s", path_prefix, path_suffix);
+  return path;
+}
+#endif
+
+static int __bionic_open_tzdata_path(const char* path,
+                                     const char* olson_id,
+                                     int32_t* entry_length) {
   int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC));
   if (fd == -1) {
-    free(path);
     return -2; // Distinguish failure to find any data from failure to find a specific id.
   }
 
@@ -2389,7 +2395,6 @@
   if (bytes_read != sizeof(header)) {
     fprintf(stderr, "%s: could not read header of \"%s\": %s\n",
             __FUNCTION__, path, (bytes_read == -1) ? strerror(errno) : "short read");
-    free(path);
     close(fd);
     return -1;
   }
@@ -2397,7 +2402,6 @@
   if (strncmp(header.tzdata_version, "tzdata", 6) != 0 || header.tzdata_version[11] != 0) {
     fprintf(stderr, "%s: bad magic in \"%s\": \"%.6s\"\n",
             __FUNCTION__, path, header.tzdata_version);
-    free(path);
     close(fd);
     return -1;
   }
@@ -2412,7 +2416,6 @@
   if (TEMP_FAILURE_RETRY(lseek(fd, ntohl(header.index_offset), SEEK_SET)) == -1) {
     fprintf(stderr, "%s: couldn't seek to index in \"%s\": %s\n",
             __FUNCTION__, path, strerror(errno));
-    free(path);
     close(fd);
     return -1;
   }
@@ -2423,14 +2426,12 @@
   if (index == NULL) {
     fprintf(stderr, "%s: couldn't allocate %zd-byte index for \"%s\"\n",
             __FUNCTION__, index_size, path);
-    free(path);
     close(fd);
     return -1;
   }
   if (TEMP_FAILURE_RETRY(read(fd, index, index_size)) != index_size) {
     fprintf(stderr, "%s: could not read index of \"%s\": %s\n",
             __FUNCTION__, path, (bytes_read == -1) ? strerror(errno) : "short read");
-    free(path);
     free(index);
     close(fd);
     return -1;
@@ -2462,7 +2463,6 @@
   free(index);
 
   if (specific_zone_offset == -1) {
-    free(path);
     close(fd);
     return -1;
   }
@@ -2470,29 +2470,50 @@
   if (TEMP_FAILURE_RETRY(lseek(fd, specific_zone_offset, SEEK_SET)) == -1) {
     fprintf(stderr, "%s: could not seek to %ld in \"%s\": %s\n",
             __FUNCTION__, specific_zone_offset, path, strerror(errno));
-    free(path);
     close(fd);
     return -1;
   }
 
   // TODO: check that there's TZ_MAGIC at this offset, so we can fall back to the other file if not.
 
-  free(path);
   return fd;
 }
 
 static int __bionic_open_tzdata(const char* olson_id, int32_t* entry_length) {
-  int fd = __bionic_open_tzdata_path("ANDROID_DATA", "/misc/zoneinfo/current/tzdata",
-                                     olson_id, entry_length);
-  if (fd < 0) {
-    fd = __bionic_open_tzdata_path("ANDROID_ROOT", "/usr/share/zoneinfo/tzdata",
-                                   olson_id, entry_length);
-    if (fd == -2) {
-      // The first thing that 'recovery' does is try to format the current time. It doesn't have
-      // any tzdata available, so we must not abort here --- doing so breaks the recovery image!
-      fprintf(stderr, "%s: couldn't find any tzdata when looking for %s!\n", __FUNCTION__, olson_id);
-    }
+  int fd;
+
+#if defined(__ANDROID__)
+  // On Android, try the two hard-coded locations.
+  fd = __bionic_open_tzdata_path("/data/misc/zoneinfo/current/tzdata",
+                                 olson_id, entry_length);
+  if (fd >= 0) return fd;
+
+  fd = __bionic_open_tzdata_path("/system/usr/share/zoneinfo/tzdata",
+                                 olson_id, entry_length);
+  if (fd >= 0) return fd;
+#else
+  // On the host, we don't expect those locations to exist, and we're not
+  // worried about security so we trust $ANDROID_DATA and $ANDROID_ROOT to
+  // point us in the right direction.
+  char* path = make_path("ANDROID_DATA", "/misc/zoneinfo/current/tzdata");
+  fd = __bionic_open_tzdata_path(path, olson_id, entry_length);
+  free(path);
+  if (fd >= 0) return fd;
+
+  path = make_path("ANDROID_ROOT", "/usr/share/zoneinfo/tzdata");
+  fd = __bionic_open_tzdata_path(path, olson_id, entry_length);
+  free(path);
+  if (fd >= 0) return fd;
+#endif
+
+  // Not finding any tzdata is more serious that not finding a specific zone,
+  // and worth logging.
+  if (fd == -2) {
+    // The first thing that 'recovery' does is try to format the current time. It doesn't have
+    // any tzdata available, so we must not abort here --- doing so breaks the recovery image!
+    fprintf(stderr, "%s: couldn't find any tzdata when looking for %s!\n", __FUNCTION__, olson_id);
   }
+
   return fd;
 }
 
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 497fc2d..1647db7 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -83,15 +83,17 @@
 #if defined(__LP64__)
 static const char* const kSystemLibDir     = "/system/lib64";
 static const char* const kVendorLibDir     = "/vendor/lib64";
-static const char* const kAsanSystemLibDir = "/data/lib64";
-static const char* const kAsanVendorLibDir = "/data/vendor/lib64";
+static const char* const kAsanSystemLibDir = "/data/asan/system/lib64";
+static const char* const kAsanVendorLibDir = "/data/asan/vendor/lib64";
 #else
 static const char* const kSystemLibDir     = "/system/lib";
 static const char* const kVendorLibDir     = "/vendor/lib";
-static const char* const kAsanSystemLibDir = "/data/lib";
-static const char* const kAsanVendorLibDir = "/data/vendor/lib";
+static const char* const kAsanSystemLibDir = "/data/asan/system/lib";
+static const char* const kAsanVendorLibDir = "/data/asan/vendor/lib";
 #endif
 
+static const char* const kAsanLibDirPrefix = "/data/asan";
+
 static const char* const kDefaultLdPaths[] = {
   kSystemLibDir,
   kVendorLibDir,
@@ -1946,20 +1948,10 @@
   if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
     char translated_path[PATH_MAX];
     if (realpath(translated_name, translated_path) != nullptr) {
-      if (file_is_under_dir(translated_path, kSystemLibDir)) {
-        asan_name_holder = std::string(kAsanSystemLibDir) + "/" +
-            (translated_path + strlen(kSystemLibDir) + 1);
-        if (file_exists(asan_name_holder.c_str())) {
-          translated_name = asan_name_holder.c_str();
-          PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
-        }
-      } else if (file_is_under_dir(translated_path, kVendorLibDir)) {
-        asan_name_holder = std::string(kAsanVendorLibDir) + "/" +
-            (translated_path + strlen(kVendorLibDir) + 1);
-        if (file_exists(asan_name_holder.c_str())) {
-          translated_name = asan_name_holder.c_str();
-          PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
-        }
+      asan_name_holder = std::string(kAsanLibDirPrefix) + translated_path;
+      if (file_exists(asan_name_holder.c_str())) {
+        translated_name = asan_name_holder.c_str();
+        PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
       }
     }
   }
diff --git a/tests/dl_test.cpp b/tests/dl_test.cpp
index ee9b2e1..aa8bd57 100644
--- a/tests/dl_test.cpp
+++ b/tests/dl_test.cpp
@@ -24,6 +24,7 @@
 
 #include <string>
 
+#include "gtest_globals.h"
 #include "utils.h"
 
 extern "C" int main_global_default_serial() {
@@ -86,4 +87,26 @@
 #endif
 }
 
+TEST(dl, preinit_system_calls) {
+#if defined(__BIONIC__)
+  std::string helper = get_testlib_root() +
+      "/preinit_syscall_test_helper/preinit_syscall_test_helper";
+  chmod(helper.c_str(), 0755); // TODO: "x" lost in CTS, b/34945607
+  ExecTestHelper eth;
+  eth.SetArgs({ helper.c_str(), nullptr });
+  eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
+#endif
+}
+
+TEST(dl, xfail_preinit_getauxval) {
+#if defined(__BIONIC__)
+  std::string helper = get_testlib_root() +
+      "/preinit_getauxval_test_helper/preinit_getauxval_test_helper";
+  chmod(helper.c_str(), 0755); // TODO: "x" lost in CTS, b/34945607
+  ExecTestHelper eth;
+  eth.SetArgs({ helper.c_str(), nullptr });
+  eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
+#endif
+}
+
 // TODO: Add tests for LD_PRELOADs
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 5eb16c5..a031fe9 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -547,3 +547,17 @@
     shared_libs: ["libcfi-test"],
     ldflags: ["-Wl,--rpath,${ORIGIN}/.."],
 }
+
+cc_test {
+    name: "preinit_getauxval_test_helper",
+    host_supported: false,
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["preinit_getauxval_test_helper.cpp"],
+}
+
+cc_test {
+    name: "preinit_syscall_test_helper",
+    host_supported: false,
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["preinit_syscall_test_helper.cpp"],
+}
diff --git a/tests/libs/libs_utils.h b/tests/libs/libs_utils.h
index f11cbe7..7dae241 100644
--- a/tests/libs/libs_utils.h
+++ b/tests/libs/libs_utils.h
@@ -17,13 +17,11 @@
 #ifndef LIBS_UTILS_H
 #define LIBS_UTILS_H
 
+#include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 
-#define CHECK(x)                                                            \
-  do {                                                                      \
-    fprintf(stderr, "CHECK(" #x ") failed at %s:%d\n", __FILE__, __LINE__); \
-    if (!(x)) abort();                                                      \
-  } while (0)
+#define CHECK(e) \
+    ((e) ? static_cast<void>(0) : __assert2(__FILE__, __LINE__, __PRETTY_FUNCTION__, #e))
 
 #endif  // LIBS_UTILS_H
diff --git a/tests/libs/preinit_getauxval_test_helper.cpp b/tests/libs/preinit_getauxval_test_helper.cpp
new file mode 100644
index 0000000..2a79b97
--- /dev/null
+++ b/tests/libs/preinit_getauxval_test_helper.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+
+#include "libs_utils.h"
+
+static unsigned long g_AT_RANDOM;
+static unsigned long g_AT_PAGESZ;
+
+static void preinit_ctor() {
+  g_AT_RANDOM = getauxval(AT_RANDOM);
+  g_AT_PAGESZ = getauxval(AT_PAGESZ);
+}
+
+__attribute__((section(".preinit_array"), used)) void (*preinit_ctor_p)(void) = preinit_ctor;
+
+int main() {
+  // Did getauxval during preinit get the same results as getauxval now?
+  CHECK(getauxval(AT_RANDOM) == g_AT_RANDOM);
+  CHECK(getauxval(AT_PAGESZ) == g_AT_PAGESZ);
+  return 0;
+}
diff --git a/tests/libs/preinit_syscall_test_helper.cpp b/tests/libs/preinit_syscall_test_helper.cpp
new file mode 100644
index 0000000..9b6b6df
--- /dev/null
+++ b/tests/libs/preinit_syscall_test_helper.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+
+#include "libs_utils.h"
+
+static ssize_t g_result;
+static int g_errno;
+
+static void preinit_ctor() {
+  // Can we make a system call?
+  g_result = write(-1, "", 1);
+  g_errno = errno;
+}
+
+__attribute__((section(".preinit_array"), used)) void (*preinit_ctor_p)(void) = preinit_ctor;
+
+int main() {
+  // Did we get the expected failure?
+  CHECK(g_result == -1);
+  CHECK(g_errno == EBADF);
+  return 0;
+}
diff --git a/tests/sys_ptrace_test.cpp b/tests/sys_ptrace_test.cpp
index bce5898..69638be 100644
--- a/tests/sys_ptrace_test.cpp
+++ b/tests/sys_ptrace_test.cpp
@@ -17,6 +17,7 @@
 #include <sys/ptrace.h>
 
 #include <elf.h>
+#include <err.h>
 #include <fcntl.h>
 #include <sched.h>
 #include <sys/prctl.h>
@@ -26,11 +27,16 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <chrono>
+#include <thread>
+
 #include <gtest/gtest.h>
 
 #include <android-base/macros.h>
 #include <android-base/unique_fd.h>
 
+using namespace std::chrono_literals;
+
 using android::base::unique_fd;
 
 // Host libc does not define this.
@@ -367,31 +373,39 @@
 
 class PtraceResumptionTest : public ::testing::Test {
  public:
+  unique_fd worker_pipe_write;
+
   pid_t worker = -1;
+  pid_t tracer = -1;
+
   PtraceResumptionTest() {
+    unique_fd worker_pipe_read;
+    int pipefd[2];
+    if (pipe2(pipefd, O_CLOEXEC) != 0) {
+      err(1, "failed to create pipe");
+    }
+
+    worker_pipe_read.reset(pipefd[0]);
+    worker_pipe_write.reset(pipefd[1]);
+
+    worker = fork();
+    if (worker == -1) {
+      err(1, "failed to fork worker");
+    } else if (worker == 0) {
+      char buf;
+      worker_pipe_write.reset();
+      TEMP_FAILURE_RETRY(read(worker_pipe_read.get(), &buf, sizeof(buf)));
+      exit(0);
+    }
   }
 
   ~PtraceResumptionTest() {
   }
 
   void AssertDeath(int signo);
-  void Start(std::function<void()> f) {
-    unique_fd worker_pipe_read, worker_pipe_write;
-    int pipefd[2];
-    ASSERT_EQ(0, pipe2(pipefd, O_CLOEXEC));
-    worker_pipe_read.reset(pipefd[0]);
-    worker_pipe_write.reset(pipefd[1]);
 
-    worker = fork();
-    ASSERT_NE(-1, worker);
-    if (worker == 0) {
-      char buf;
-      worker_pipe_write.reset();
-      TEMP_FAILURE_RETRY(read(worker_pipe_read.get(), &buf, sizeof(buf)));
-      exit(0);
-    }
-
-    pid_t tracer = fork();
+  void StartTracer(std::function<void()> f) {
+    tracer = fork();
     ASSERT_NE(-1, tracer);
     if (tracer == 0) {
       f();
@@ -400,26 +414,66 @@
       }
       exit(0);
     }
+  }
+
+  bool WaitForTracer() {
+    if (tracer == -1) {
+      errx(1, "tracer not started");
+    }
 
     int result;
     pid_t rc = waitpid(tracer, &result, 0);
-    ASSERT_EQ(tracer, rc);
-    EXPECT_TRUE(WIFEXITED(result) || WIFSIGNALED(result));
+    if (rc != tracer) {
+      printf("waitpid returned %d (%s)\n", rc, strerror(errno));
+      return false;
+    }
+
+    if (!WIFEXITED(result) && !WIFSIGNALED(result)) {
+      printf("!WIFEXITED && !WIFSIGNALED\n");
+      return false;
+    }
+
     if (WIFEXITED(result)) {
       if (WEXITSTATUS(result) != 0) {
-        FAIL() << "tracer failed";
+        printf("tracer failed\n");
+        return false;
       }
     }
 
-    rc = waitpid(worker, &result, WNOHANG);
-    ASSERT_EQ(0, rc);
+    return true;
+  }
+
+  bool WaitForWorker() {
+    if (worker == -1) {
+      errx(1, "worker not started");
+    }
+
+    int result;
+    pid_t rc = waitpid(worker, &result, WNOHANG);
+    if (rc != 0) {
+      printf("worker exited prematurely\n");
+      return false;
+    }
 
     worker_pipe_write.reset();
 
     rc = waitpid(worker, &result, 0);
-    ASSERT_EQ(worker, rc);
-    EXPECT_TRUE(WIFEXITED(result));
-    EXPECT_EQ(WEXITSTATUS(result), 0);
+    if (rc != worker) {
+      printf("waitpid for worker returned %d (%s)\n", rc, strerror(errno));
+      return false;
+    }
+
+    if (!WIFEXITED(result)) {
+      printf("worker didn't exit\n");
+      return false;
+    }
+
+    if (WEXITSTATUS(result) != 0) {
+      printf("worker exited with status %d\n", WEXITSTATUS(result));
+      return false;
+    }
+
+    return true;
   }
 };
 
@@ -436,22 +490,74 @@
   }
 }
 
+TEST_F(PtraceResumptionTest, smoke) {
+  // Make sure that the worker doesn't exit before the tracer stops tracing.
+  StartTracer([this]() {
+    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
+    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
+    wait_for_ptrace_stop(worker);
+    std::this_thread::sleep_for(500ms);
+  });
+
+  worker_pipe_write.reset();
+  std::this_thread::sleep_for(250ms);
+
+  int result;
+  ASSERT_EQ(0, waitpid(worker, &result, WNOHANG));
+  ASSERT_TRUE(WaitForTracer());
+  ASSERT_EQ(worker, waitpid(worker, &result, 0));
+}
+
 TEST_F(PtraceResumptionTest, seize) {
-  Start([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
+  StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
+  ASSERT_TRUE(WaitForTracer());
+  ASSERT_TRUE(WaitForWorker());
 }
 
 TEST_F(PtraceResumptionTest, seize_interrupt) {
-  Start([this]() {
+  StartTracer([this]() {
     ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
     ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
+    wait_for_ptrace_stop(worker);
   });
+  ASSERT_TRUE(WaitForTracer());
+  ASSERT_TRUE(WaitForWorker());
 }
 
 TEST_F(PtraceResumptionTest, seize_interrupt_cont) {
-  Start([this]() {
+  StartTracer([this]() {
     ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
     ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
     wait_for_ptrace_stop(worker);
     ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 0, 0)) << strerror(errno);
   });
+  ASSERT_TRUE(WaitForTracer());
+  ASSERT_TRUE(WaitForWorker());
+}
+
+TEST_F(PtraceResumptionTest, zombie_seize) {
+  StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
+  ASSERT_TRUE(WaitForWorker());
+  ASSERT_TRUE(WaitForTracer());
+}
+
+TEST_F(PtraceResumptionTest, zombie_seize_interrupt) {
+  StartTracer([this]() {
+    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
+    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
+    wait_for_ptrace_stop(worker);
+  });
+  ASSERT_TRUE(WaitForWorker());
+  ASSERT_TRUE(WaitForTracer());
+}
+
+TEST_F(PtraceResumptionTest, zombie_seize_interrupt_cont) {
+  StartTracer([this]() {
+    ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
+    ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
+    wait_for_ptrace_stop(worker);
+    ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 0, 0)) << strerror(errno);
+  });
+  ASSERT_TRUE(WaitForWorker());
+  ASSERT_TRUE(WaitForTracer());
 }