summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--memory_replay/Action.cpp168
-rw-r--r--memory_replay/Action.h45
-rw-r--r--memory_replay/Android.mk72
-rw-r--r--memory_replay/LineBuffer.cpp59
-rw-r--r--memory_replay/LineBuffer.h36
-rw-r--r--memory_replay/NativeInfo.cpp78
-rw-r--r--memory_replay/NativeInfo.h26
-rw-r--r--memory_replay/Pointers.cpp115
-rw-r--r--memory_replay/Pointers.h51
-rw-r--r--memory_replay/Thread.cpp62
-rw-r--r--memory_replay/Thread.h64
-rw-r--r--memory_replay/Threads.cpp151
-rw-r--r--memory_replay/Threads.h55
-rw-r--r--memory_replay/dumps/README71
-rw-r--r--memory_replay/dumps/camera.zipbin0 -> 9622035 bytes
-rw-r--r--memory_replay/dumps/gmail.zipbin0 -> 12189897 bytes
-rw-r--r--memory_replay/dumps/maps.zipbin0 -> 8784596 bytes
-rw-r--r--memory_replay/dumps/surfaceflinger.zipbin0 -> 4043232 bytes
-rw-r--r--memory_replay/dumps/system_server.zipbin0 -> 34244259 bytes
-rw-r--r--memory_replay/dumps/systemui.zipbin0 -> 1879288 bytes
-rw-r--r--memory_replay/dumps/youtube.zipbin0 -> 6799540 bytes
-rw-r--r--memory_replay/main.cpp187
-rw-r--r--memory_replay/tests/ActionTest.cpp168
-rw-r--r--memory_replay/tests/LineBufferTest.cpp241
-rw-r--r--memory_replay/tests/NativeInfoTest.cpp266
-rw-r--r--memory_replay/tests/PointersTest.cpp113
-rw-r--r--memory_replay/tests/ThreadTest.cpp114
-rw-r--r--memory_replay/tests/ThreadsTest.cpp124
28 files changed, 2266 insertions, 0 deletions
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
new file mode 100644
index 00000000..3c97c438
--- /dev/null
+++ b/memory_replay/Action.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 <inttypes.h>
+#include <malloc.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Threads.h"
+#include "Pointers.h"
+
+class EndThreadAction : public Action {
+ public:
+ EndThreadAction() {}
+
+ bool EndThread() override { return true; }
+
+ void Execute(Pointers*) override {}
+};
+
+class AllocAction : public Action {
+ public:
+ 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;
+ }
+ }
+
+ void Execute(Pointers* pointers) override {
+ void* memory = malloc(size_);
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+ }
+};
+
+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;
+ }
+ }
+
+ void Execute(Pointers* pointers) override {
+ void* memory = calloc(n_elements_, size_);
+ memset(memory, 0, n_elements_ * size_);
+ pointers->Add(key_pointer_, memory);
+ }
+
+ 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; }
+
+ void Execute(Pointers* pointers) override {
+ void* old_memory = nullptr;
+ if (old_pointer_ != 0) {
+ old_memory = pointers->Remove(old_pointer_);
+ }
+ void* memory = realloc(old_memory, size_);
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+ }
+
+ 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;
+ }
+ }
+
+ void Execute(Pointers* pointers) override {
+ void* memory = memalign(align_, size_);
+ memset(memory, 1, size_);
+ pointers->Add(key_pointer_, memory);
+ }
+
+ private:
+ size_t align_ = 0;
+};
+
+class FreeAction : public AllocAction {
+ public:
+ FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+ }
+
+ bool DoesFree() override { return key_pointer_ != 0; }
+
+ void Execute(Pointers* pointers) override {
+ if (key_pointer_) {
+ void* memory = pointers->Remove(key_pointer_);
+ free(memory);
+ }
+ }
+};
+
+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 00000000..0f010aaf
--- /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 void 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 00000000..b01fa869
--- /dev/null
+++ b/memory_replay/Android.mk
@@ -0,0 +1,72 @@
+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
+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_SHARED_LIBRARIES := libbase
+
+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 00000000..5e65ad62
--- /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 00000000..934d3021
--- /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/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
new file mode 100644
index 00000000..b88eaf68
--- /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 00000000..59536954
--- /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 00000000..b9604f06
--- /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 00000000..2491716a
--- /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:
+ 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 00000000..497b288e
--- /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 00000000..66226542
--- /dev/null
+++ b/memory_replay/Thread.h
@@ -0,0 +1,64 @@
+/*
+ * 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 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;
+
+ 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 00000000..24835e95
--- /dev/null
+++ b/memory_replay/Threads.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 <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();
+ 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_;
+ 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) {
+ pthread_join(thread->thread_id_, nullptr);
+ 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 00000000..af4815c3
--- /dev/null
+++ b/memory_replay/Threads.h
@@ -0,0 +1,55 @@
+/*
+ * 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_; }
+
+ private:
+ Pointers* pointers_ = nullptr;
+ Thread* threads_ = nullptr;
+ size_t data_size_ = 0;
+ size_t max_threads_ = 0;
+ size_t num_threads_= 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 00000000..d306b9a4
--- /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 00000000..b9d1fb10
--- /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 00000000..45e0d040
--- /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 00000000..17cf4e76
--- /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 00000000..495f2f2d
--- /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 00000000..bb43baf6
--- /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 00000000..8909face
--- /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 00000000..bea68abb
--- /dev/null
+++ b/memory_replay/dumps/youtube.zip
Binary files differ
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
new file mode 100644
index 00000000..ce5fcdf0
--- /dev/null
+++ b/memory_replay/main.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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, SEEK_SET, 0);
+ 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, SEEK_SET, 0);
+ 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();
+}
+
+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 00000000..cd72c24e
--- /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 00000000..8e9976e5
--- /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 <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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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 00000000..89f314ec
--- /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 <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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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 00000000..a39f0189
--- /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 00000000..72492905
--- /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 00000000..c2ba023c
--- /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), "");
+}