diff options
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 Binary files differnew file mode 100644 index 00000000..b9d1fb10 --- /dev/null +++ b/memory_replay/dumps/camera.zip diff --git a/memory_replay/dumps/gmail.zip b/memory_replay/dumps/gmail.zip Binary files differnew file mode 100644 index 00000000..45e0d040 --- /dev/null +++ b/memory_replay/dumps/gmail.zip diff --git a/memory_replay/dumps/maps.zip b/memory_replay/dumps/maps.zip Binary files differnew file mode 100644 index 00000000..17cf4e76 --- /dev/null +++ b/memory_replay/dumps/maps.zip diff --git a/memory_replay/dumps/surfaceflinger.zip b/memory_replay/dumps/surfaceflinger.zip Binary files differnew file mode 100644 index 00000000..495f2f2d --- /dev/null +++ b/memory_replay/dumps/surfaceflinger.zip diff --git a/memory_replay/dumps/system_server.zip b/memory_replay/dumps/system_server.zip Binary files differnew file mode 100644 index 00000000..bb43baf6 --- /dev/null +++ b/memory_replay/dumps/system_server.zip diff --git a/memory_replay/dumps/systemui.zip b/memory_replay/dumps/systemui.zip Binary files differnew file mode 100644 index 00000000..8909face --- /dev/null +++ b/memory_replay/dumps/systemui.zip diff --git a/memory_replay/dumps/youtube.zip b/memory_replay/dumps/youtube.zip Binary files differnew file mode 100644 index 00000000..bea68abb --- /dev/null +++ b/memory_replay/dumps/youtube.zip 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), ""); +} |