diff options
Diffstat (limited to 'libbacktrace')
-rw-r--r-- | libbacktrace/Android.mk | 290 | ||||
-rw-r--r-- | libbacktrace/BacktraceImpl.cpp | 208 | ||||
-rw-r--r-- | libbacktrace/BacktraceImpl.h | 79 | ||||
-rw-r--r-- | libbacktrace/BacktraceMap.cpp | 147 | ||||
-rw-r--r-- | libbacktrace/BacktraceThread.cpp | 215 | ||||
-rw-r--r-- | libbacktrace/BacktraceThread.h | 90 | ||||
-rw-r--r-- | libbacktrace/Corkscrew.cpp | 251 | ||||
-rw-r--r-- | libbacktrace/Corkscrew.h | 82 | ||||
-rw-r--r-- | libbacktrace/UnwindCurrent.cpp | 191 | ||||
-rw-r--r-- | libbacktrace/UnwindCurrent.h | 54 | ||||
-rw-r--r-- | libbacktrace/UnwindMap.cpp | 110 | ||||
-rw-r--r-- | libbacktrace/UnwindMap.h | 39 | ||||
-rw-r--r-- | libbacktrace/UnwindPtrace.cpp | 133 | ||||
-rw-r--r-- | libbacktrace/UnwindPtrace.h | 40 | ||||
-rw-r--r-- | libbacktrace/backtrace_test.cpp | 695 | ||||
-rw-r--r-- | libbacktrace/backtrace_testlib.c | 55 | ||||
-rw-r--r-- | libbacktrace/thread_utils.c | 42 | ||||
-rw-r--r-- | libbacktrace/thread_utils.h | 30 |
18 files changed, 2751 insertions, 0 deletions
diff --git a/libbacktrace/Android.mk b/libbacktrace/Android.mk new file mode 100644 index 0000000..80cd861 --- /dev/null +++ b/libbacktrace/Android.mk @@ -0,0 +1,290 @@ +LOCAL_PATH:= $(call my-dir) + +common_src := \ + BacktraceImpl.cpp \ + BacktraceMap.cpp \ + BacktraceThread.cpp \ + thread_utils.c \ + +common_cflags := \ + -Wall \ + -Wno-unused-parameter \ + -Werror \ + +common_conlyflags := \ + -std=gnu99 \ + +common_cppflags := \ + -std=gnu++11 \ + +common_shared_libs := \ + libcutils \ + libgccdemangle \ + liblog \ + +# To enable using libunwind on each arch, add it to this list. +libunwind_architectures := + +ifeq ($(TARGET_ARCH),$(filter $(TARGET_ARCH),$(libunwind_architectures))) + +#---------------------------------------------------------------------------- +# The native libbacktrace library with libunwind. +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + $(common_src) \ + UnwindCurrent.cpp \ + UnwindMap.cpp \ + UnwindPtrace.cpp \ + +LOCAL_CFLAGS := \ + $(common_cflags) \ + +LOCAL_CONLYFLAGS += \ + $(common_conlyflags) \ + +LOCAL_CPPFLAGS += \ + $(common_cppflags) \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +LOCAL_C_INCLUDES := \ + $(common_c_includes) \ + external/libunwind/include \ + +LOCAL_SHARED_LIBRARIES := \ + $(common_shared_libs) \ + libunwind \ + libunwind-ptrace \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include external/stlport/libstlport.mk + +include $(BUILD_SHARED_LIBRARY) + +else + +#---------------------------------------------------------------------------- +# The native libbacktrace library with libcorkscrew. +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + $(common_src) \ + Corkscrew.cpp \ + +LOCAL_CFLAGS := \ + $(common_cflags) \ + +LOCAL_CONLYFLAGS += \ + $(common_conlyflags) \ + +LOCAL_CPPFLAGS += \ + $(common_cppflags) \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +LOCAL_C_INCLUDES := \ + $(common_c_includes) \ + system/core/libcorkscrew \ + +LOCAL_SHARED_LIBRARIES := \ + $(common_shared_libs) \ + libcorkscrew \ + libdl \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include external/stlport/libstlport.mk + +include $(BUILD_SHARED_LIBRARY) + +endif + +#---------------------------------------------------------------------------- +# libbacktrace test library, all optimizations turned off +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := libbacktrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_testlib.c + +LOCAL_CFLAGS += \ + -std=gnu99 \ + -O0 \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include $(BUILD_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# libbacktrace test executable +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := backtrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_test.cpp \ + thread_utils.c \ + +LOCAL_CFLAGS += \ + $(common_cflags) \ + -fno-builtin \ + -fstack-protector-all \ + -O0 \ + -g \ + -DGTEST_OS_LINUX_ANDROID \ + -DGTEST_HAS_STD_STRING \ + +ifeq ($(TARGET_ARCH),arm64) + $(info TODO: $(LOCAL_PATH)/Android.mk -fstack-protector not yet available for the AArch64 toolchain) + LOCAL_CFLAGS += -fno-stack-protector +endif # arm64 + +LOCAL_CONLYFLAGS += \ + $(common_conlyflags) \ + +LOCAL_CPPFLAGS += \ + $(common_cppflags) \ + +LOCAL_SHARED_LIBRARIES += \ + libcutils \ + libbacktrace_test \ + libbacktrace \ + +LOCAL_LDLIBS := \ + -lpthread \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include $(BUILD_NATIVE_TEST) + +#---------------------------------------------------------------------------- +# Only x86 host versions of libbacktrace supported. +#---------------------------------------------------------------------------- +ifeq ($(HOST_ARCH),x86) + +#---------------------------------------------------------------------------- +# The host libbacktrace library using libcorkscrew +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + + +LOCAL_CFLAGS += \ + $(common_cflags) \ + +LOCAL_CONLYFLAGS += \ + $(common_conlyflags) \ + +LOCAL_C_INCLUDES := \ + $(common_c_includes) \ + +LOCAL_SHARED_LIBRARIES := \ + libgccdemangle \ + liblog \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +ifeq ($(HOST_OS),linux) +LOCAL_SRC_FILES += \ + $(common_src) \ + Corkscrew.cpp \ + +LOCAL_C_INCLUDES += \ + system/core/libcorkscrew \ + +LOCAL_SHARED_LIBRARIES := \ + libcorkscrew \ + +LOCAL_CPPFLAGS += \ + $(common_cppflags) \ + +LOCAL_LDLIBS += \ + -ldl \ + -lrt \ + +else +LOCAL_SRC_FILES += \ + BacktraceMap.cpp \ + +endif + +include $(BUILD_HOST_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# The host test is only supported on linux. +#---------------------------------------------------------------------------- +ifeq ($(HOST_OS),linux) + +#---------------------------------------------------------------------------- +# libbacktrace host test library, all optimizations turned off +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := libbacktrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_testlib.c + +LOCAL_CFLAGS += \ + -std=gnu99 \ + -O0 \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include $(BUILD_HOST_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# libbacktrace host test executable +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := backtrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_test.cpp \ + thread_utils.c \ + +LOCAL_CFLAGS += \ + $(common_cflags) \ + -fno-builtin \ + -fstack-protector-all \ + -O0 \ + -g \ + -DGTEST_HAS_STD_STRING \ + +LOCAL_SHARED_LIBRARIES := \ + libbacktrace_test \ + libbacktrace \ + +LOCAL_LDLIBS := \ + -lpthread \ + +LOCAL_ADDITIONAL_DEPENDENCIES := \ + $(LOCAL_PATH)/Android.mk + +include $(BUILD_HOST_NATIVE_TEST) + +endif # HOST_OS == linux + +endif # HOST_ARCH == x86 diff --git a/libbacktrace/BacktraceImpl.cpp b/libbacktrace/BacktraceImpl.cpp new file mode 100644 index 0000000..39296b4 --- /dev/null +++ b/libbacktrace/BacktraceImpl.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2013 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 <stdlib.h> +#include <string.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <unistd.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#include <string> + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> + +#include "BacktraceImpl.h" +#include "thread_utils.h" + +//------------------------------------------------------------------------- +// Backtrace functions. +//------------------------------------------------------------------------- +Backtrace::Backtrace(BacktraceImpl* impl, pid_t pid, BacktraceMap* map) + : pid_(pid), tid_(-1), map_(map), map_shared_(true), impl_(impl) { + impl_->SetParent(this); + + if (map_ == NULL) { + map_ = BacktraceMap::Create(pid); + map_shared_ = false; + } +} + +Backtrace::~Backtrace() { + if (impl_) { + delete impl_; + impl_ = NULL; + } + + if (map_ && !map_shared_) { + delete map_; + map_ = NULL; + } +} + +bool Backtrace::Unwind(size_t num_ignore_frames) { + return impl_->Unwind(num_ignore_frames); +} + +extern "C" char* __cxa_demangle(const char* mangled, char* buf, size_t* len, + int* status); + +std::string Backtrace::GetFunctionName(uintptr_t pc, uintptr_t* offset) { + std::string func_name = impl_->GetFunctionNameRaw(pc, offset); + if (!func_name.empty()) { +#if defined(__APPLE__) + // Mac OS' __cxa_demangle demangles "f" as "float"; last tested on 10.7. + if (func_name[0] != '_') { + return func_name; + } +#endif + char* name = __cxa_demangle(func_name.c_str(), 0, 0, 0); + if (name) { + func_name = name; + free(name); + } + } + return func_name; +} + +bool Backtrace::VerifyReadWordArgs(uintptr_t ptr, uint32_t* out_value) { + if (ptr & 3) { + BACK_LOGW("invalid pointer %p", (void*)ptr); + *out_value = (uint32_t)-1; + return false; + } + return true; +} + +std::string Backtrace::FormatFrameData(size_t frame_num) { + if (frame_num >= frames_.size()) { + return ""; + } + return FormatFrameData(&frames_[frame_num]); +} + +std::string Backtrace::FormatFrameData(const backtrace_frame_data_t* frame) { + const char* map_name; + if (frame->map && !frame->map->name.empty()) { + map_name = frame->map->name.c_str(); + } else { + map_name = "<unknown>"; + } + + uintptr_t relative_pc; + if (frame->map) { + relative_pc = frame->pc - frame->map->start; + } else { + relative_pc = frame->pc; + } + + char buf[512]; + if (!frame->func_name.empty() && frame->func_offset) { + snprintf(buf, sizeof(buf), "#%02zu pc %0*" PRIxPTR " %s (%s+%" PRIuPTR ")", + frame->num, (int)sizeof(uintptr_t)*2, relative_pc, map_name, + frame->func_name.c_str(), frame->func_offset); + } else if (!frame->func_name.empty()) { + snprintf(buf, sizeof(buf), "#%02zu pc %0*" PRIxPTR " %s (%s)", frame->num, + (int)sizeof(uintptr_t)*2, relative_pc, map_name, frame->func_name.c_str()); + } else { + snprintf(buf, sizeof(buf), "#%02zu pc %0*" PRIxPTR " %s", frame->num, + (int)sizeof(uintptr_t)*2, relative_pc, map_name); + } + + return buf; +} + +const backtrace_map_t* Backtrace::FindMap(uintptr_t pc) { + return map_->Find(pc); +} + +//------------------------------------------------------------------------- +// BacktraceCurrent functions. +//------------------------------------------------------------------------- +BacktraceCurrent::BacktraceCurrent( + BacktraceImpl* impl, BacktraceMap* map) : Backtrace(impl, getpid(), map) { +} + +BacktraceCurrent::~BacktraceCurrent() { +} + +bool BacktraceCurrent::ReadWord(uintptr_t ptr, uint32_t* out_value) { + if (!VerifyReadWordArgs(ptr, out_value)) { + return false; + } + + const backtrace_map_t* map = FindMap(ptr); + if (map && map->flags & PROT_READ) { + *out_value = *reinterpret_cast<uint32_t*>(ptr); + return true; + } else { + BACK_LOGW("pointer %p not in a readable map", reinterpret_cast<void*>(ptr)); + *out_value = static_cast<uint32_t>(-1); + return false; + } +} + +//------------------------------------------------------------------------- +// BacktracePtrace functions. +//------------------------------------------------------------------------- +BacktracePtrace::BacktracePtrace( + BacktraceImpl* impl, pid_t pid, pid_t tid, BacktraceMap* map) + : Backtrace(impl, pid, map) { + tid_ = tid; +} + +BacktracePtrace::~BacktracePtrace() { +} + +bool BacktracePtrace::ReadWord(uintptr_t ptr, uint32_t* out_value) { + if (!VerifyReadWordArgs(ptr, out_value)) { + return false; + } + +#if defined(__APPLE__) + BACK_LOGW("MacOS does not support reading from another pid."); + return false; +#else + // ptrace() returns -1 and sets errno when the operation fails. + // To disambiguate -1 from a valid result, we clear errno beforehand. + errno = 0; + *out_value = ptrace(PTRACE_PEEKTEXT, Tid(), reinterpret_cast<void*>(ptr), NULL); + if (*out_value == static_cast<uint32_t>(-1) && errno) { + BACK_LOGW("invalid pointer %p reading from tid %d, ptrace() strerror(errno)=%s", + reinterpret_cast<void*>(ptr), Tid(), strerror(errno)); + return false; + } + return true; +#endif +} + +Backtrace* Backtrace::Create(pid_t pid, pid_t tid, BacktraceMap* map) { + if (pid == BACKTRACE_CURRENT_PROCESS || pid == getpid()) { + if (tid == BACKTRACE_CURRENT_THREAD || tid == gettid()) { + return CreateCurrentObj(map); + } else { + return CreateThreadObj(tid, map); + } + } else if (tid == BACKTRACE_CURRENT_THREAD) { + return CreatePtraceObj(pid, pid, map); + } else { + return CreatePtraceObj(pid, tid, map); + } +} diff --git a/libbacktrace/BacktraceImpl.h b/libbacktrace/BacktraceImpl.h new file mode 100644 index 0000000..af014d5 --- /dev/null +++ b/libbacktrace/BacktraceImpl.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 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_IMPL_H +#define _LIBBACKTRACE_BACKTRACE_IMPL_H + +#include <backtrace/Backtrace.h> +#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: + virtual ~BacktraceImpl() { } + + virtual bool Unwind(size_t num_ignore_frames) = 0; + + // The name returned is not demangled, Backtrace::GetFunctionName() + // takes care of demangling the name. + virtual std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) = 0; + + void SetParent(Backtrace* backtrace) { backtrace_obj_ = backtrace; } + + inline pid_t Pid() { return backtrace_obj_->Pid(); } + inline pid_t Tid() { return backtrace_obj_->Tid(); } + + inline const backtrace_map_t* FindMap(uintptr_t addr) { + return backtrace_obj_->FindMap(addr); + } + inline std::string GetFunctionName(uintptr_t pc, uintptr_t* offset) { + return backtrace_obj_->GetFunctionName(pc, offset); + } + inline BacktraceMap* GetMap() { return backtrace_obj_->GetMap(); } + +protected: + inline std::vector<backtrace_frame_data_t>* GetFrames() { return &backtrace_obj_->frames_; } + + Backtrace* backtrace_obj_; +}; + +class BacktraceCurrent : public Backtrace { +public: + BacktraceCurrent(BacktraceImpl* impl, BacktraceMap* map); + virtual ~BacktraceCurrent(); + + bool ReadWord(uintptr_t ptr, uint32_t* out_value); +}; + +class BacktracePtrace : public Backtrace { +public: + BacktracePtrace(BacktraceImpl* impl, pid_t pid, pid_t tid, BacktraceMap* map); + virtual ~BacktracePtrace(); + + bool ReadWord(uintptr_t ptr, uint32_t* out_value); +}; + +Backtrace* CreateCurrentObj(BacktraceMap* map); +Backtrace* CreatePtraceObj(pid_t pid, pid_t tid, BacktraceMap* map); +Backtrace* CreateThreadObj(pid_t tid, BacktraceMap* map); + +#endif // _LIBBACKTRACE_BACKTRACE_IMPL_H diff --git a/libbacktrace/BacktraceMap.cpp b/libbacktrace/BacktraceMap.cpp new file mode 100644 index 0000000..6320800 --- /dev/null +++ b/libbacktrace/BacktraceMap.cpp @@ -0,0 +1,147 @@ +/* + * 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 <ctype.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <backtrace/backtrace_constants.h> +#include <backtrace/BacktraceMap.h> +#include <log/log.h> + +#include "thread_utils.h" +#include "BacktraceImpl.h" + +BacktraceMap::BacktraceMap(pid_t pid) : pid_(pid) { + if (pid_ < 0) { + pid_ = getpid(); + } +} + +BacktraceMap::~BacktraceMap() { +} + +const backtrace_map_t* BacktraceMap::Find(uintptr_t addr) { + for (BacktraceMap::const_iterator it = begin(); + it != end(); ++it) { + if (addr >= it->start && addr < it->end) { + return &*it; + } + } + return NULL; +} + +bool BacktraceMap::ParseLine(const char* line, backtrace_map_t* map) { + unsigned long int start; + unsigned long int end; + char permissions[5]; + int name_pos; + +#if defined(__APPLE__) +// Mac OS vmmap(1) output: +// __TEXT 0009f000-000a1000 [ 8K 8K] r-x/rwx SM=COW /Volumes/android/dalvik-dev/out/host/darwin-x86/bin/libcorkscrew_test\n +// 012345678901234567890123456789012345678901234567890123456789 +// 0 1 2 3 4 5 + if (sscanf(line, "%*21c %lx-%lx [%*13c] %3c/%*3c SM=%*3c %n", + &start, &end, permissions, &name_pos) != 3) { +#else +// Linux /proc/<pid>/maps lines: +// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so\n +// 012345678901234567890123456789012345678901234567890123456789 +// 0 1 2 3 4 5 + if (sscanf(line, "%lx-%lx %4s %*x %*x:%*x %*d%n", + &start, &end, permissions, &name_pos) != 3) { +#endif + return false; + } + + map->start = start; + map->end = end; + map->flags = PROT_NONE; + if (permissions[0] == 'r') { + map->flags |= PROT_READ; + } + if (permissions[1] == 'w') { + map->flags |= PROT_WRITE; + } + if (permissions[2] == 'x') { + map->flags |= PROT_EXEC; + } + + while (isspace(line[name_pos])) { + name_pos += 1; + } + map->name = line+name_pos; + if (!map->name.empty() && map->name[map->name.length()-1] == '\n') { + map->name.erase(map->name.length()-1); + } + + ALOGV("Parsed map: start=%p, end=%p, flags=%x, name=%s", + map->start, map->end, map->flags, map->name.c_str()); + return true; +} + +bool BacktraceMap::Build() { +#if defined(__APPLE__) + char cmd[sizeof(pid_t)*3 + sizeof("vmmap -w -resident -submap -allSplitLibs -interleaved ") + 1]; +#else + char path[sizeof(pid_t)*3 + sizeof("/proc//maps") + 1]; +#endif + char line[1024]; + +#if defined(__APPLE__) + // cmd is guaranteed to always be big enough to hold this string. + sprintf(cmd, "vmmap -w -resident -submap -allSplitLibs -interleaved %d", pid_); + FILE* fp = popen(cmd, "r"); +#else + // path is guaranteed to always be big enough to hold this string. + sprintf(path, "/proc/%d/maps", pid_); + FILE* fp = fopen(path, "r"); +#endif + if (fp == NULL) { + return false; + } + + while(fgets(line, sizeof(line), fp)) { + backtrace_map_t map; + if (ParseLine(line, &map)) { + maps_.push_back(map); + } + } +#if defined(__APPLE__) + pclose(fp); +#else + fclose(fp); +#endif + + return true; +} + +#if defined(__APPLE__) +// Corkscrew and libunwind don't compile on the mac, so create a generic +// map object. +BacktraceMap* BacktraceMap::Create(pid_t pid) { + BacktraceMap* map = new BacktraceMap(pid); + if (!map->Build()) { + delete map; + return NULL; + } + return map; +} +#endif diff --git a/libbacktrace/BacktraceThread.cpp b/libbacktrace/BacktraceThread.cpp new file mode 100644 index 0000000..5ffe516 --- /dev/null +++ b/libbacktrace/BacktraceThread.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2013 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 <inttypes.h> +#include <pthread.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> + +#include <cutils/atomic.h> + +#include "BacktraceThread.h" +#include "thread_utils.h" + +//------------------------------------------------------------------------- +// ThreadEntry implementation. +//------------------------------------------------------------------------- +static ThreadEntry* g_list = NULL; +static pthread_mutex_t g_entry_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t g_sigaction_mutex = PTHREAD_MUTEX_INITIALIZER; + +ThreadEntry::ThreadEntry( + BacktraceThreadInterface* intf, pid_t pid, pid_t tid, size_t num_ignore_frames) + : thread_intf(intf), pid(pid), tid(tid), next(NULL), prev(NULL), + state(STATE_WAITING), num_ignore_frames(num_ignore_frames) { +} + +ThreadEntry::~ThreadEntry() { + pthread_mutex_lock(&g_entry_mutex); + if (g_list == this) { + g_list = next; + } else { + if (next) { + next->prev = prev; + } + prev->next = next; + } + pthread_mutex_unlock(&g_entry_mutex); + + next = NULL; + prev = NULL; +} + +ThreadEntry* ThreadEntry::AddThreadToUnwind( + BacktraceThreadInterface* intf, pid_t pid, pid_t tid, size_t num_ignore_frames) { + ThreadEntry* entry = new ThreadEntry(intf, pid, tid, num_ignore_frames); + + pthread_mutex_lock(&g_entry_mutex); + ThreadEntry* cur_entry = g_list; + while (cur_entry != NULL) { + if (cur_entry->Match(pid, tid)) { + // There is already an entry for this pid/tid, this is bad. + BACK_LOGW("Entry for pid %d tid %d already exists.", pid, tid); + + pthread_mutex_unlock(&g_entry_mutex); + return NULL; + } + cur_entry = cur_entry->next; + } + + // Add the entry to the list. + entry->next = g_list; + if (g_list) { + g_list->prev = entry; + } + g_list = entry; + pthread_mutex_unlock(&g_entry_mutex); + + return entry; +} + +//------------------------------------------------------------------------- +// BacktraceThread functions. +//------------------------------------------------------------------------- +static void SignalHandler(int n __attribute__((unused)), siginfo_t* siginfo, + void* sigcontext) { + if (pthread_mutex_lock(&g_entry_mutex) == 0) { + pid_t pid = getpid(); + pid_t tid = gettid(); + ThreadEntry* cur_entry = g_list; + while (cur_entry) { + if (cur_entry->Match(pid, tid)) { + break; + } + cur_entry = cur_entry->next; + } + pthread_mutex_unlock(&g_entry_mutex); + if (!cur_entry) { + BACK_LOGW("Unable to find pid %d tid %d information", pid, tid); + return; + } + + if (android_atomic_acquire_cas(STATE_WAITING, STATE_DUMPING, &cur_entry->state) == 0) { + cur_entry->thread_intf->ThreadUnwind(siginfo, sigcontext, + cur_entry->num_ignore_frames); + } + android_atomic_release_store(STATE_DONE, &cur_entry->state); + } +} + +BacktraceThread::BacktraceThread( + BacktraceImpl* impl, BacktraceThreadInterface* thread_intf, pid_t tid, + BacktraceMap* map) + : BacktraceCurrent(impl, map), thread_intf_(thread_intf) { + tid_ = tid; +} + +BacktraceThread::~BacktraceThread() { +} + +void BacktraceThread::FinishUnwind() { + for (std::vector<backtrace_frame_data_t>::iterator it = frames_.begin(); + it != frames_.end(); ++it) { + it->map = FindMap(it->pc); + + it->func_offset = 0; + it->func_name = GetFunctionName(it->pc, &it->func_offset); + } +} + +bool BacktraceThread::TriggerUnwindOnThread(ThreadEntry* entry) { + entry->state = STATE_WAITING; + + if (tgkill(Pid(), Tid(), SIGURG) != 0) { + BACK_LOGW("tgkill failed %s", strerror(errno)); + return false; + } + + // Allow up to ten seconds for the dump to start. + int wait_millis = 10000; + int32_t state; + while (true) { + state = android_atomic_acquire_load(&entry->state); + if (state != STATE_WAITING) { + break; + } + if (wait_millis--) { + usleep(1000); + } else { + break; + } + } + + bool cancelled = false; + if (state == STATE_WAITING) { + if (android_atomic_acquire_cas(state, STATE_CANCEL, &entry->state) == 0) { + BACK_LOGW("Cancelled dump of thread %d", entry->tid); + state = STATE_CANCEL; + cancelled = true; + } else { + state = android_atomic_acquire_load(&entry->state); + } + } + + // Wait for at most ten seconds for the cancel or dump to finish. + wait_millis = 10000; + while (android_atomic_acquire_load(&entry->state) != STATE_DONE) { + if (wait_millis--) { + usleep(1000); + } else { + BACK_LOGW("Didn't finish thread unwind in 60 seconds."); + break; + } + } + return !cancelled; +} + +bool BacktraceThread::Unwind(size_t num_ignore_frames) { + ThreadEntry* entry = ThreadEntry::AddThreadToUnwind( + thread_intf_, Pid(), Tid(), num_ignore_frames); + if (!entry) { + return false; + } + + // Prevent multiple threads trying to set the trigger action on different + // threads at the same time. + bool retval = false; + if (pthread_mutex_lock(&g_sigaction_mutex) == 0) { + struct sigaction act, oldact; + memset(&act, 0, sizeof(act)); + act.sa_sigaction = SignalHandler; + act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&act.sa_mask); + if (sigaction(SIGURG, &act, &oldact) == 0) { + retval = TriggerUnwindOnThread(entry); + sigaction(SIGURG, &oldact, NULL); + } else { + BACK_LOGW("sigaction failed %s", strerror(errno)); + } + pthread_mutex_unlock(&g_sigaction_mutex); + } else { + BACK_LOGW("unable to acquire sigaction mutex."); + } + + if (retval) { + FinishUnwind(); + } + delete entry; + + return retval; +} diff --git a/libbacktrace/BacktraceThread.h b/libbacktrace/BacktraceThread.h new file mode 100644 index 0000000..3412d58 --- /dev/null +++ b/libbacktrace/BacktraceThread.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 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_THREAD_H +#define _LIBBACKTRACE_BACKTRACE_THREAD_H + +#include <inttypes.h> +#include <sys/types.h> + +#include "BacktraceImpl.h" + +enum state_e { + STATE_WAITING = 0, + STATE_DUMPING, + STATE_DONE, + STATE_CANCEL, +}; + +class BacktraceThreadInterface; + +struct ThreadEntry { + ThreadEntry( + BacktraceThreadInterface* impl, pid_t pid, pid_t tid, + size_t num_ignore_frames); + ~ThreadEntry(); + + bool Match(pid_t chk_pid, pid_t chk_tid) { return (chk_pid == pid && chk_tid == tid); } + + static ThreadEntry* AddThreadToUnwind( + BacktraceThreadInterface* thread_intf, pid_t pid, pid_t tid, + size_t num_ignored_frames); + + BacktraceThreadInterface* thread_intf; + pid_t pid; + pid_t tid; + ThreadEntry* next; + ThreadEntry* prev; + int32_t state; + int num_ignore_frames; +}; + +// Interface class that does not contain any local storage, only defines +// virtual functions to be defined by subclasses. +class BacktraceThreadInterface { +public: + virtual ~BacktraceThreadInterface() { } + + virtual void ThreadUnwind( + siginfo_t* siginfo, void* sigcontext, size_t num_ignore_frames) = 0; +}; + +class BacktraceThread : public BacktraceCurrent { +public: + // impl and thread_intf should point to the same object, this allows + // the compiler to catch if an implementation does not properly + // subclass both. + BacktraceThread( + BacktraceImpl* impl, BacktraceThreadInterface* thread_intf, pid_t tid, + BacktraceMap* map); + virtual ~BacktraceThread(); + + virtual bool Unwind(size_t num_ignore_frames); + + virtual void ThreadUnwind( + siginfo_t* siginfo, void* sigcontext, size_t num_ignore_frames) { + thread_intf_->ThreadUnwind(siginfo, sigcontext, num_ignore_frames); + } + +private: + virtual bool TriggerUnwindOnThread(ThreadEntry* entry); + + virtual void FinishUnwind(); + + BacktraceThreadInterface* thread_intf_; +}; + +#endif // _LIBBACKTRACE_BACKTRACE_THREAD_H diff --git a/libbacktrace/Corkscrew.cpp b/libbacktrace/Corkscrew.cpp new file mode 100644 index 0000000..efeee2e --- /dev/null +++ b/libbacktrace/Corkscrew.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "libbacktrace" + +#include <backtrace/Backtrace.h> + +#include <string.h> + +#include <backtrace-arch.h> +#include <corkscrew/backtrace.h> + +#ifndef __USE_GNU +#define __USE_GNU +#endif +#include <dlfcn.h> + +#include "Corkscrew.h" + +//------------------------------------------------------------------------- +// CorkscrewMap functions. +//------------------------------------------------------------------------- +CorkscrewMap::CorkscrewMap(pid_t pid) : BacktraceMap(pid), map_info_(NULL) { +} + +CorkscrewMap::~CorkscrewMap() { + if (map_info_) { + free_map_info_list(map_info_); + map_info_ = NULL; + } +} + +bool CorkscrewMap::Build() { + map_info_ = load_map_info_list(pid_); + + // Use the information in map_info_ to construct the BacktraceMap data + // rather than reparsing /proc/self/maps. + map_info_t* cur_map = map_info_; + while (cur_map) { + backtrace_map_t map; + map.start = cur_map->start; + map.end = cur_map->end; + map.flags = 0; + if (cur_map->is_readable) { + map.flags |= PROT_READ; + } + if (cur_map->is_writable) { + map.flags |= PROT_WRITE; + } + if (cur_map->is_executable) { + map.flags |= PROT_EXEC; + } + map.name = cur_map->name; + + // The maps are in descending order, but we want them in ascending order. + maps_.push_front(map); + + cur_map = cur_map->next; + } + return map_info_ != NULL; +} + +//------------------------------------------------------------------------- +// CorkscrewCommon functions. +//------------------------------------------------------------------------- +bool CorkscrewCommon::GenerateFrameData( + backtrace_frame_t* cork_frames, ssize_t num_frames) { + if (num_frames < 0) { + BACK_LOGW("libcorkscrew unwind failed."); + return false; + } + + std::vector<backtrace_frame_data_t>* frames = GetFrames(); + frames->resize(num_frames); + size_t i = 0; + for (std::vector<backtrace_frame_data_t>::iterator it = frames->begin(); + it != frames->end(); ++it, ++i) { + it->num = i; + it->pc = cork_frames[i].absolute_pc; + it->sp = cork_frames[i].stack_top; + it->stack_size = cork_frames[i].stack_size; + it->func_offset = 0; + + it->map = FindMap(it->pc); + it->func_name = GetFunctionName(it->pc, &it->func_offset); + } + return true; +} + +//------------------------------------------------------------------------- +// CorkscrewCurrent functions. +//------------------------------------------------------------------------- +CorkscrewCurrent::CorkscrewCurrent() { +} + +CorkscrewCurrent::~CorkscrewCurrent() { +} + +bool CorkscrewCurrent::Unwind(size_t num_ignore_frames) { + backtrace_frame_t frames[MAX_BACKTRACE_FRAMES]; + ssize_t num_frames = unwind_backtrace(frames, num_ignore_frames, MAX_BACKTRACE_FRAMES); + + return GenerateFrameData(frames, num_frames); +} + +std::string CorkscrewCurrent::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) { + *offset = 0; + + Dl_info info; + const backtrace_map_t* map = FindMap(pc); + if (map) { + if (dladdr((const void*)pc, &info)) { + if (info.dli_sname) { + *offset = pc - map->start - (uintptr_t)info.dli_saddr + (uintptr_t)info.dli_fbase; + return info.dli_sname; + } + } else { + // dladdr(3) didn't find a symbol; maybe it's static? Look in the ELF file... + symbol_table_t* symbol_table = load_symbol_table(map->name.c_str()); + if (symbol_table) { + // First check if we can find the symbol using a relative pc. + std::string name; + const symbol_t* elf_symbol = find_symbol(symbol_table, pc - map->start); + if (elf_symbol) { + name = elf_symbol->name; + *offset = pc - map->start - elf_symbol->start; + } else if ((elf_symbol = find_symbol(symbol_table, pc)) != NULL) { + // Found the symbol using the absolute pc. + name = elf_symbol->name; + *offset = pc - elf_symbol->start; + } + free_symbol_table(symbol_table); + return name; + } + } + } + return ""; +} + +//------------------------------------------------------------------------- +// CorkscrewThread functions. +//------------------------------------------------------------------------- +CorkscrewThread::CorkscrewThread() { +} + +CorkscrewThread::~CorkscrewThread() { +} + +void CorkscrewThread::ThreadUnwind( + siginfo_t* siginfo, void* sigcontext, size_t num_ignore_frames) { + backtrace_frame_t cork_frames[MAX_BACKTRACE_FRAMES]; + CorkscrewMap* map = static_cast<CorkscrewMap*>(GetMap()); + ssize_t num_frames = unwind_backtrace_signal_arch( + siginfo, sigcontext, map->GetMapInfo(), cork_frames, + num_ignore_frames, MAX_BACKTRACE_FRAMES); + if (num_frames > 0) { + std::vector<backtrace_frame_data_t>* frames = GetFrames(); + frames->resize(num_frames); + size_t i = 0; + for (std::vector<backtrace_frame_data_t>::iterator it = frames->begin(); + it != frames->end(); ++it, ++i) { + it->num = i; + it->pc = cork_frames[i].absolute_pc; + it->sp = cork_frames[i].stack_top; + it->stack_size = cork_frames[i].stack_size; + it->map = NULL; + it->func_offset = 0; + } + } +} + +//------------------------------------------------------------------------- +// CorkscrewPtrace functions. +//------------------------------------------------------------------------- +CorkscrewPtrace::CorkscrewPtrace() : ptrace_context_(NULL) { +} + +CorkscrewPtrace::~CorkscrewPtrace() { + if (ptrace_context_) { + free_ptrace_context(ptrace_context_); + ptrace_context_ = NULL; + } +} + +bool CorkscrewPtrace::Unwind(size_t num_ignore_frames) { + ptrace_context_ = load_ptrace_context(Tid()); + + backtrace_frame_t frames[MAX_BACKTRACE_FRAMES]; + ssize_t num_frames = unwind_backtrace_ptrace( + Tid(), ptrace_context_, frames, num_ignore_frames, MAX_BACKTRACE_FRAMES); + + return GenerateFrameData(frames, num_frames); +} + +std::string CorkscrewPtrace::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) { + // Get information about a different process. + const map_info_t* map_info; + const symbol_t* symbol; + find_symbol_ptrace(ptrace_context_, pc, &map_info, &symbol); + char* symbol_name = NULL; + if (symbol) { + if (map_info) { + *offset = pc - map_info->start - symbol->start; + } + symbol_name = symbol->name; + return symbol_name; + } + + return ""; +} + +//------------------------------------------------------------------------- +// C++ object creation functions. +//------------------------------------------------------------------------- +Backtrace* CreateCurrentObj(BacktraceMap* map) { + return new BacktraceCurrent(new CorkscrewCurrent(), map); +} + +Backtrace* CreatePtraceObj(pid_t pid, pid_t tid, BacktraceMap* map) { + return new BacktracePtrace(new CorkscrewPtrace(), pid, tid, map); +} + +Backtrace* CreateThreadObj(pid_t tid, BacktraceMap* map) { + CorkscrewThread* thread_obj = new CorkscrewThread(); + return new BacktraceThread(thread_obj, thread_obj, tid, map); +} + +//------------------------------------------------------------------------- +// BacktraceMap create function. +//------------------------------------------------------------------------- +BacktraceMap* BacktraceMap::Create(pid_t pid) { + BacktraceMap* map = new CorkscrewMap(pid); + if (!map->Build()) { + delete map; + return NULL; + } + return map; +} diff --git a/libbacktrace/Corkscrew.h b/libbacktrace/Corkscrew.h new file mode 100644 index 0000000..1633398 --- /dev/null +++ b/libbacktrace/Corkscrew.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 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_CORKSCREW_H +#define _LIBBACKTRACE_CORKSCREW_H + +#include <inttypes.h> + +#include <string> + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> + +#include <corkscrew/backtrace.h> + +#include "BacktraceImpl.h" +#include "BacktraceThread.h" + +class CorkscrewMap : public BacktraceMap { +public: + CorkscrewMap(pid_t pid); + virtual ~CorkscrewMap(); + + virtual bool Build(); + + map_info_t* GetMapInfo() { return map_info_; } + +private: + map_info_t* map_info_; +}; + +class CorkscrewCommon : public BacktraceImpl { +public: + bool GenerateFrameData(backtrace_frame_t* cork_frames, ssize_t num_frames); +}; + +class CorkscrewCurrent : public CorkscrewCommon { +public: + CorkscrewCurrent(); + virtual ~CorkscrewCurrent(); + + virtual bool Unwind(size_t num_ignore_threads); + + virtual std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset); +}; + +class CorkscrewThread : public CorkscrewCurrent, public BacktraceThreadInterface { +public: + CorkscrewThread(); + virtual ~CorkscrewThread(); + + virtual void ThreadUnwind( + siginfo_t* siginfo, void* sigcontext, size_t num_ignore_frames); +}; + +class CorkscrewPtrace : public CorkscrewCommon { +public: + CorkscrewPtrace(); + virtual ~CorkscrewPtrace(); + + virtual std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset); + + virtual bool Unwind(size_t num_ignore_threads); + +private: + ptrace_context_t* ptrace_context_; +}; + +#endif // _LIBBACKTRACE_CORKSCREW_H diff --git a/libbacktrace/UnwindCurrent.cpp b/libbacktrace/UnwindCurrent.cpp new file mode 100644 index 0000000..17b71b9 --- /dev/null +++ b/libbacktrace/UnwindCurrent.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "libbacktrace" + +#include <sys/types.h> + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> + +#define UNW_LOCAL_ONLY +#include <libunwind.h> + +#include "UnwindCurrent.h" +#include "UnwindMap.h" + +// Define the ucontext_t structures needed for each supported arch. +#if defined(__arm__) + // The current version of the <signal.h> doesn't define ucontext_t. + #include <asm/sigcontext.h> // Ensure 'struct sigcontext' is defined. + + // Machine context at the time a signal was raised. + typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + uint32_t uc_sigmask; + } ucontext_t; +#elif defined(__i386__) + #include <asm/sigcontext.h> + #include <asm/ucontext.h> + typedef struct ucontext ucontext_t; +#elif !defined(__mips__) && !defined(__aarch64__) + #error Unsupported architecture. +#endif + +//------------------------------------------------------------------------- +// UnwindCurrent functions. +//------------------------------------------------------------------------- +UnwindCurrent::UnwindCurrent() { +} + +UnwindCurrent::~UnwindCurrent() { +} + +bool UnwindCurrent::Unwind(size_t num_ignore_frames) { + int ret = unw_getcontext(&context_); + if (ret < 0) { + BACK_LOGW("unw_getcontext failed %d", ret); + return false; + } + return UnwindFromContext(num_ignore_frames, true); +} + +std::string UnwindCurrent::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) { + *offset = 0; + char buf[512]; + unw_word_t value; + if (unw_get_proc_name_by_ip(unw_local_addr_space, pc, buf, sizeof(buf), + &value, &context_) >= 0 && buf[0] != '\0') { + *offset = static_cast<uintptr_t>(value); + return buf; + } + return ""; +} + +bool UnwindCurrent::UnwindFromContext(size_t num_ignore_frames, bool resolve) { + // The cursor structure is pretty large, do not put it on the stack. + unw_cursor_t* cursor = new unw_cursor_t; + int ret = unw_init_local(cursor, &context_); + if (ret < 0) { + BACK_LOGW("unw_init_local failed %d", ret); + delete cursor; + return false; + } + + std::vector<backtrace_frame_data_t>* frames = GetFrames(); + frames->reserve(MAX_BACKTRACE_FRAMES); + size_t num_frames = 0; + do { + unw_word_t pc; + ret = unw_get_reg(cursor, UNW_REG_IP, &pc); + if (ret < 0) { + BACK_LOGW("Failed to read IP %d", ret); + break; + } + unw_word_t sp; + ret = unw_get_reg(cursor, UNW_REG_SP, &sp); + if (ret < 0) { + BACK_LOGW("Failed to read SP %d", ret); + break; + } + + if (num_ignore_frames == 0) { + frames->resize(num_frames+1); + backtrace_frame_data_t* frame = &frames->at(num_frames); + frame->num = num_frames; + frame->pc = static_cast<uintptr_t>(pc); + frame->sp = static_cast<uintptr_t>(sp); + frame->stack_size = 0; + + if (num_frames > 0) { + // Set the stack size for the previous frame. + backtrace_frame_data_t* prev = &frames->at(num_frames-1); + prev->stack_size = frame->sp - prev->sp; + } + + if (resolve) { + frame->func_name = GetFunctionName(frame->pc, &frame->func_offset); + frame->map = FindMap(frame->pc); + } else { + frame->map = NULL; + frame->func_offset = 0; + } + num_frames++; + } else { + num_ignore_frames--; + } + ret = unw_step (cursor); + } while (ret > 0 && num_frames < MAX_BACKTRACE_FRAMES); + + delete cursor; + return true; +} + +void UnwindCurrent::ExtractContext(void* sigcontext) { + unw_tdep_context_t* context = reinterpret_cast<unw_tdep_context_t*>(&context_); + const ucontext_t* uc = reinterpret_cast<const ucontext_t*>(sigcontext); + +#if defined(__arm__) + context->regs[0] = uc->uc_mcontext.arm_r0; + context->regs[1] = uc->uc_mcontext.arm_r1; + context->regs[2] = uc->uc_mcontext.arm_r2; + context->regs[3] = uc->uc_mcontext.arm_r3; + context->regs[4] = uc->uc_mcontext.arm_r4; + context->regs[5] = uc->uc_mcontext.arm_r5; + context->regs[6] = uc->uc_mcontext.arm_r6; + context->regs[7] = uc->uc_mcontext.arm_r7; + context->regs[8] = uc->uc_mcontext.arm_r8; + context->regs[9] = uc->uc_mcontext.arm_r9; + context->regs[10] = uc->uc_mcontext.arm_r10; + context->regs[11] = uc->uc_mcontext.arm_fp; + context->regs[12] = uc->uc_mcontext.arm_ip; + context->regs[13] = uc->uc_mcontext.arm_sp; + context->regs[14] = uc->uc_mcontext.arm_lr; + context->regs[15] = uc->uc_mcontext.arm_pc; +#elif defined(__mips__) || defined(__i386__) + context->uc_mcontext = uc->uc_mcontext; +#endif +} + +//------------------------------------------------------------------------- +// UnwindThread functions. +//------------------------------------------------------------------------- +UnwindThread::UnwindThread() { +} + +UnwindThread::~UnwindThread() { +} + +void UnwindThread::ThreadUnwind( + siginfo_t* /*siginfo*/, void* sigcontext, size_t num_ignore_frames) { + ExtractContext(sigcontext); + UnwindFromContext(num_ignore_frames, false); +} + +//------------------------------------------------------------------------- +// C++ object creation function. +//------------------------------------------------------------------------- +Backtrace* CreateCurrentObj(BacktraceMap* map) { + return new BacktraceCurrent(new UnwindCurrent(), map); +} + +Backtrace* CreateThreadObj(pid_t tid, BacktraceMap* map) { + UnwindThread* thread_obj = new UnwindThread(); + return new BacktraceThread(thread_obj, thread_obj, tid, map); +} diff --git a/libbacktrace/UnwindCurrent.h b/libbacktrace/UnwindCurrent.h new file mode 100644 index 0000000..acce110 --- /dev/null +++ b/libbacktrace/UnwindCurrent.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 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_UNWIND_CURRENT_H +#define _LIBBACKTRACE_UNWIND_CURRENT_H + +#include <string> + +#include "BacktraceImpl.h" +#include "BacktraceThread.h" + +#define UNW_LOCAL_ONLY +#include <libunwind.h> + +class UnwindCurrent : public BacktraceImpl { +public: + UnwindCurrent(); + virtual ~UnwindCurrent(); + + virtual bool Unwind(size_t num_ignore_frames); + + virtual std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset); + + bool UnwindFromContext(size_t num_ignore_frames, bool resolve); + + void ExtractContext(void* sigcontext); + +protected: + unw_context_t context_; +}; + +class UnwindThread : public UnwindCurrent, public BacktraceThreadInterface { +public: + UnwindThread(); + virtual ~UnwindThread(); + + virtual void ThreadUnwind( + siginfo_t* siginfo, void* sigcontext, size_t num_ignore_frames); +}; + +#endif // _LIBBACKTRACE_UNWIND_CURRENT_H diff --git a/libbacktrace/UnwindMap.cpp b/libbacktrace/UnwindMap.cpp new file mode 100644 index 0000000..03bb192 --- /dev/null +++ b/libbacktrace/UnwindMap.cpp @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#define LOG_TAG "libbacktrace" + +#include <pthread.h> +#include <sys/types.h> +#include <unistd.h> + +#include <backtrace/BacktraceMap.h> + +#include <libunwind.h> + +#include "UnwindMap.h" + +//------------------------------------------------------------------------- +// libunwind has a single shared address space for the current process +// aka local. If multiple maps are created for the current pid, then +// 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_set(unw_local_addr_space, NULL); + unw_map_cursor_destroy(&map_cursor_); + } + pthread_mutex_unlock(&g_map_mutex); + } else { + unw_map_cursor_destroy(&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 to this cursor map. + unw_map_set(unw_local_addr_space, &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; + + // 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)) { + backtrace_map_t map; + + map.start = unw_map.start; + map.end = unw_map.end; + map.flags = unw_map.flags; + map.name = unw_map.path; + + // The maps are in descending order, but we want them in ascending order. + maps_.push_front(map); + } + + return true; +} + +//------------------------------------------------------------------------- +// BacktraceMap create function. +//------------------------------------------------------------------------- +BacktraceMap* BacktraceMap::Create(pid_t pid) { + BacktraceMap* map = new UnwindMap(pid); + if (!map->Build()) { + delete map; + return NULL; + } + return map; +} diff --git a/libbacktrace/UnwindMap.h b/libbacktrace/UnwindMap.h new file mode 100644 index 0000000..5a874e8 --- /dev/null +++ b/libbacktrace/UnwindMap.h @@ -0,0 +1,39 @@ +/* + * 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_UNWIND_MAP_H +#define _LIBBACKTRACE_UNWIND_MAP_H + +#include <backtrace/BacktraceMap.h> + +// The unw_map_cursor_t structure is different depending on whether it is +// the local or remote version. In order to get the correct version, include +// libunwind.h first then this header. + +class UnwindMap : public BacktraceMap { +public: + UnwindMap(pid_t pid); + virtual ~UnwindMap(); + + virtual bool Build(); + + unw_map_cursor_t* GetMapCursor() { return &map_cursor_; } + +private: + unw_map_cursor_t map_cursor_; +}; + +#endif // _LIBBACKTRACE_UNWIND_MAP_H diff --git a/libbacktrace/UnwindPtrace.cpp b/libbacktrace/UnwindPtrace.cpp new file mode 100644 index 0000000..732dae8 --- /dev/null +++ b/libbacktrace/UnwindPtrace.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "libbacktrace" + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> + +#include <sys/types.h> +#include <string.h> + +#include <libunwind.h> +#include <libunwind-ptrace.h> + +#include "UnwindMap.h" +#include "UnwindPtrace.h" + +UnwindPtrace::UnwindPtrace() : addr_space_(NULL), upt_info_(NULL) { +} + +UnwindPtrace::~UnwindPtrace() { + if (upt_info_) { + _UPT_destroy(upt_info_); + upt_info_ = NULL; + } + if (addr_space_) { + // Remove the map from the address space before destroying it. + // It will be freed in the UnwindMap destructor. + unw_map_set(addr_space_, NULL); + + unw_destroy_addr_space(addr_space_); + addr_space_ = NULL; + } +} + +bool UnwindPtrace::Unwind(size_t num_ignore_frames) { + addr_space_ = unw_create_addr_space(&_UPT_accessors, 0); + if (!addr_space_) { + BACK_LOGW("unw_create_addr_space failed."); + return false; + } + + UnwindMap* map = static_cast<UnwindMap*>(GetMap()); + unw_map_set(addr_space_, map->GetMapCursor()); + + upt_info_ = reinterpret_cast<struct UPT_info*>(_UPT_create(Tid())); + if (!upt_info_) { + BACK_LOGW("Failed to create upt info."); + return false; + } + + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, addr_space_, upt_info_); + if (ret < 0) { + BACK_LOGW("unw_init_remote failed %d", ret); + return false; + } + + std::vector<backtrace_frame_data_t>* frames = GetFrames(); + frames->reserve(MAX_BACKTRACE_FRAMES); + size_t num_frames = 0; + do { + unw_word_t pc; + ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (ret < 0) { + BACK_LOGW("Failed to read IP %d", ret); + break; + } + unw_word_t sp; + ret = unw_get_reg(&cursor, UNW_REG_SP, &sp); + if (ret < 0) { + BACK_LOGW("Failed to read SP %d", ret); + break; + } + + if (num_ignore_frames == 0) { + frames->resize(num_frames+1); + backtrace_frame_data_t* frame = &frames->at(num_frames); + frame->num = num_frames; + frame->pc = static_cast<uintptr_t>(pc); + frame->sp = static_cast<uintptr_t>(sp); + frame->stack_size = 0; + + if (num_frames > 0) { + backtrace_frame_data_t* prev = &frames->at(num_frames-1); + prev->stack_size = frame->sp - prev->sp; + } + + frame->func_name = GetFunctionName(frame->pc, &frame->func_offset); + + frame->map = FindMap(frame->pc); + + num_frames++; + } else { + num_ignore_frames--; + } + ret = unw_step (&cursor); + } while (ret > 0 && num_frames < MAX_BACKTRACE_FRAMES); + + return true; +} + +std::string UnwindPtrace::GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) { + *offset = 0; + char buf[512]; + unw_word_t value; + if (unw_get_proc_name_by_ip(addr_space_, pc, buf, sizeof(buf), &value, + upt_info_) >= 0 && buf[0] != '\0') { + *offset = static_cast<uintptr_t>(value); + return buf; + } + return ""; +} + +//------------------------------------------------------------------------- +// C++ object creation function. +//------------------------------------------------------------------------- +Backtrace* CreatePtraceObj(pid_t pid, pid_t tid, BacktraceMap* map) { + return new BacktracePtrace(new UnwindPtrace(), pid, tid, map); +} diff --git a/libbacktrace/UnwindPtrace.h b/libbacktrace/UnwindPtrace.h new file mode 100644 index 0000000..1e82117 --- /dev/null +++ b/libbacktrace/UnwindPtrace.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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_UNWIND_PTRACE_H +#define _LIBBACKTRACE_UNWIND_PTRACE_H + +#include <string> + +#include "BacktraceImpl.h" + +#include <libunwind.h> + +class UnwindPtrace : public BacktraceImpl { +public: + UnwindPtrace(); + virtual ~UnwindPtrace(); + + virtual bool Unwind(size_t num_ignore_frames); + + virtual std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset); + +private: + unw_addr_space_t addr_space_; + struct UPT_info* upt_info_; +}; + +#endif // _LIBBACKTRACE_UNWIND_PTRACE_H diff --git a/libbacktrace/backtrace_test.cpp b/libbacktrace/backtrace_test.cpp new file mode 100644 index 0000000..23eaf92 --- /dev/null +++ b/libbacktrace/backtrace_test.cpp @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2013 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 <dirent.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include <backtrace/Backtrace.h> +#include <backtrace/BacktraceMap.h> +#include <UniquePtr.h> + +#include <cutils/atomic.h> +#include <gtest/gtest.h> + +#include <vector> + +#include "thread_utils.h" + +// Number of microseconds per milliseconds. +#define US_PER_MSEC 1000 + +// Number of nanoseconds in a second. +#define NS_PER_SEC 1000000000ULL + +// Number of simultaneous dumping operations to perform. +#define NUM_THREADS 20 + +// Number of simultaneous threads running in our forked process. +#define NUM_PTRACE_THREADS 5 + +struct thread_t { + pid_t tid; + int32_t state; + pthread_t threadId; +}; + +struct dump_thread_t { + thread_t thread; + Backtrace* backtrace; + int32_t* now; + int32_t done; +}; + +extern "C" { +// Prototypes for functions in the test library. +int test_level_one(int, int, int, int, void (*)(void*), void*); + +int test_recursive_call(int, void (*)(void*), void*); +} + +uint64_t NanoTime() { + struct timespec t = { 0, 0 }; + clock_gettime(CLOCK_MONOTONIC, &t); + return static_cast<uint64_t>(t.tv_sec * NS_PER_SEC + t.tv_nsec); +} + +void DumpFrames(Backtrace* backtrace) { + if (backtrace->NumFrames() == 0) { + printf(" No frames to dump\n"); + return; + } + + for (size_t i = 0; i < backtrace->NumFrames(); i++) { + printf(" %s\n", backtrace->FormatFrameData(i).c_str()); + } +} + +void WaitForStop(pid_t pid) { + uint64_t start = NanoTime(); + + siginfo_t si; + while (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) < 0 && (errno == EINTR || errno == ESRCH)) { + if ((NanoTime() - start) > NS_PER_SEC) { + printf("The process did not get to a stopping point in 1 second.\n"); + break; + } + usleep(US_PER_MSEC); + } +} + +bool ReadyLevelBacktrace(Backtrace* backtrace) { + // See if test_level_four is in the backtrace. + bool found = false; + for (Backtrace::const_iterator it = backtrace->begin(); it != backtrace->end(); ++it) { + if (it->func_name == "test_level_four") { + found = true; + break; + } + } + + return found; +} + +void VerifyLevelDump(Backtrace* backtrace) { + ASSERT_GT(backtrace->NumFrames(), static_cast<size_t>(0)); + ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES)); + + // Look through the frames starting at the highest to find the + // frame we want. + size_t frame_num = 0; + for (size_t i = backtrace->NumFrames()-1; i > 2; i--) { + if (backtrace->GetFrame(i)->func_name == "test_level_one") { + frame_num = i; + break; + } + } + ASSERT_LT(static_cast<size_t>(0), frame_num); + ASSERT_LE(static_cast<size_t>(3), frame_num); + + ASSERT_EQ(backtrace->GetFrame(frame_num)->func_name, "test_level_one"); + ASSERT_EQ(backtrace->GetFrame(frame_num-1)->func_name, "test_level_two"); + ASSERT_EQ(backtrace->GetFrame(frame_num-2)->func_name, "test_level_three"); + ASSERT_EQ(backtrace->GetFrame(frame_num-3)->func_name, "test_level_four"); +} + +void VerifyLevelBacktrace(void*) { + UniquePtr<Backtrace> backtrace( + Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyLevelDump(backtrace.get()); +} + +bool ReadyMaxBacktrace(Backtrace* backtrace) { + return (backtrace->NumFrames() == MAX_BACKTRACE_FRAMES); +} + +void VerifyMaxDump(Backtrace* backtrace) { + ASSERT_EQ(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES)); + // Verify that the last frame is our recursive call. + ASSERT_EQ(backtrace->GetFrame(MAX_BACKTRACE_FRAMES-1)->func_name, + "test_recursive_call"); +} + +void VerifyMaxBacktrace(void*) { + UniquePtr<Backtrace> backtrace( + Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyMaxDump(backtrace.get()); +} + +void ThreadSetState(void* data) { + thread_t* thread = reinterpret_cast<thread_t*>(data); + android_atomic_acquire_store(1, &thread->state); + volatile int i = 0; + while (thread->state) { + i++; + } +} + +void VerifyThreadTest(pid_t tid, void (*VerifyFunc)(Backtrace*)) { + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), tid)); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyFunc(backtrace.get()); +} + +bool WaitForNonZero(int32_t* value, uint64_t seconds) { + uint64_t start = NanoTime(); + do { + if (android_atomic_acquire_load(value)) { + return true; + } + } while ((NanoTime() - start) < seconds * NS_PER_SEC); + return false; +} + +TEST(libbacktrace, local_trace) { + ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelBacktrace, NULL), 0); +} + +void VerifyIgnoreFrames( + Backtrace* bt_all, Backtrace* bt_ign1, + Backtrace* bt_ign2, const char* cur_proc) { + EXPECT_EQ(bt_all->NumFrames(), bt_ign1->NumFrames() + 1); + EXPECT_EQ(bt_all->NumFrames(), bt_ign2->NumFrames() + 2); + + // Check all of the frames are the same > the current frame. + bool check = (cur_proc == NULL); + for (size_t i = 0; i < bt_ign2->NumFrames(); i++) { + if (check) { + EXPECT_EQ(bt_ign2->GetFrame(i)->pc, bt_ign1->GetFrame(i+1)->pc); + EXPECT_EQ(bt_ign2->GetFrame(i)->sp, bt_ign1->GetFrame(i+1)->sp); + EXPECT_EQ(bt_ign2->GetFrame(i)->stack_size, bt_ign1->GetFrame(i+1)->stack_size); + + EXPECT_EQ(bt_ign2->GetFrame(i)->pc, bt_all->GetFrame(i+2)->pc); + EXPECT_EQ(bt_ign2->GetFrame(i)->sp, bt_all->GetFrame(i+2)->sp); + EXPECT_EQ(bt_ign2->GetFrame(i)->stack_size, bt_all->GetFrame(i+2)->stack_size); + } + if (!check && bt_ign2->GetFrame(i)->func_name == cur_proc) { + check = true; + } + } +} + +void VerifyLevelIgnoreFrames(void*) { + UniquePtr<Backtrace> all( + Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(all.get() != NULL); + ASSERT_TRUE(all->Unwind(0)); + + UniquePtr<Backtrace> ign1( + Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(ign1.get() != NULL); + ASSERT_TRUE(ign1->Unwind(1)); + + UniquePtr<Backtrace> ign2( + Backtrace::Create(BACKTRACE_CURRENT_PROCESS, BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(ign2.get() != NULL); + ASSERT_TRUE(ign2->Unwind(2)); + + VerifyIgnoreFrames(all.get(), ign1.get(), ign2.get(), "VerifyLevelIgnoreFrames"); +} + +TEST(libbacktrace, local_trace_ignore_frames) { + ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelIgnoreFrames, NULL), 0); +} + +TEST(libbacktrace, local_max_trace) { + ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxBacktrace, NULL), 0); +} + +void VerifyProcTest(pid_t pid, pid_t tid, bool share_map, + bool (*ReadyFunc)(Backtrace*), + void (*VerifyFunc)(Backtrace*)) { + pid_t ptrace_tid; + if (tid < 0) { + ptrace_tid = pid; + } else { + ptrace_tid = tid; + } + uint64_t start = NanoTime(); + bool verified = false; + do { + usleep(US_PER_MSEC); + if (ptrace(PTRACE_ATTACH, ptrace_tid, 0, 0) == 0) { + // Wait for the process to get to a stopping point. + WaitForStop(ptrace_tid); + + UniquePtr<BacktraceMap> map; + if (share_map) { + map.reset(BacktraceMap::Create(pid)); + } + UniquePtr<Backtrace> backtrace(Backtrace::Create(pid, tid, map.get())); + ASSERT_TRUE(backtrace->Unwind(0)); + ASSERT_TRUE(backtrace.get() != NULL); + if (ReadyFunc(backtrace.get())) { + VerifyFunc(backtrace.get()); + verified = true; + } + + ASSERT_TRUE(ptrace(PTRACE_DETACH, ptrace_tid, 0, 0) == 0); + } + // If 5 seconds have passed, then we are done. + } while (!verified && (NanoTime() - start) <= 5 * NS_PER_SEC); + ASSERT_TRUE(verified); +} + +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); + } + VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyLevelBacktrace, VerifyLevelDump); + + kill(pid, SIGKILL); + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +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); + } + + VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, true, ReadyLevelBacktrace, VerifyLevelDump); + + kill(pid, SIGKILL); + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +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); + } + VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyMaxBacktrace, VerifyMaxDump); + + kill(pid, SIGKILL); + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +void VerifyProcessIgnoreFrames(Backtrace* bt_all) { + UniquePtr<Backtrace> ign1(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(ign1.get() != NULL); + ASSERT_TRUE(ign1->Unwind(1)); + + UniquePtr<Backtrace> ign2(Backtrace::Create(bt_all->Pid(), BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(ign2.get() != NULL); + ASSERT_TRUE(ign2->Unwind(2)); + + VerifyIgnoreFrames(bt_all, ign1.get(), ign2.get(), NULL); +} + +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); + } + VerifyProcTest(pid, BACKTRACE_CURRENT_THREAD, false, ReadyLevelBacktrace, VerifyProcessIgnoreFrames); + + kill(pid, SIGKILL); + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +// Create a process with multiple threads and dump all of the threads. +void* PtraceThreadLevelRun(void*) { + EXPECT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); + return NULL; +} + +void GetThreads(pid_t pid, std::vector<pid_t>* threads) { + // Get the list of tasks. + char task_path[128]; + snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid); + + DIR* tasks_dir = opendir(task_path); + ASSERT_TRUE(tasks_dir != NULL); + struct dirent* entry; + while ((entry = readdir(tasks_dir)) != NULL) { + char* end; + pid_t tid = strtoul(entry->d_name, &end, 10); + if (*end == '\0') { + threads->push_back(tid); + } + } + closedir(tasks_dir); +} + +TEST(libbacktrace, ptrace_threads) { + pid_t pid; + if ((pid = fork()) == 0) { + for (size_t i = 0; i < NUM_PTRACE_THREADS; i++) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_t thread; + ASSERT_TRUE(pthread_create(&thread, &attr, PtraceThreadLevelRun, NULL) == 0); + } + ASSERT_NE(test_level_one(1, 2, 3, 4, NULL, NULL), 0); + exit(1); + } + + // Check to see that all of the threads are running before unwinding. + std::vector<pid_t> threads; + uint64_t start = NanoTime(); + do { + usleep(US_PER_MSEC); + threads.clear(); + GetThreads(pid, &threads); + } while ((threads.size() != NUM_PTRACE_THREADS + 1) && + ((NanoTime() - start) <= 5 * NS_PER_SEC)); + ASSERT_EQ(threads.size(), static_cast<size_t>(NUM_PTRACE_THREADS + 1)); + + ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0); + WaitForStop(pid); + for (std::vector<int>::const_iterator it = threads.begin(); it != threads.end(); ++it) { + // Skip the current forked process, we only care about the threads. + if (pid == *it) { + continue; + } + VerifyProcTest(pid, *it, false, ReadyLevelBacktrace, VerifyLevelDump); + } + ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0); + + kill(pid, SIGKILL); + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +void VerifyLevelThread(void*) { + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), gettid())); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyLevelDump(backtrace.get()); +} + +TEST(libbacktrace, thread_current_level) { + ASSERT_NE(test_level_one(1, 2, 3, 4, VerifyLevelThread, NULL), 0); +} + +void VerifyMaxThread(void*) { + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), gettid())); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyMaxDump(backtrace.get()); +} + +TEST(libbacktrace, thread_current_max) { + ASSERT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, VerifyMaxThread, NULL), 0); +} + +void* ThreadLevelRun(void* data) { + thread_t* thread = reinterpret_cast<thread_t*>(data); + + thread->tid = gettid(); + EXPECT_NE(test_level_one(1, 2, 3, 4, ThreadSetState, data), 0); + return NULL; +} + +TEST(libbacktrace, thread_level_trace) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + thread_t thread_data = { 0, 0, 0 }; + pthread_t thread; + ASSERT_TRUE(pthread_create(&thread, &attr, ThreadLevelRun, &thread_data) == 0); + + // Wait up to 2 seconds for the tid to be set. + ASSERT_TRUE(WaitForNonZero(&thread_data.state, 2)); + + // Save the current signal action and make sure it is restored afterwards. + struct sigaction cur_action; + ASSERT_TRUE(sigaction(SIGURG, NULL, &cur_action) == 0); + + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), thread_data.tid)); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyLevelDump(backtrace.get()); + + // Tell the thread to exit its infinite loop. + android_atomic_acquire_store(0, &thread_data.state); + + // Verify that the old action was restored. + struct sigaction new_action; + ASSERT_TRUE(sigaction(SIGURG, NULL, &new_action) == 0); + EXPECT_EQ(cur_action.sa_sigaction, new_action.sa_sigaction); + EXPECT_EQ(cur_action.sa_flags, new_action.sa_flags); +} + +TEST(libbacktrace, thread_ignore_frames) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + thread_t thread_data = { 0, 0, 0 }; + pthread_t thread; + ASSERT_TRUE(pthread_create(&thread, &attr, ThreadLevelRun, &thread_data) == 0); + + // Wait up to 2 seconds for the tid to be set. + ASSERT_TRUE(WaitForNonZero(&thread_data.state, 2)); + + UniquePtr<Backtrace> all(Backtrace::Create(getpid(), thread_data.tid)); + ASSERT_TRUE(all.get() != NULL); + ASSERT_TRUE(all->Unwind(0)); + + UniquePtr<Backtrace> ign1(Backtrace::Create(getpid(), thread_data.tid)); + ASSERT_TRUE(ign1.get() != NULL); + ASSERT_TRUE(ign1->Unwind(1)); + + UniquePtr<Backtrace> ign2(Backtrace::Create(getpid(), thread_data.tid)); + ASSERT_TRUE(ign2.get() != NULL); + ASSERT_TRUE(ign2->Unwind(2)); + + VerifyIgnoreFrames(all.get(), ign1.get(), ign2.get(), NULL); + + // Tell the thread to exit its infinite loop. + android_atomic_acquire_store(0, &thread_data.state); +} + +void* ThreadMaxRun(void* data) { + thread_t* thread = reinterpret_cast<thread_t*>(data); + + thread->tid = gettid(); + EXPECT_NE(test_recursive_call(MAX_BACKTRACE_FRAMES+10, ThreadSetState, data), 0); + return NULL; +} + +TEST(libbacktrace, thread_max_trace) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + thread_t thread_data = { 0, 0, 0 }; + pthread_t thread; + ASSERT_TRUE(pthread_create(&thread, &attr, ThreadMaxRun, &thread_data) == 0); + + // Wait for the tid to be set. + ASSERT_TRUE(WaitForNonZero(&thread_data.state, 2)); + + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), thread_data.tid)); + ASSERT_TRUE(backtrace.get() != NULL); + ASSERT_TRUE(backtrace->Unwind(0)); + + VerifyMaxDump(backtrace.get()); + + // Tell the thread to exit its infinite loop. + android_atomic_acquire_store(0, &thread_data.state); +} + +void* ThreadDump(void* data) { + dump_thread_t* dump = reinterpret_cast<dump_thread_t*>(data); + while (true) { + if (android_atomic_acquire_load(dump->now)) { + break; + } + } + + // The status of the actual unwind will be checked elsewhere. + dump->backtrace = Backtrace::Create(getpid(), dump->thread.tid); + dump->backtrace->Unwind(0); + + android_atomic_acquire_store(1, &dump->done); + + return NULL; +} + +TEST(libbacktrace, thread_multiple_dump) { + // Dump NUM_THREADS simultaneously. + std::vector<thread_t> runners(NUM_THREADS); + std::vector<dump_thread_t> dumpers(NUM_THREADS); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (size_t i = 0; i < NUM_THREADS; i++) { + // Launch the runners, they will spin in hard loops doing nothing. + runners[i].tid = 0; + runners[i].state = 0; + ASSERT_TRUE(pthread_create(&runners[i].threadId, &attr, ThreadMaxRun, &runners[i]) == 0); + } + + // Wait for tids to be set. + for (std::vector<thread_t>::iterator it = runners.begin(); it != runners.end(); ++it) { + ASSERT_TRUE(WaitForNonZero(&it->state, 10)); + } + + // Start all of the dumpers at once, they will spin until they are signalled + // to begin their dump run. + int32_t dump_now = 0; + for (size_t i = 0; i < NUM_THREADS; i++) { + dumpers[i].thread.tid = runners[i].tid; + dumpers[i].thread.state = 0; + dumpers[i].done = 0; + dumpers[i].now = &dump_now; + + ASSERT_TRUE(pthread_create(&dumpers[i].thread.threadId, &attr, ThreadDump, &dumpers[i]) == 0); + } + + // Start all of the dumpers going at once. + android_atomic_acquire_store(1, &dump_now); + + for (size_t i = 0; i < NUM_THREADS; i++) { + ASSERT_TRUE(WaitForNonZero(&dumpers[i].done, 10)); + + // Tell the runner thread to exit its infinite loop. + android_atomic_acquire_store(0, &runners[i].state); + + ASSERT_TRUE(dumpers[i].backtrace != NULL); + VerifyMaxDump(dumpers[i].backtrace); + + delete dumpers[i].backtrace; + dumpers[i].backtrace = NULL; + } +} + +// This test is for UnwindMaps that should share the same map cursor when +// multiple maps are created for the current process at the same time. +TEST(libbacktrace, simultaneous_maps) { + BacktraceMap* map1 = BacktraceMap::Create(getpid()); + BacktraceMap* map2 = BacktraceMap::Create(getpid()); + BacktraceMap* map3 = BacktraceMap::Create(getpid()); + + Backtrace* back1 = Backtrace::Create(getpid(), BACKTRACE_CURRENT_THREAD, map1); + EXPECT_TRUE(back1->Unwind(0)); + delete back1; + delete map1; + + Backtrace* back2 = Backtrace::Create(getpid(), BACKTRACE_CURRENT_THREAD, map2); + EXPECT_TRUE(back2->Unwind(0)); + delete back2; + delete map2; + + Backtrace* back3 = Backtrace::Create(getpid(), BACKTRACE_CURRENT_THREAD, map3); + EXPECT_TRUE(back3->Unwind(0)); + delete back3; + delete map3; +} + +TEST(libbacktrace, format_test) { + UniquePtr<Backtrace> backtrace(Backtrace::Create(getpid(), BACKTRACE_CURRENT_THREAD)); + ASSERT_TRUE(backtrace.get() != NULL); + + backtrace_frame_data_t frame; + frame.num = 1; + frame.pc = 2; + frame.sp = 0; + frame.stack_size = 0; + frame.map = NULL; + frame.func_offset = 0; + + backtrace_map_t map; + map.start = 0; + map.end = 0; + + // Check no map set. + frame.num = 1; +#if defined(__LP64__) + EXPECT_EQ("#01 pc 0000000000000002 <unknown>", +#else + EXPECT_EQ("#01 pc 00000002 <unknown>", +#endif + backtrace->FormatFrameData(&frame)); + + // Check map name empty, but exists. + frame.map = ↦ + map.start = 1; +#if defined(__LP64__) + EXPECT_EQ("#01 pc 0000000000000001 <unknown>", +#else + EXPECT_EQ("#01 pc 00000001 <unknown>", +#endif + backtrace->FormatFrameData(&frame)); + + + // Check relative pc is set and map name is set. + frame.pc = 0x12345679; + frame.map = ↦ + map.name = "MapFake"; + map.start = 1; +#if defined(__LP64__) + EXPECT_EQ("#01 pc 0000000012345678 MapFake", +#else + EXPECT_EQ("#01 pc 12345678 MapFake", +#endif + backtrace->FormatFrameData(&frame)); + + // Check func_name is set, but no func offset. + frame.func_name = "ProcFake"; +#if defined(__LP64__) + EXPECT_EQ("#01 pc 0000000012345678 MapFake (ProcFake)", +#else + EXPECT_EQ("#01 pc 12345678 MapFake (ProcFake)", +#endif + backtrace->FormatFrameData(&frame)); + + // Check func_name is set, and func offset is non-zero. + frame.func_offset = 645; +#if defined(__LP64__) + EXPECT_EQ("#01 pc 0000000012345678 MapFake (ProcFake+645)", +#else + EXPECT_EQ("#01 pc 12345678 MapFake (ProcFake+645)", +#endif + backtrace->FormatFrameData(&frame)); +} diff --git a/libbacktrace/backtrace_testlib.c b/libbacktrace/backtrace_testlib.c new file mode 100644 index 0000000..d4d15db --- /dev/null +++ b/libbacktrace/backtrace_testlib.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 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 <stdio.h> + +int test_level_four(int one, int two, int three, int four, + void (*callback_func)(void*), void* data) { + if (callback_func != NULL) { + callback_func(data); + } else { + while (1) { + } + } + return one + two + three + four; +} + +int test_level_three(int one, int two, int three, int four, + void (*callback_func)(void*), void* data) { + return test_level_four(one+3, two+6, three+9, four+12, callback_func, data) + 3; +} + +int test_level_two(int one, int two, int three, int four, + void (*callback_func)(void*), void* data) { + return test_level_three(one+2, two+4, three+6, four+8, callback_func, data) + 2; +} + +int test_level_one(int one, int two, int three, int four, + void (*callback_func)(void*), void* data) { + return test_level_two(one+1, two+2, three+3, four+4, callback_func, data) + 1; +} + +int test_recursive_call(int level, void (*callback_func)(void*), void* data) { + if (level > 0) { + return test_recursive_call(level - 1, callback_func, data) + level; + } else if (callback_func != NULL) { + callback_func(data); + } else { + while (1) { + } + } + return 0; +} diff --git a/libbacktrace/thread_utils.c b/libbacktrace/thread_utils.c new file mode 100644 index 0000000..6f4cd3c --- /dev/null +++ b/libbacktrace/thread_utils.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 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 "thread_utils.h" + +#if defined(__APPLE__) + +#include <sys/syscall.h> + +// Mac OS >= 10.6 has a system call equivalent to Linux's gettid(). +pid_t gettid() { + return syscall(SYS_thread_selfid); +} + +#elif !defined(__BIONIC__) + +// glibc doesn't implement or export either gettid or tgkill. +#include <unistd.h> +#include <sys/syscall.h> + +pid_t gettid() { + return syscall(__NR_gettid); +} + +int tgkill(int tgid, int tid, int sig) { + return syscall(__NR_tgkill, tgid, tid, sig); +} + +#endif diff --git a/libbacktrace/thread_utils.h b/libbacktrace/thread_utils.h new file mode 100644 index 0000000..ae4c929 --- /dev/null +++ b/libbacktrace/thread_utils.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 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_THREAD_UTILS_H +#define _LIBBACKTRACE_THREAD_UTILS_H + +#include <unistd.h> + +__BEGIN_DECLS + +int tgkill(int tgid, int tid, int sig); + +pid_t gettid(); + +__END_DECLS + +#endif /* _LIBBACKTRACE_THREAD_UTILS_H */ |