Project import
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
new file mode 100644
index 0000000..216ff9d
--- /dev/null
+++ b/memory_replay/Action.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 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 <inttypes.h>
+#include <malloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <time.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Threads.h"
+#include "Pointers.h"
+
+static uint64_t nanotime() {
+  struct timespec t;
+  t.tv_sec = t.tv_nsec = 0;
+  clock_gettime(CLOCK_MONOTONIC, &t);
+  return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
+}
+
+class EndThreadAction : public Action {
+ public:
+  EndThreadAction() {}
+
+  bool EndThread() override { return true; }
+
+  uint64_t Execute(Pointers*) override { return 0; }
+};
+
+class AllocAction : public Action {
+ public:
+  explicit AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
+
+ protected:
+  uintptr_t key_pointer_ = 0;
+  size_t size_ = 0;
+};
+
+class MallocAction : public AllocAction {
+ public:
+  MallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu", &size_) != 1) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = malloc(size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+};
+
+class CallocAction : public AllocAction {
+ public:
+  CallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu %zu", &n_elements_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = calloc(n_elements_, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 0, n_elements_ * size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  size_t n_elements_ = 0;
+};
+
+class ReallocAction : public AllocAction {
+ public:
+  ReallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%" SCNxPTR " %zu", &old_pointer_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  bool DoesFree() override { return old_pointer_ != 0; }
+
+  uint64_t Execute(Pointers* pointers) override {
+    void* old_memory = nullptr;
+    if (old_pointer_ != 0) {
+      old_memory = pointers->Remove(old_pointer_);
+    }
+
+    uint64_t time_nsecs = nanotime();
+    void* memory = realloc(old_memory, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  uintptr_t old_pointer_ = 0;
+};
+
+class MemalignAction : public AllocAction {
+ public:
+  MemalignAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) {
+    if (sscanf(line, "%zu %zu", &align_, &size_) != 2) {
+      is_error_ = true;
+    }
+  }
+
+  uint64_t Execute(Pointers* pointers) override {
+    uint64_t time_nsecs = nanotime();
+    void* memory = memalign(align_, size_);
+    time_nsecs = nanotime() - time_nsecs;
+
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+
+    return time_nsecs;
+  }
+
+ private:
+  size_t align_ = 0;
+};
+
+class FreeAction : public AllocAction {
+ public:
+  explicit FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+  }
+
+  bool DoesFree() override { return key_pointer_ != 0; }
+
+  uint64_t Execute(Pointers* pointers) override {
+    if (key_pointer_) {
+      void* memory = pointers->Remove(key_pointer_);
+      uint64_t time_nsecs = nanotime();
+      free(memory);
+      return nanotime() - time_nsecs;
+    }
+    return 0;
+  }
+};
+
+size_t Action::MaxActionSize() {
+  size_t max = MAX(sizeof(EndThreadAction), sizeof(MallocAction));
+  max = MAX(max, sizeof(CallocAction));
+  max = MAX(max, sizeof(ReallocAction));
+  max = MAX(max, sizeof(MemalignAction));
+  return MAX(max, sizeof(FreeAction));
+}
+
+Action* Action::CreateAction(uintptr_t key_pointer, const char* type,
+                             const char* line, void* action_memory) {
+  Action* action = nullptr;
+  if (strcmp(type, "malloc") == 0) {
+    action = new (action_memory) MallocAction(key_pointer, line);
+  } else if (strcmp(type, "free") == 0) {
+    action = new (action_memory) FreeAction(key_pointer);
+  } else if (strcmp(type, "calloc") == 0) {
+    action = new (action_memory) CallocAction(key_pointer, line);
+  } else if (strcmp(type, "realloc") == 0) {
+    action = new (action_memory) ReallocAction(key_pointer, line);
+  } else if (strcmp(type, "memalign") == 0) {
+    action = new (action_memory) MemalignAction(key_pointer, line);
+  } else if (strcmp(type, "thread_done") == 0) {
+    action = new (action_memory) EndThreadAction();
+  }
+
+  if (action == nullptr || action->IsError()) {
+    return nullptr;
+  }
+  return action;
+}
diff --git a/memory_replay/Action.h b/memory_replay/Action.h
new file mode 100644
index 0000000..f498f52
--- /dev/null
+++ b/memory_replay/Action.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef _MEMORY_REPLAY_ACTION_H
+#define _MEMORY_REPLAY_ACTION_H
+
+#include <stdint.h>
+
+class Pointers;
+
+class Action {
+ public:
+  Action() {}
+  virtual ~Action() {}
+
+  virtual uint64_t Execute(Pointers* pointers) = 0;
+
+  bool IsError() { return is_error_; };
+
+  virtual bool EndThread() { return false; }
+
+  virtual bool DoesFree() { return false; }
+
+  static size_t MaxActionSize();
+  static Action* CreateAction(uintptr_t key_pointer, const char* type,
+                              const char* line, void* action_memory);
+
+ protected:
+  bool is_error_ = false;
+};
+
+#endif // _MEMORY_REPLAY_ACTION_H
diff --git a/memory_replay/Android.mk b/memory_replay/Android.mk
new file mode 100644
index 0000000..7179d61
--- /dev/null
+++ b/memory_replay/Android.mk
@@ -0,0 +1,79 @@
+LOCAL_PATH := $(call my-dir)
+
+memory_replay_src_files := \
+	Action.cpp \
+	LineBuffer.cpp \
+	NativeInfo.cpp \
+	Pointers.cpp \
+	Thread.cpp \
+	Threads.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_LDLIBS := -lrt
+include $(BUILD_HOST_EXECUTABLE)
+
+memory_replay_test_src_files := \
+	tests/ActionTest.cpp \
+	tests/LineBufferTest.cpp \
+	tests/NativeInfoTest.cpp \
+	tests/PointersTest.cpp \
+	tests/ThreadTest.cpp \
+	tests/ThreadsTest.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	$(memory_replay_src_files) \
+	$(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+
+LOCAL_SHARED_LIBRARIES := libbase
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	$(memory_replay_src_files) \
+	$(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+LOCAL_MODULE_HOST_OS := linux
+
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_LDLIBS := -lrt
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_HOST_NATIVE_TEST)
+
+memory_replay_src_files :=
+memory_replay_test_src_files :=
diff --git a/memory_replay/LineBuffer.cpp b/memory_replay/LineBuffer.cpp
new file mode 100644
index 0000000..5e65ad6
--- /dev/null
+++ b/memory_replay/LineBuffer.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 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 <string.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+
+LineBuffer::LineBuffer(int fd, char* buffer, size_t buffer_len) : fd_(fd), buffer_(buffer), buffer_len_(buffer_len) {
+}
+
+bool LineBuffer::GetLine(char** line, size_t* line_len) {
+  while (true) {
+    if (bytes_ > 0) {
+      char* newline = reinterpret_cast<char*>(memchr(buffer_ + start_, '\n', bytes_));
+      if (newline != nullptr) {
+        *newline = '\0';
+        *line = buffer_ + start_;
+        start_ = newline - buffer_ + 1;
+        bytes_ -= newline - *line + 1;
+        *line_len = newline - *line;
+        return true;
+      }
+    }
+    if (start_ > 0) {
+      // Didn't find anything, copy the current to the front of the buffer.
+      memmove(buffer_, buffer_ + start_, bytes_);
+      start_ = 0;
+    }
+    ssize_t bytes = TEMP_FAILURE_RETRY(read(fd_, buffer_ + bytes_, buffer_len_ - bytes_ - 1));
+    if (bytes <= 0) {
+      if (bytes_ > 0) {
+        // The read data might not contain a nul terminator, so add one.
+        buffer_[bytes_] = '\0';
+        *line = buffer_ + start_;
+        *line_len = bytes_;
+        bytes_ = 0;
+        start_ = 0;
+        return true;
+      }
+      return false;
+    }
+    bytes_ += bytes;
+  }
+}
diff --git a/memory_replay/LineBuffer.h b/memory_replay/LineBuffer.h
new file mode 100644
index 0000000..934d302
--- /dev/null
+++ b/memory_replay/LineBuffer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef _MEMORY_REPLAY_LINE_BUFFER_H
+#define _MEMORY_REPLAY_LINE_BUFFER_H
+
+#include <stdint.h>
+
+class LineBuffer {
+ public:
+  LineBuffer(int fd, char* buffer, size_t buffer_len);
+
+  bool GetLine(char** line, size_t* line_len);
+
+ private:
+  int fd_;
+  char* buffer_ = nullptr;
+  size_t buffer_len_ = 0;
+  size_t start_ = 0;
+  size_t bytes_ = 0;
+};
+
+#endif // _MEMORY_REPLAY_LINE_BUFFER_H
diff --git a/memory_replay/NOTICE b/memory_replay/NOTICE
new file mode 100644
index 0000000..8530865
--- /dev/null
+++ b/memory_replay/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2015, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
new file mode 100644
index 0000000..b88eaf6
--- /dev/null
+++ b/memory_replay/NativeInfo.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+
+// This function is not re-entrant since it uses a static buffer for
+// the line data.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes) {
+  static char map_buffer[65535];
+  LineBuffer line_buf(smaps_fd, map_buffer, sizeof(map_buffer));
+  char* line;
+  size_t total_pss_bytes = 0;
+  size_t total_va_bytes = 0;
+  size_t line_len;
+  bool native_map = false;
+  while (line_buf.GetLine(&line, &line_len)) {
+    uintptr_t start, end;
+    int name_pos;
+    size_t native_pss_kB;
+    if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n",
+        &start, &end, &name_pos) == 2) {
+      if (strcmp(line + name_pos, "[anon:libc_malloc]") == 0 ||
+          strcmp(line + name_pos, "[heap]") == 0) {
+        total_va_bytes += end - start;
+        native_map = true;
+      } else {
+        native_map = false;
+      }
+    } else if (native_map && sscanf(line, "Pss: %zu", &native_pss_kB) == 1) {
+      total_pss_bytes += native_pss_kB * 1024;
+    }
+  }
+  close(smaps_fd);
+  *pss_bytes = total_pss_bytes;
+  *va_bytes = total_va_bytes;
+}
+
+void PrintNativeInfo(const char* preamble) {
+  size_t pss_bytes;
+  size_t va_bytes;
+
+  int smaps_fd = open("/proc/self/smaps", O_RDONLY);
+  if (smaps_fd == -1) {
+    err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno));
+  }
+
+  GetNativeInfo(smaps_fd, &pss_bytes, &va_bytes);
+  printf("%sNative PSS: %zu bytes %0.2fMB\n", preamble, pss_bytes, pss_bytes/(1024*1024.0));
+  printf("%sNative VA Space: %zu bytes %0.2fMB\n", preamble, va_bytes, va_bytes/(1024*1024.0));
+  fflush(stdout);
+
+  close(smaps_fd);
+}
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
new file mode 100644
index 0000000..5953695
--- /dev/null
+++ b/memory_replay/NativeInfo.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef _MEMORY_REPLAY_NATIVE_INFO_H
+#define _MEMORY_REPLAY_NATIVE_INFO_H
+
+// This function is not re-entrant.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes);
+
+// This function is not re-entrant.
+void PrintNativeInfo(const char* preamble);
+
+#endif // _MEMORY_REPLAY_NATIVE_INFO_H
diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp
new file mode 100644
index 0000000..b9604f0
--- /dev/null
+++ b/memory_replay/Pointers.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 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 <err.h>
+#include <inttypes.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "err.h"
+#include "Pointers.h"
+
+Pointers::Pointers(size_t max_allocs) {
+  size_t pagesize = getpagesize();
+  // Create a mmap that contains a 4:1 ratio of allocations to entries.
+  // Align to a page.
+  pointers_size_ = (max_allocs * 4 * sizeof(pointer_data) + pagesize - 1) & ~(pagesize - 1);
+  max_pointers_ = pointers_size_ / sizeof(pointer_data);
+  void* memory = mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE,
+                      MAP_ANON | MAP_PRIVATE, -1, 0);
+  if (memory == MAP_FAILED) {
+    err(1, "Unable to allocate data for pointer hash: %zu total_allocs\n", max_allocs);
+  }
+  // Make sure that all of the PSS for this is counted right away.
+  memset(memory, 0, pointers_size_);
+  pointers_ = reinterpret_cast<pointer_data*>(memory);
+}
+
+Pointers::~Pointers() {
+  if (pointers_ != nullptr) {
+    munmap(pointers_, pointers_size_);
+    pointers_ = nullptr;
+  }
+}
+
+void Pointers::Add(uintptr_t key_pointer, void* pointer) {
+  pointer_data* data = FindEmpty(key_pointer);
+  if (data == nullptr) {
+    err(1, "No empty entry found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+  atomic_store(&data->key_pointer, key_pointer);
+  data->pointer = pointer;
+}
+
+void* Pointers::Remove(uintptr_t key_pointer) {
+  if (key_pointer == 0) {
+    err(1, "Illegal zero value passed to Remove\n");
+  }
+
+  pointer_data* data = Find(key_pointer);
+  if (data == nullptr) {
+    err(1, "No pointer value found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+
+  void* pointer = data->pointer;
+  atomic_store(&data->key_pointer, uintptr_t(0));
+
+  return pointer;
+}
+
+pointer_data* Pointers::Find(uintptr_t key_pointer) {
+  size_t index = GetHash(key_pointer);
+  for (size_t entries = max_pointers_; entries != 0; entries--) {
+    if (atomic_load(&pointers_[index].key_pointer) == key_pointer) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) {
+  size_t index = GetHash(key_pointer);
+  for (size_t entries = 0; entries < max_pointers_; entries++) {
+    uintptr_t empty = 0;
+    if (atomic_compare_exchange_strong(&pointers_[index].key_pointer, &empty,
+        uintptr_t(1))) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+size_t Pointers::GetHash(uintptr_t key_pointer) {
+  return key_pointer % max_pointers_;
+}
+
+void Pointers::FreeAll() {
+  for (size_t i = 0; i < max_pointers_; i++) {
+    if (atomic_load(&pointers_[i].key_pointer) != 0) {
+      free(pointers_[i].pointer);
+    }
+  }
+}
diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h
new file mode 100644
index 0000000..c7cd825
--- /dev/null
+++ b/memory_replay/Pointers.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef _MEMORY_REPLAY_POINTERS_H
+#define _MEMORY_REPLAY_POINTERS_H
+
+#include <stdatomic.h>
+#include <stdint.h>
+
+struct pointer_data {
+  std::atomic_uintptr_t key_pointer;
+  void* pointer;
+};
+
+class Pointers {
+ public:
+  explicit Pointers(size_t max_allocs);
+  virtual ~Pointers();
+
+  void Add(uintptr_t key_pointer, void* pointer);
+
+  void* Remove(uintptr_t key_pointer);
+
+  size_t max_pointers() { return max_pointers_; }
+
+  void FreeAll();
+
+ private:
+  pointer_data* FindEmpty(uintptr_t key_pointer);
+  pointer_data* Find(uintptr_t key_pointer);
+  size_t GetHash(uintptr_t key_pointer);
+
+  pointer_data* pointers_ = nullptr;
+  size_t pointers_size_ = 0;
+  size_t max_pointers_ = 0;
+};
+
+#endif // _MEMORY_REPLAY_POINTERS_H
diff --git a/memory_replay/Thread.cpp b/memory_replay/Thread.cpp
new file mode 100644
index 0000000..497b288
--- /dev/null
+++ b/memory_replay/Thread.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 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 <pthread.h>
+
+#include "Action.h"
+#include "Thread.h"
+
+Thread::Thread() {
+  pthread_cond_init(&cond_, nullptr);
+}
+
+Thread::~Thread() {
+  pthread_cond_destroy(&cond_);
+}
+
+void Thread::WaitForReady() {
+  pthread_mutex_lock(&mutex_);
+  while (pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::WaitForPending() {
+  pthread_mutex_lock(&mutex_);
+  while (!pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::SetPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = true;
+  pthread_mutex_unlock(&mutex_);
+  pthread_cond_signal(&cond_);
+}
+
+void Thread::ClearPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = false;
+  pthread_mutex_unlock(&mutex_);
+  pthread_cond_signal(&cond_);
+}
+
+Action* Thread::CreateAction(uintptr_t key_pointer, const char* type, const char* line) {
+  return Action::CreateAction(key_pointer, type, line, action_memory_);
+}
diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h
new file mode 100644
index 0000000..7724c12
--- /dev/null
+++ b/memory_replay/Thread.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef _MEMORY_REPLAY_THREAD_H
+#define _MEMORY_REPLAY_THREAD_H
+
+#include <pthread.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+class Action;
+class Pointers;
+
+constexpr size_t ACTION_MEMORY_SIZE = 128;
+
+class Thread {
+ public:
+  Thread();
+  virtual ~Thread();
+
+  void WaitForReady();
+  void WaitForPending();
+  void SetPending();
+  void ClearPending();
+
+  Action* CreateAction(uintptr_t key_pointer, const char* type, const char* line);
+  void AddTimeNsecs(uint64_t nsecs) { total_time_nsecs_ += nsecs; }
+
+  void set_pointers(Pointers* pointers) { pointers_ = pointers; }
+  Pointers* pointers() { return pointers_; }
+
+  Action* GetAction() { return reinterpret_cast<Action*>(action_memory_); }
+
+ private:
+  pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
+  pthread_cond_t cond_;
+  bool pending_ = false;
+
+  pthread_t thread_id_;
+  pid_t tid_ = 0;
+  uint64_t total_time_nsecs_ = 0;
+
+  Pointers* pointers_ = nullptr;
+
+  // Per thread memory for an Action. Only one action can be processed.
+  // at a time.
+  static constexpr size_t ACTION_SIZE = 128;
+  uint8_t action_memory_[ACTION_SIZE];
+
+  friend class Threads;
+};
+
+#endif // _MEMORY_REPLAY_THREAD_H
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
new file mode 100644
index 0000000..81a679e
--- /dev/null
+++ b/memory_replay/Threads.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 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 <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Thread.h"
+#include "Threads.h"
+
+void* ThreadRunner(void* data) {
+  Thread* thread = reinterpret_cast<Thread*>(data);
+  while (true) {
+    thread->WaitForPending();
+    Action* action = thread->GetAction();
+    thread->AddTimeNsecs(action->Execute(thread->pointers()));
+    bool end_thread = action->EndThread();
+    thread->ClearPending();
+    if (end_thread) {
+      break;
+    }
+  }
+  return nullptr;
+}
+
+Threads::Threads(Pointers* pointers, size_t max_threads)
+    : pointers_(pointers), max_threads_(max_threads) {
+  size_t pagesize = getpagesize();
+  data_size_ = (max_threads_ * sizeof(Thread) + pagesize - 1) & ~(pagesize - 1);
+  max_threads_ = data_size_ / sizeof(Thread);
+
+  void* memory = mmap(nullptr, data_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (memory == MAP_FAILED) {
+    err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu\n",
+        data_size_, max_threads_);
+  }
+
+  if (Thread::ACTION_SIZE < Action::MaxActionSize()) {
+    err(1, "Thread action size is too small: ACTION_SIZE %zu, max size %zu\n",
+        Thread::ACTION_SIZE, Action::MaxActionSize());
+  }
+
+  threads_ = new (memory) Thread[max_threads_];
+}
+
+Threads::~Threads() {
+  if (threads_) {
+    munmap(threads_, data_size_);
+    threads_ = nullptr;
+    data_size_ = 0;
+  }
+}
+
+Thread* Threads::CreateThread(pid_t tid) {
+  if (num_threads_ == max_threads_) {
+    err(1, "Too many threads created, current max %zu.\n", num_threads_);
+  }
+  Thread* thread = FindEmptyEntry(tid);
+  if (thread == nullptr) {
+    err(1, "No empty entries found, current max %zu, num threads %zu\n",
+          max_threads_, num_threads_);
+  }
+  thread->tid_ = tid;
+  thread->pointers_ = pointers_;
+  thread->total_time_nsecs_ = 0;
+  if (pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread) == -1) {
+    err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
+  }
+
+  num_threads_++;
+  return thread;
+}
+
+Thread* Threads::FindThread(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = num_threads_; entries != 0; ) {
+    pid_t cur_tid = threads_[index].tid_;
+    if (cur_tid == tid) {
+      return threads_ + index;
+    }
+    if (cur_tid != 0) {
+      entries--;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::WaitForAllToQuiesce() {
+  for (size_t i = 0, threads = 0; threads < num_threads_; i++) {
+    pid_t cur_tid = threads_[i].tid_;
+    if (cur_tid != 0) {
+      threads++;
+      threads_[i].WaitForReady();
+    }
+  }
+}
+
+size_t Threads::GetHashEntry(pid_t tid) {
+  return tid % max_threads_;
+}
+
+Thread* Threads::FindEmptyEntry(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = 0; entries < max_threads_; entries++) {
+    if (threads_[index].tid_ == 0) {
+      return threads_ + index;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::Finish(Thread* thread) {
+  int ret = pthread_join(thread->thread_id_, nullptr);
+  if (ret != 0) {
+    fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
+    exit(1);
+  }
+  total_time_nsecs_ += thread->total_time_nsecs_;
+  thread->tid_ = 0;
+  num_threads_--;
+}
+
+void Threads::FinishAll() {
+  for (size_t i = 0; i < max_threads_; i++) {
+    if (threads_[i].tid_ != 0) {
+      threads_[i].CreateAction(0, "thread_done", nullptr);
+      threads_[i].SetPending();
+      Finish(threads_ + i);
+    }
+  }
+}
diff --git a/memory_replay/Threads.h b/memory_replay/Threads.h
new file mode 100644
index 0000000..4778bff
--- /dev/null
+++ b/memory_replay/Threads.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef _MEMORY_REPLAY_THREADS_H
+#define _MEMORY_REPLAY_THREADS_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+class Pointers;
+class Thread;
+
+class Threads {
+ public:
+  Threads(Pointers* pointers, size_t max_threads);
+  virtual ~Threads();
+
+  Thread* CreateThread(pid_t tid);
+  Thread* FindThread(pid_t tid);
+  void WaitForAllToQuiesce();
+  void Finish(Thread* thread);
+  void FinishAll();
+
+  size_t num_threads() { return num_threads_; }
+  size_t max_threads() { return max_threads_; }
+  uint64_t total_time_nsecs() { return total_time_nsecs_; }
+
+ private:
+  Pointers* pointers_ = nullptr;
+  Thread* threads_ = nullptr;
+  size_t data_size_ = 0;
+  size_t max_threads_ = 0;
+  size_t num_threads_= 0;
+  uint64_t total_time_nsecs_ = 0;
+
+  Thread* FindEmptyEntry(pid_t tid);
+  size_t GetHashEntry(pid_t tid);
+
+  void ClearData();
+
+  friend Thread;
+};
+
+#endif // _MEMORY_REPLAY_THREADS_H
diff --git a/memory_replay/dumps/README b/memory_replay/dumps/README
new file mode 100644
index 0000000..d306b9a
--- /dev/null
+++ b/memory_replay/dumps/README
@@ -0,0 +1,71 @@
+The files in this directory are a collection of recordings of
+the memory allocations of a set of apps.
+
+In order to run these files through the tool, they will need to be placed
+unzipped on the device.
+
+Format of dumps:
+
+<tid>: <action_name> <ptr> [<optional_arguments>]
+
+<tid>
+  The pid_t value that is the gettid() value recorded during the run.
+
+<action_name> 
+  One of:
+    malloc - Allocate memory using the malloc function.
+    calloc - Allocate memory using the calloc function.
+    memalign - Allocate memory using the memalign function. This is used
+               during recording for either memalign or posix_memalign.
+    realloc - Allocate memory using the realloc function.
+    free - Free memory allocated using one of the above actions.
+    thread_done - Terminate the thread with the given tid.
+
+Format of each action:
+
+<tid>: malloc <ptr> <size>
+ Allocation made by malloc(<size>).  <ptr> is the value returned by malloc.
+
+Example:
+
+100: malloc 0xb48390a0 48
+
+<tid>: calloc <ptr> <nmemb> <size>
+  Allocation made by calloc(<nmemb>, <size>. <ptr> is the value returned
+  by calloc.
+
+Example:
+
+200: calloc 0xb48c1100 32 8
+
+<tid>:realloc <new_ptr> <old_ptr> <size>
+  Allocation made by realloc(<old_ptr>, <size>). <old_ptr> can be 0x0
+  to indicate a realloc with a nullptr. <new_ptr> is the value returned
+  by realloc.
+
+Example:
+
+300: realloc 0x96b90920 0x93605280 150
+
+<tid>:memalign <ptr> <alignment> <size>
+  Allocation made by memalign(<alignment>, <size>). <ptr> is the value
+  returned by memalign.
+
+Example:
+
+400: memalign 0xae42d080 16 104
+
+<tid>: free <ptr>
+  Find a previously allocated pointer <ptr> and call free(<ptr>).
+  <ptr> can be 0x0 to indicate the freeing of a nullptr.
+
+Example:
+
+500: free 0xb4827400
+
+<tid>: thread_done 0x0
+  Indicates that the thread <tid> has completed.
+
+Example:
+
+600: thread_done 0x0
diff --git a/memory_replay/dumps/camera.zip b/memory_replay/dumps/camera.zip
new file mode 100644
index 0000000..b9d1fb1
--- /dev/null
+++ b/memory_replay/dumps/camera.zip
Binary files differ
diff --git a/memory_replay/dumps/gmail.zip b/memory_replay/dumps/gmail.zip
new file mode 100644
index 0000000..45e0d04
--- /dev/null
+++ b/memory_replay/dumps/gmail.zip
Binary files differ
diff --git a/memory_replay/dumps/maps.zip b/memory_replay/dumps/maps.zip
new file mode 100644
index 0000000..17cf4e7
--- /dev/null
+++ b/memory_replay/dumps/maps.zip
Binary files differ
diff --git a/memory_replay/dumps/surfaceflinger.zip b/memory_replay/dumps/surfaceflinger.zip
new file mode 100644
index 0000000..495f2f2
--- /dev/null
+++ b/memory_replay/dumps/surfaceflinger.zip
Binary files differ
diff --git a/memory_replay/dumps/system_server.zip b/memory_replay/dumps/system_server.zip
new file mode 100644
index 0000000..bb43baf
--- /dev/null
+++ b/memory_replay/dumps/system_server.zip
Binary files differ
diff --git a/memory_replay/dumps/systemui.zip b/memory_replay/dumps/systemui.zip
new file mode 100644
index 0000000..8909fac
--- /dev/null
+++ b/memory_replay/dumps/systemui.zip
Binary files differ
diff --git a/memory_replay/dumps/youtube.zip b/memory_replay/dumps/youtube.zip
new file mode 100644
index 0000000..bea68ab
--- /dev/null
+++ b/memory_replay/dumps/youtube.zip
Binary files differ
diff --git a/memory_replay/fast/.clang-format b/memory_replay/fast/.clang-format
new file mode 100644
index 0000000..9b7478c
--- /dev/null
+++ b/memory_replay/fast/.clang-format
@@ -0,0 +1,15 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+ContinuationIndentWidth: 2
+PointerAlignment: Left
+TabWidth: 2
+UseTab: Never
+PenaltyExcessCharacter: 32
+
+Cpp11BracedListStyle: false
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
new file mode 100644
index 0000000..42b5298
--- /dev/null
+++ b/memory_replay/main.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "Action.h"
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+static char g_buffer[65535];
+
+size_t GetMaxAllocs(int fd) {
+  lseek(fd, 0, SEEK_SET);
+  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+  char* line;
+  size_t line_len;
+  size_t num_allocs = 0;
+  while (line_buf.GetLine(&line, &line_len)) {
+    char* word = reinterpret_cast<char*>(memchr(line, ':', line_len));
+    if (word == nullptr) {
+      continue;
+    }
+
+    word++;
+    while (*word++ == ' ');
+    // This will treat a realloc as an allocation, even if it frees
+    // another allocation. Since reallocs are relatively rare, this
+    // shouldn't inflate the numbers that much.
+    if (*word == 'f') {
+      // Check if this is a free of zero.
+      uintptr_t pointer;
+      if (sscanf(word, "free %" SCNxPTR, &pointer) == 1 && pointer != 0) {
+        num_allocs--;
+      }
+    } else if (*word != 't') {
+      // Skip the thread_done message.
+      num_allocs++;
+    }
+  }
+  return num_allocs;
+}
+
+void ProcessDump(int fd, size_t max_allocs, size_t max_threads) {
+  lseek(fd, 0, SEEK_SET);
+  Pointers pointers(max_allocs);
+  Threads threads(&pointers, max_threads);
+
+  printf("Maximum threads available:   %zu\n", threads.max_threads());
+  printf("Maximum allocations in dump: %zu\n", max_allocs);
+  printf("Total pointers available:    %zu\n", pointers.max_pointers());
+  printf("\n");
+
+  PrintNativeInfo("Initial ");
+
+  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+  char* line;
+  size_t line_len;
+  size_t line_number = 0;
+  while (line_buf.GetLine(&line, &line_len)) {
+    pid_t tid;
+    int line_pos = 0;
+    char type[128];
+    uintptr_t key_pointer;
+
+    // Every line is of this format:
+    //   <tid>: <action_type> <pointer>
+    // Some actions have extra arguments which will be used and verified
+    // when creating the Action object.
+    if (sscanf(line, "%d: %s %" SCNxPTR " %n", &tid, type, &key_pointer, &line_pos) != 3) {
+      err(1, "Unparseable line found: %s\n", line);
+    }
+    line_number++;
+    if ((line_number % 100000) == 0) {
+      printf("  At line %zu:\n", line_number);
+      PrintNativeInfo("    ");
+    }
+    Thread* thread = threads.FindThread(tid);
+    if (thread == nullptr) {
+      thread = threads.CreateThread(tid);
+    }
+
+    // Wait for the thread to complete any previous actions before handling
+    // the next action.
+    thread->WaitForReady();
+
+    Action* action = thread->CreateAction(key_pointer, type, line + line_pos);
+    if (action == nullptr) {
+      err(1, "Cannot create action from line: %s\n", line);
+    }
+
+    bool does_free = action->DoesFree();
+    if (does_free) {
+      // Make sure that any other threads doing allocations are complete
+      // before triggering the action. Otherwise, another thread could
+      // be creating the allocation we are going to free.
+      threads.WaitForAllToQuiesce();
+    }
+
+    // Tell the thread to execute the action.
+    thread->SetPending();
+
+    if (action->EndThread()) {
+      // Wait for the thread to finish and clear the thread entry.
+      threads.Finish(thread);
+    }
+
+    // Wait for this action to complete. This avoids a race where
+    // another thread could be creating the same allocation where are
+    // trying to free.
+    if (does_free) {
+      thread->WaitForReady();
+    }
+  }
+  // Wait for all threads to stop processing actions.
+  threads.WaitForAllToQuiesce();
+
+  PrintNativeInfo("Final ");
+
+  // Free any outstanding pointers.
+  // This allows us to run a tool like valgrind to verify that no memory
+  // is leaked and everything is accounted for during a run.
+  threads.FinishAll();
+  pointers.FreeAll();
+
+  // Print out the total time making all allocation calls.
+  printf("Total Allocation/Free Time: %" PRIu64 "ns %0.2fs\n",
+         threads.total_time_nsecs(), threads.total_time_nsecs()/1000000000.0);
+}
+
+constexpr size_t DEFAULT_MAX_THREADS = 512;
+
+int main(int argc, char** argv) {
+  if (argc != 2 && argc != 3) {
+    if (argc > 3) {
+      fprintf(stderr, "Only two arguments are expected.\n");
+    } else {
+      fprintf(stderr, "Requires at least one argument.\n");
+    }
+    fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
+    return 1;
+  }
+
+  size_t max_threads = DEFAULT_MAX_THREADS;
+  if (argc == 3) {
+    max_threads = atoi(argv[2]);
+  }
+
+  int dump_fd = open(argv[1], O_RDONLY);
+  if (dump_fd == -1) {
+    fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
+    return 1;
+  }
+
+  printf("Processing: %s\n", argv[1]);
+
+  // Do a first pass to get the total number of allocations used at one
+  // time to allow a single mmap that can hold the maximum number of
+  // pointers needed at once.
+  size_t max_allocs = GetMaxAllocs(dump_fd);
+  ProcessDump(dump_fd, max_allocs, max_threads);
+
+  close(dump_fd);
+
+  return 0;
+}
diff --git a/memory_replay/tests/ActionTest.cpp b/memory_replay/tests/ActionTest.cpp
new file mode 100644
index 0000000..cd72c24
--- /dev/null
+++ b/memory_replay/tests/ActionTest.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "Action.h"
+#include "Pointers.h"
+
+TEST(ActionTest, malloc) {
+  uint8_t memory[Action::MaxActionSize()];
+  const char* line = "1024";
+  Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, malloc_malformed) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x1234, "malloc", line, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, free) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x1234, "free", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_TRUE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  pointers.Add(0x1234, malloc(10));
+  action->Execute(&pointers);
+}
+
+TEST(ActionTest, calloc) {
+  uint8_t memory[128];
+  const char* line = "100 10";
+  Action* action = Action::CreateAction(0x1234, "calloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, free_zero) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0, "free", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+  // Should be a nop.
+  action->Execute(nullptr);
+}
+
+TEST(ActionTest, calloc_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "100";
+  Action* action = Action::CreateAction(0x1234, "calloc", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "calloc", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, realloc) {
+  uint8_t memory[128];
+  const char* line = "0xabcd 100";
+  Action* action = Action::CreateAction(0x1234, "realloc", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_TRUE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  pointers.Add(0xabcd, malloc(10));
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+
+  const char* null_line = "0x0 100";
+  action = Action::CreateAction(0x1234, "realloc", null_line, memory);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  action->Execute(&pointers);
+  pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, realloc_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "0x100";
+  Action* action = Action::CreateAction(0x1234, "realloc", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "realloc", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, memalign) {
+  uint8_t memory[128];
+  const char* line = "16 300";
+  Action* action = Action::CreateAction(0x1234, "memalign", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_FALSE(action->EndThread());
+
+  Pointers pointers(1);
+  action->Execute(&pointers);
+  void* pointer = pointers.Remove(0x1234);
+  ASSERT_TRUE(pointer != nullptr);
+  free(pointer);
+}
+
+TEST(ActionTest, memalign_malformed) {
+  uint8_t memory[128];
+  const char* line1 = "100";
+  Action* action = Action::CreateAction(0x1234, "memalign", line1, memory);
+  ASSERT_FALSE(action != NULL);
+
+  const char* line2 = "";
+  action = Action::CreateAction(0x1234, "memalign", line2, memory);
+  ASSERT_FALSE(action != NULL);
+}
+
+TEST(ActionTest, endthread) {
+  uint8_t memory[128];
+  const char* line = "";
+  Action* action = Action::CreateAction(0x0, "thread_done", line, memory);
+  ASSERT_TRUE(action != NULL);
+  ASSERT_FALSE(action->DoesFree());
+  ASSERT_TRUE(action->EndThread());
+
+  action->Execute(nullptr);
+}
diff --git a/memory_replay/tests/LineBufferTest.cpp b/memory_replay/tests/LineBufferTest.cpp
new file mode 100644
index 0000000..29d1b53
--- /dev/null
+++ b/memory_replay/tests/LineBufferTest.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+
+#include "LineBuffer.h"
+
+class LineBufferTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    tmp_file_ = new TemporaryFile();
+    ASSERT_TRUE(tmp_file_->fd != -1);
+  }
+
+  void TearDown() override {
+    delete tmp_file_;
+  }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(LineBufferTest, single_line) {
+  std::string line_data;
+  line_data += "Single line with newline.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Single line with newline.", line);
+  ASSERT_EQ(sizeof("Single line with newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_line_no_newline) {
+  std::string line_data;
+  line_data += "Single line with no newline.";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Single line with no newline.", line);
+  ASSERT_EQ(sizeof("Single line with no newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, single_read_no_end_newline) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last no newline.";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[100];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last no newline.", line);
+  ASSERT_EQ(sizeof("Third line is last no newline.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, one_line_per_read) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[24];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, multiple_line_per_read_multiple_reads) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "Third line is last.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[60];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Third line is last.", line);
+  ASSERT_EQ(sizeof("Third line is last.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
+
+TEST_F(LineBufferTest, line_larger_than_buffer) {
+  std::string line_data;
+  line_data += "The first line.\n";
+  line_data += "Second line here.\n";
+  line_data += "This is a really, really, really, kind of long.\n";
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  char buffer[25];
+  LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer));
+
+  char* line;
+  size_t line_len;
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The first line.", line);
+  ASSERT_EQ(sizeof("The first line.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("Second line here.", line);
+  ASSERT_EQ(sizeof("Second line here.") - 1, line_len);
+
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("This is a really, really", line);
+  ASSERT_EQ(sizeof(buffer) - 1, line_len);
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ(", really, kind of long.", line);
+  ASSERT_EQ(sizeof(", really, kind of long.") - 1, line_len);
+
+  line_data += "The fourth line.\n";
+  ASSERT_TRUE(line_buf.GetLine(&line, &line_len));
+  ASSERT_STREQ("The fourth line.", line);
+  ASSERT_EQ(sizeof("The fourth line.") - 1, line_len);
+
+  ASSERT_FALSE(line_buf.GetLine(&line, &line_len));
+}
diff --git a/memory_replay/tests/NativeInfoTest.cpp b/memory_replay/tests/NativeInfoTest.cpp
new file mode 100644
index 0000000..59dbbd9
--- /dev/null
+++ b/memory_replay/tests/NativeInfoTest.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <stdint.h>
+
+#include <string>
+
+#include <android-base/test_utils.h>
+
+#include "NativeInfo.h"
+
+class NativeInfoTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    tmp_file_ = new TemporaryFile();
+    ASSERT_TRUE(tmp_file_->fd != -1);
+  }
+
+  void TearDown() override {
+    delete tmp_file_;
+  }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(NativeInfoTest, no_matching) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:thread signal stack]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:thread signal stack]\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(0U, pss_bytes);
+  ASSERT_EQ(0U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_anons) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(32768U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_heaps) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(45056U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, mix_heap_anon) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:skip]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:skip]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   40 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f3e000-b6f3f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(73728U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
diff --git a/memory_replay/tests/PointersTest.cpp b/memory_replay/tests/PointersTest.cpp
new file mode 100644
index 0000000..a39f018
--- /dev/null
+++ b/memory_replay/tests/PointersTest.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include "Pointers.h"
+
+TEST(PointersTest, smoke) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, readd_pointer) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  pointers.Add(0x1234, reinterpret_cast<void*>(0x5555));
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0x5555), memory_pointer);
+}
+
+
+TEST(PointersTest, expect_collision) {
+  Pointers pointers(2);
+
+  // This assumes the simple hash being used will result in a collision
+  // hitting the same entry.
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x11234, reinterpret_cast<void*>(0xabcf));
+  void* memory_pointer = pointers.Remove(0x11234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcf), memory_pointer);
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, multiple_add_removes) {
+  Pointers pointers(4);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x1235, reinterpret_cast<void*>(0xabcf));
+  pointers.Add(0x1236, reinterpret_cast<void*>(0xabc1));
+  pointers.Add(0x1237, reinterpret_cast<void*>(0xabc2));
+
+  void* memory_pointer = pointers.Remove(0x1236);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc1), memory_pointer);
+
+  pointers.Add(0x2349, reinterpret_cast<void*>(0x2abcd));
+
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x1237);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc2), memory_pointer);
+
+  pointers.Add(0x3500, reinterpret_cast<void*>(0x3abcd));
+
+  memory_pointer = pointers.Remove(0x3500);
+  ASSERT_EQ(reinterpret_cast<void*>(0x3abcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x2349);
+  ASSERT_EQ(reinterpret_cast<void*>(0x2abcd), memory_pointer);
+}
+
+static void TestNoEntriesLeft() {
+  Pointers pointers(1);
+
+  // Even though we've requested only one pointer, we get more due
+  // to the way the data is allocated.
+  for (size_t i = 0; i <= pointers.max_pointers(); i++) {
+    pointers.Add(0x1234 + i, reinterpret_cast<void*>(0xabcd + i));
+  }
+}
+
+TEST(PointersTest_DeathTest, no_entries_left) {
+  ASSERT_EXIT(TestNoEntriesLeft(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestFindNoPointer() {
+  Pointers pointers(1);
+
+  pointers.Remove(0x1234);
+}
+
+TEST(PointersTest_DeathTest, find_no_pointer) {
+  ASSERT_EXIT(TestFindNoPointer(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestRemoveZeroValue() {
+  Pointers pointers(1);
+
+  void* memory = pointers.Remove(0);
+  if (memory) {}
+}
+
+TEST(PointersTest_DeathTest, remove_zero_value) {
+  ASSERT_EXIT(TestRemoveZeroValue(), ::testing::ExitedWithCode(1), "");
+}
diff --git a/memory_replay/tests/ThreadTest.cpp b/memory_replay/tests/ThreadTest.cpp
new file mode 100644
index 0000000..7249290
--- /dev/null
+++ b/memory_replay/tests/ThreadTest.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+
+typedef std::pair<Thread*, volatile bool*> thread_data_t;
+
+TEST(ThreadTest, ready) {
+  Thread thread;
+
+  // A thread should be ready immediately. If not, this will hang forever.
+  thread.WaitForReady();
+}
+
+void* ThreadWaitForReady(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForReady();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, ready_thread) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  thread.SetPending();
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForReady, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.ClearPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+void* ThreadWaitForPending(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForPending();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, pending) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForPending, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.SetPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+TEST(ThreadTest, pointers) {
+  Pointers pointers(2);
+  Thread thread;
+
+  ASSERT_TRUE(thread.pointers() == nullptr);
+  thread.set_pointers(&pointers);
+  ASSERT_TRUE(thread.pointers() == &pointers);
+}
+
+TEST(ThreadTest, action) {
+  Thread thread;
+
+  Action* action = thread.CreateAction(0x1234, "thread_done", "");
+  ASSERT_EQ(action, thread.GetAction());
+
+  // Verify the action object is not garbage.
+  action->Execute(nullptr);
+
+  ASSERT_TRUE(action->EndThread());
+  ASSERT_FALSE(action->DoesFree());
+}
diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp
new file mode 100644
index 0000000..c2ba023
--- /dev/null
+++ b/memory_replay/tests/ThreadsTest.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+TEST(ThreadsTest, single_thread) {
+  Pointers pointers(2);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* found_thread = threads.FindThread(900);
+  ASSERT_EQ(thread, found_thread);
+
+  thread->CreateAction(0x1234, "thread_done", "");
+
+  thread->SetPending();
+
+  threads.Finish(thread);
+
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, multiple_threads) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread1 = threads.CreateThread(900);
+  ASSERT_TRUE(thread1 != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* thread2 = threads.CreateThread(901);
+  ASSERT_TRUE(thread2 != nullptr);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  Thread* thread3 = threads.CreateThread(902);
+  ASSERT_TRUE(thread3 != nullptr);
+  ASSERT_EQ(3U, threads.num_threads());
+
+  Thread* found_thread1 = threads.FindThread(900);
+  ASSERT_EQ(thread1, found_thread1);
+
+  Thread* found_thread2 = threads.FindThread(901);
+  ASSERT_EQ(thread2, found_thread2);
+
+  Thread* found_thread3 = threads.FindThread(902);
+  ASSERT_EQ(thread3, found_thread3);
+
+  thread1->CreateAction(0x1234, "thread_done", "");
+  thread2->CreateAction(0x1235, "thread_done", "");
+  thread3->CreateAction(0x1236, "thread_done", "");
+
+  thread1->SetPending();
+  threads.Finish(thread1);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  thread3->SetPending();
+  threads.Finish(thread3);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  thread2->SetPending();
+  threads.Finish(thread2);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, verify_quiesce) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  // If WaitForAllToQuiesce is not correct, then this should provoke an error
+  // since we are overwriting the action data while it's being used.
+  for (size_t i = 0; i < 512; i++) {
+    thread->CreateAction(0x1234 + i, "malloc", "100");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+
+    thread->CreateAction(0x1234 + i, "free", "");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+  }
+
+  thread->CreateAction(0x1236, "thread_done", "");
+  thread->SetPending();
+  threads.Finish(thread);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+static void TestTooManyThreads() {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  for (size_t i = 0; i <= threads.max_threads(); i++) {
+    Thread* thread = threads.CreateThread(900+i);
+    ASSERT_EQ(thread, threads.FindThread(900+i));
+  }
+}
+
+TEST(ThreadsTest, too_many_threads) {
+  ASSERT_EXIT(TestTooManyThreads(), ::testing::ExitedWithCode(1), "");
+}