diff options
author | Christopher Ferris <cferris@google.com> | 2014-03-07 19:42:19 -0800 |
---|---|---|
committer | Christopher Ferris <cferris@google.com> | 2014-04-03 14:19:24 -0700 |
commit | e29609106033a48a6128664668d22bf4fb42a7ee (patch) | |
tree | c01cd6a8fd876b11a08ff0120af3f263d71b7b53 /libbacktrace | |
parent | 223fc42b5e289e882f67c893374ffbef595a6901 (diff) | |
download | system_core-e29609106033a48a6128664668d22bf4fb42a7ee.zip system_core-e29609106033a48a6128664668d22bf4fb42a7ee.tar.gz system_core-e29609106033a48a6128664668d22bf4fb42a7ee.tar.bz2 |
Create an UnwindMapLocal object.
The way libunwind handles local unwinds is different from remote unwinds,
so create a new map object to handle the differences.
Add new test to verify the map data is being generated correctly.
Add new tests to check for leaks.
Refactor the BACK_LOGW code into a single header file.
Change-Id: I01f3cbfc4b927646174ea1b614fa25d23b9b3427
Diffstat (limited to 'libbacktrace')
-rwxr-xr-x | libbacktrace/Android.mk | 2 | ||||
-rw-r--r-- | libbacktrace/BacktraceImpl.cpp | 1 | ||||
-rwxr-xr-x | libbacktrace/BacktraceImpl.h | 5 | ||||
-rwxr-xr-x | libbacktrace/BacktraceLog.h | 28 | ||||
-rw-r--r-- | libbacktrace/BacktraceThread.cpp | 1 | ||||
-rw-r--r-- | libbacktrace/Corkscrew.cpp | 3 | ||||
-rw-r--r-- | libbacktrace/GetPss.cpp | 85 | ||||
-rw-r--r-- | libbacktrace/GetPss.h | 22 | ||||
-rwxr-xr-x | libbacktrace/UnwindCurrent.cpp | 3 | ||||
-rw-r--r-- | libbacktrace/UnwindMap.cpp | 122 | ||||
-rw-r--r-- | libbacktrace/UnwindMap.h | 19 | ||||
-rw-r--r-- | libbacktrace/UnwindPtrace.cpp | 3 | ||||
-rw-r--r-- | libbacktrace/backtrace_test.cpp | 147 |
13 files changed, 379 insertions, 62 deletions
diff --git a/libbacktrace/Android.mk b/libbacktrace/Android.mk index 2e56756..c743077 100755 --- a/libbacktrace/Android.mk +++ b/libbacktrace/Android.mk @@ -129,9 +129,11 @@ endif # arm64 backtrace_test_cflags_target := \ -DGTEST_OS_LINUX_ANDROID \ + -DENABLE_PSS_TESTS \ backtrace_test_src_files := \ backtrace_test.cpp \ + GetPss.cpp \ thread_utils.c \ backtrace_test_ldlibs := \ diff --git a/libbacktrace/BacktraceImpl.cpp b/libbacktrace/BacktraceImpl.cpp index 855810e..05007d9 100644 --- a/libbacktrace/BacktraceImpl.cpp +++ b/libbacktrace/BacktraceImpl.cpp @@ -27,6 +27,7 @@ #include <backtrace/BacktraceMap.h> #include "BacktraceImpl.h" +#include "BacktraceLog.h" #include "thread_utils.h" //------------------------------------------------------------------------- diff --git a/libbacktrace/BacktraceImpl.h b/libbacktrace/BacktraceImpl.h index 48dd11c..7b31c38 100755 --- a/libbacktrace/BacktraceImpl.h +++ b/libbacktrace/BacktraceImpl.h @@ -21,11 +21,6 @@ #include <backtrace/BacktraceMap.h> #include <sys/types.h> -#include <log/log.h> - -// Macro to log the function name along with the warning message. -#define BACK_LOGW(format, ...) \ - ALOGW("%s: " format, __PRETTY_FUNCTION__, ##__VA_ARGS__) class BacktraceImpl { public: diff --git a/libbacktrace/BacktraceLog.h b/libbacktrace/BacktraceLog.h new file mode 100755 index 0000000..1632ec2 --- /dev/null +++ b/libbacktrace/BacktraceLog.h @@ -0,0 +1,28 @@ +/* + * 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 _LIBBACKTRACE_BACKTRACE_LOG_H +#define _LIBBACKTRACE_BACKTRACE_LOG_H + +#define LOG_TAG "libbacktrace" + +#include <log/log.h> + +// Macro to log the function name along with the warning message. +#define BACK_LOGW(format, ...) \ + ALOGW("%s: " format, __PRETTY_FUNCTION__, ##__VA_ARGS__) + +#endif // _LIBBACKTRACE_BACKTRACE_LOG_H diff --git a/libbacktrace/BacktraceThread.cpp b/libbacktrace/BacktraceThread.cpp index 5ffe516..4cda19e 100644 --- a/libbacktrace/BacktraceThread.cpp +++ b/libbacktrace/BacktraceThread.cpp @@ -23,6 +23,7 @@ #include <cutils/atomic.h> +#include "BacktraceLog.h" #include "BacktraceThread.h" #include "thread_utils.h" diff --git a/libbacktrace/Corkscrew.cpp b/libbacktrace/Corkscrew.cpp index efeee2e..773b0a2 100644 --- a/libbacktrace/Corkscrew.cpp +++ b/libbacktrace/Corkscrew.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "libbacktrace" - #include <backtrace/Backtrace.h> #include <string.h> @@ -28,6 +26,7 @@ #endif #include <dlfcn.h> +#include "BacktraceLog.h" #include "Corkscrew.h" //------------------------------------------------------------------------- diff --git a/libbacktrace/GetPss.cpp b/libbacktrace/GetPss.cpp new file mode 100644 index 0000000..442383b --- /dev/null +++ b/libbacktrace/GetPss.cpp @@ -0,0 +1,85 @@ +/* + * 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 <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> + +// This is an extremely simplified version of libpagemap. + +#define _BITS(x, offset, bits) (((x) >> offset) & ((1LL << (bits)) - 1)) + +#define PAGEMAP_PRESENT(x) (_BITS(x, 63, 1)) +#define PAGEMAP_SWAPPED(x) (_BITS(x, 62, 1)) +#define PAGEMAP_SHIFT(x) (_BITS(x, 55, 6)) +#define PAGEMAP_PFN(x) (_BITS(x, 0, 55)) +#define PAGEMAP_SWAP_OFFSET(x) (_BITS(x, 5, 50)) +#define PAGEMAP_SWAP_TYPE(x) (_BITS(x, 0, 5)) + +static bool ReadData(int fd, unsigned long place, uint64_t *data) { + if (lseek(fd, place * sizeof(uint64_t), SEEK_SET) < 0) { + return false; + } + if (read(fd, (void*)data, sizeof(uint64_t)) != (ssize_t)sizeof(uint64_t)) { + return false; + } + return true; +} + +size_t GetPssBytes() { + FILE* maps = fopen("/proc/self/maps", "r"); + assert(maps != NULL); + + int pagecount_fd = open("/proc/kpagecount", O_RDONLY); + assert(pagecount_fd >= 0); + + int pagemap_fd = open("/proc/self/pagemap", O_RDONLY); + assert(pagemap_fd >= 0); + + char line[4096]; + size_t total_pss = 0; + int pagesize = getpagesize(); + while (fgets(line, sizeof(line), maps)) { + uintptr_t start, end; + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " ", &start, &end) != 2) { + total_pss = 0; + break; + } + for (size_t page = start/pagesize; page < end/pagesize; page++) { + uint64_t data; + if (ReadData(pagemap_fd, page, &data)) { + if (PAGEMAP_PRESENT(data) && !PAGEMAP_SWAPPED(data)) { + uint64_t count; + if (ReadData(pagecount_fd, PAGEMAP_PFN(data), &count)) { + total_pss += (count >= 1) ? pagesize / count : 0; + } + } + } + } + } + + fclose(maps); + + close(pagecount_fd); + close(pagemap_fd); + + return total_pss; +} diff --git a/libbacktrace/GetPss.h b/libbacktrace/GetPss.h new file mode 100644 index 0000000..787c33d --- /dev/null +++ b/libbacktrace/GetPss.h @@ -0,0 +1,22 @@ +/* + * 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 _LIBBACKTRACE_GET_PSS_H +#define _LIBBACKTRACE_GET_PSS_H + +size_t GetPssBytes(); + +#endif // _LIBBACKTRACE_GET_PSS_H diff --git a/libbacktrace/UnwindCurrent.cpp b/libbacktrace/UnwindCurrent.cpp index 81e69bb..034b73c 100755 --- a/libbacktrace/UnwindCurrent.cpp +++ b/libbacktrace/UnwindCurrent.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "libbacktrace" - #include <sys/ucontext.h> #include <sys/types.h> @@ -25,6 +23,7 @@ #define UNW_LOCAL_ONLY #include <libunwind.h> +#include "BacktraceLog.h" #include "UnwindCurrent.h" #include "UnwindMap.h" diff --git a/libbacktrace/UnwindMap.cpp b/libbacktrace/UnwindMap.cpp index 8268db6..1615518 100644 --- a/libbacktrace/UnwindMap.cpp +++ b/libbacktrace/UnwindMap.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "libbacktrace" - #include <pthread.h> #include <sys/types.h> #include <unistd.h> @@ -24,6 +22,7 @@ #include <libunwind.h> +#include "BacktraceLog.h" #include "UnwindMap.h" //------------------------------------------------------------------------- @@ -32,57 +31,21 @@ // only update the local address space once, and keep a reference count // of maps using the same map cursor. //------------------------------------------------------------------------- -static pthread_mutex_t g_map_mutex = PTHREAD_MUTEX_INITIALIZER; -static unw_map_cursor_t g_map_cursor; -static int g_map_references = 0; - UnwindMap::UnwindMap(pid_t pid) : BacktraceMap(pid) { - map_cursor_.map_list = NULL; } UnwindMap::~UnwindMap() { - if (pid_ == getpid()) { - pthread_mutex_lock(&g_map_mutex); - if (--g_map_references == 0) { - // Clear the local address space map. - unw_map_local_set(NULL); - unw_map_cursor_destroy(&map_cursor_); - } - pthread_mutex_unlock(&g_map_mutex); - } else { - unw_map_cursor_destroy(&map_cursor_); - } + unw_map_cursor_destroy(&map_cursor_); + unw_map_cursor_clear(&map_cursor_); } -bool UnwindMap::Build() { - bool return_value = true; - if (pid_ == getpid()) { - pthread_mutex_lock(&g_map_mutex); - if (g_map_references == 0) { - return_value = (unw_map_cursor_create(&map_cursor_, pid_) == 0); - if (return_value) { - // Set the local address space map to our new map. - unw_map_local_set(&map_cursor_); - g_map_references = 1; - g_map_cursor = map_cursor_; - } - } else { - g_map_references++; - map_cursor_ = g_map_cursor; - } - pthread_mutex_unlock(&g_map_mutex); - } else { - return_value = (unw_map_cursor_create(&map_cursor_, pid_) == 0); - } - - if (!return_value) - return false; - +bool UnwindMap::GenerateMap() { // Use the map_cursor information to construct the BacktraceMap data // rather than reparsing /proc/self/maps. unw_map_cursor_reset(&map_cursor_); + unw_map_t unw_map; - while (unw_map_cursor_get(&map_cursor_, &unw_map)) { + while (unw_map_cursor_get_next(&map_cursor_, &unw_map)) { backtrace_map_t map; map.start = unw_map.start; @@ -97,11 +60,82 @@ bool UnwindMap::Build() { return true; } +bool UnwindMap::Build() { + return (unw_map_cursor_create(&map_cursor_, pid_) == 0) && GenerateMap(); +} + +UnwindMapLocal::UnwindMapLocal() : UnwindMap(getpid()), map_created_(false) { +} + +UnwindMapLocal::~UnwindMapLocal() { + if (map_created_) { + unw_map_local_destroy(); + unw_map_cursor_clear(&map_cursor_); + } +} + +bool UnwindMapLocal::GenerateMap() { + // It's possible for the map to be regenerated while this loop is occurring. + // If that happens, get the map again, but only try at most three times + // before giving up. + for (int i = 0; i < 3; i++) { + maps_.clear(); + + unw_map_local_cursor_get(&map_cursor_); + + unw_map_t unw_map; + int ret; + while ((ret = unw_map_local_cursor_get_next(&map_cursor_, &unw_map)) > 0) { + backtrace_map_t map; + + map.start = unw_map.start; + map.end = unw_map.end; + map.flags = unw_map.flags; + map.name = unw_map.path; + + free(unw_map.path); + + // The maps are in descending order, but we want them in ascending order. + maps_.push_front(map); + } + // Check to see if the map changed while getting the data. + if (ret != -UNW_EINVAL) { + return true; + } + } + + BACK_LOGW("Unable to generate the map."); + return false; +} + +bool UnwindMapLocal::Build() { + return (map_created_ = (unw_map_local_create() == 0)) && GenerateMap();; +} + +const backtrace_map_t* UnwindMapLocal::Find(uintptr_t addr) { + const backtrace_map_t* map = BacktraceMap::Find(addr); + if (!map) { + // Check to see if the underlying map changed and regenerate the map + // if it did. + if (unw_map_local_cursor_valid(&map_cursor_) < 0) { + if (GenerateMap()) { + map = BacktraceMap::Find(addr); + } + } + } + return map; +} + //------------------------------------------------------------------------- // BacktraceMap create function. //------------------------------------------------------------------------- BacktraceMap* BacktraceMap::Create(pid_t pid) { - BacktraceMap* map = new UnwindMap(pid); + BacktraceMap* map; + if (pid == getpid()) { + map = new UnwindMapLocal(); + } else { + map = new UnwindMap(pid); + } if (!map->Build()) { delete map; return NULL; diff --git a/libbacktrace/UnwindMap.h b/libbacktrace/UnwindMap.h index 5a874e8..2fdb29f 100644 --- a/libbacktrace/UnwindMap.h +++ b/libbacktrace/UnwindMap.h @@ -32,8 +32,25 @@ public: unw_map_cursor_t* GetMapCursor() { return &map_cursor_; } -private: +protected: + virtual bool GenerateMap(); + unw_map_cursor_t map_cursor_; }; +class UnwindMapLocal : public UnwindMap { +public: + UnwindMapLocal(); + virtual ~UnwindMapLocal(); + + virtual bool Build(); + + virtual const backtrace_map_t* Find(uintptr_t addr); + +protected: + virtual bool GenerateMap(); + + bool map_created_; +}; + #endif // _LIBBACKTRACE_UNWIND_MAP_H diff --git a/libbacktrace/UnwindPtrace.cpp b/libbacktrace/UnwindPtrace.cpp index 732dae8..5ca7e60 100644 --- a/libbacktrace/UnwindPtrace.cpp +++ b/libbacktrace/UnwindPtrace.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "libbacktrace" - #include <backtrace/Backtrace.h> #include <backtrace/BacktraceMap.h> @@ -25,6 +23,7 @@ #include <libunwind.h> #include <libunwind-ptrace.h> +#include "BacktraceLog.h" #include "UnwindMap.h" #include "UnwindPtrace.h" diff --git a/libbacktrace/backtrace_test.cpp b/libbacktrace/backtrace_test.cpp index 23eaf92..a5e141b 100644 --- a/libbacktrace/backtrace_test.cpp +++ b/libbacktrace/backtrace_test.cpp @@ -16,9 +16,10 @@ #include <dirent.h> #include <errno.h> +#include <inttypes.h> #include <pthread.h> #include <signal.h> -#include <stdbool.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -35,6 +36,7 @@ #include <cutils/atomic.h> #include <gtest/gtest.h> +#include <algorithm> #include <vector> #include "thread_utils.h" @@ -287,7 +289,7 @@ TEST(libbacktrace, ptrace_trace) { pid_t pid; if ((pid = fork()) == 0) { ASSERT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); - exit(1); + _exit(1); } VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyLevelBacktrace, VerifyLevelDump); @@ -300,7 +302,7 @@ TEST(libbacktrace, ptrace_trace_shared_map) { pid_t pid; if ((pid = fork()) == 0) { ASSERT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); - exit(1); + _exit(1); } VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, true, ReadyLevelBacktrace, VerifyLevelDump); @@ -314,7 +316,7 @@ TEST(libbacktrace, ptrace_max_trace) { pid_t pid; if ((pid = fork()) == 0) { ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, NULL, NULL), 0); - exit(1); + _exit(1); } VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyMaxBacktrace, VerifyMaxDump); @@ -339,7 +341,7 @@ TEST(libbacktrace, ptrace_ignore_frames) { pid_t pid; if ((pid = fork()) == 0) { ASSERT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); - exit(1); + _exit(1); } VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyLevelBacktrace, VerifyProcessIgnoreFrames); @@ -384,7 +386,7 @@ TEST(libbacktrace, ptrace_threads) { ASSERT_TRUE(pthread_create(&thread, &attr, PtraceThreadLevelRun, NULL) == 0); } ASSERT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); - exit(1); + _exit(1); } // Check to see that all of the threads are running before unwinding. @@ -693,3 +695,136 @@ TEST(libbacktrace, format_test) { #endif backtrace->FormatFrameData(&frame)); } + +struct map_test_t { + uintptr_t start; + uintptr_t end; +}; + +bool map_sort(map_test_t i, map_test_t j) { + return i.start < j.start; +} + +static void VerifyMap(pid_t pid) { + char buffer[4096]; + snprintf(buffer, sizeof(buffer), "/proc/%d/maps", pid); + + FILE* map_file = fopen(buffer, "r"); + ASSERT_TRUE(map_file != NULL); + std::vector<map_test_t> test_maps; + while (fgets(buffer, sizeof(buffer), map_file)) { + map_test_t map; + ASSERT_EQ(2, sscanf(buffer, "%" SCNxPTR "-%" SCNxPTR " ", &map.start, &map.end)); + test_maps.push_back(map); + } + fclose(map_file); + std::sort(test_maps.begin(), test_maps.end(), map_sort); + + UniquePtr<BacktraceMap> map(BacktraceMap::Create(pid)); + + // Basic test that verifies that the map is in the expected order. + std::vector<map_test_t>::const_iterator test_it = test_maps.begin(); + for (BacktraceMap::const_iterator it = map->begin(); it != map->end(); ++it) { + ASSERT_TRUE(test_it != test_maps.end()); + ASSERT_EQ(test_it->start, it->start); + ASSERT_EQ(test_it->end, it->end); + ++test_it; + } + ASSERT_TRUE(test_it == test_maps.end()); +} + +TEST(libbacktrace, verify_map_remote) { + pid_t pid; + + if ((pid = fork()) == 0) { + while (true) { + } + _exit(0); + } + ASSERT_LT(0, pid); + + ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0); + + // Wait for the process to get to a stopping point. + WaitForStop(pid); + + // The maps should match exactly since the forked process has been paused. + VerifyMap(pid); + + ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0); + + kill(pid, SIGKILL); + ASSERT_EQ(waitpid(pid, NULL, 0), pid); +} + +#if defined(ENABLE_PSS_TESTS) +#include "GetPss.h" + +#define MAX_LEAK_BYTES 32*1024UL + +static void CheckForLeak(pid_t pid, pid_t tid) { + // Do a few runs to get the PSS stable. + for (size_t i = 0; i < 100; i++) { + Backtrace* backtrace = Backtrace::Create(pid, tid); + ASSERT_TRUE(backtrace != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + delete backtrace; + } + size_t stable_pss = GetPssBytes(); + + // Loop enough that even a small leak should be detectable. + for (size_t i = 0; i < 4096; i++) { + Backtrace* backtrace = Backtrace::Create(pid, tid); + ASSERT_TRUE(backtrace != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + delete backtrace; + } + size_t new_pss = GetPssBytes(); + size_t abs_diff = (new_pss > stable_pss) ? new_pss - stable_pss : stable_pss - new_pss; + // As long as the new pss is within a certain amount, consider everything okay. + ASSERT_LE(abs_diff, MAX_LEAK_BYTES); +} + +TEST(libbacktrace, check_for_leak_local) { + CheckForLeak(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD); +} + +TEST(libbacktrace, check_for_leak_local_thread) { + thread_t thread_data = { 0, 0, 0 }; + pthread_t thread; + ASSERT_TRUE(pthread_create(&thread, NULL, ThreadLevelRun, &thread_data) == 0); + + // Wait up to 2 seconds for the tid to be set. + ASSERT_TRUE(WaitForNonZero(&thread_data.state, 2)); + + CheckForLeak(BACKTRACE_CURRENT_PROCESS, thread_data.tid); + + // Tell the thread to exit its infinite loop. + android_atomic_acquire_store(0, &thread_data.state); + + ASSERT_TRUE(pthread_join(thread, NULL) == 0); +} + +TEST(libbacktrace, check_for_leak_remote) { + pid_t pid; + + if ((pid = fork()) == 0) { + while (true) { + } + _exit(0); + } + ASSERT_LT(0, pid); + + ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0); + + // Wait for the process to get to a stopping point. + WaitForStop(pid); + + CheckForLeak(pid, BACKTRACE_CURRENT_THREAD); + + ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0); + + kill(pid, SIGKILL); + ASSERT_EQ(waitpid(pid, NULL, 0), pid); +} +#endif |