diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk index f7a5f8207..de0f94334 100644 --- a/debuggerd/Android.mk +++ b/debuggerd/Android.mk @@ -99,6 +99,7 @@ debuggerd_c_includes := \ debuggerd_cpp_flags := \ $(common_cppflags) \ -Wno-missing-field-initializers \ + -fno-rtti \ # Only build the host tests on linux. ifeq ($(HOST_OS),linux) diff --git a/include/backtrace/Backtrace.h b/include/backtrace/Backtrace.h index 290682a74..f440bd283 100644 --- a/include/backtrace/Backtrace.h +++ b/include/backtrace/Backtrace.h @@ -52,6 +52,12 @@ struct ucontext; typedef ucontext ucontext_t; #endif +struct backtrace_stackinfo_t { + uint64_t start; + uint64_t end; + const uint8_t* data; +}; + class Backtrace { public: // Create the correct Backtrace object based on what is to be unwound. @@ -66,6 +72,14 @@ public: // If map is not NULL, the map is still owned by the caller. static Backtrace* Create(pid_t pid, pid_t tid, BacktraceMap* map = NULL); + // Create an offline Backtrace object that can be used to do an unwind without a process + // that is still running. If cache_file is set to true, then elf information will be cached + // for this call. The cached information survives until the calling process ends. This means + // that subsequent calls to create offline Backtrace objects will continue to use the same + // cache. It also assumes that the elf files used for each offline unwind are the same. + static Backtrace* CreateOffline(pid_t pid, pid_t tid, BacktraceMap* map, + const backtrace_stackinfo_t& stack, bool cache_file = false); + virtual ~Backtrace(); // Get the current stack trace and store in the backtrace_ structure. diff --git a/include/backtrace/BacktraceMap.h b/include/backtrace/BacktraceMap.h index bb18aa2e2..2373c45e8 100644 --- a/include/backtrace/BacktraceMap.h +++ b/include/backtrace/BacktraceMap.h @@ -31,6 +31,7 @@ #include #include +#include struct backtrace_map_t { uintptr_t start = 0; @@ -48,6 +49,8 @@ public: // is unsupported. static BacktraceMap* Create(pid_t pid, bool uncached = false); + static BacktraceMap* Create(pid_t pid, const std::vector& maps); + virtual ~BacktraceMap(); // Fill in the map data structure for the given address. diff --git a/libbacktrace/Android.build.mk b/libbacktrace/Android.build.mk index 4983b5530..8e63dab57 100644 --- a/libbacktrace/Android.build.mk +++ b/libbacktrace/Android.build.mk @@ -69,7 +69,10 @@ LOCAL_LDLIBS := \ $($(module)_ldlibs) \ $($(module)_ldlibs_$(build_type)) \ +LOCAL_STRIP_MODULE := $($(module)_strip_module) + ifeq ($(build_type),target) + include $(LLVM_DEVICE_BUILD_MK) include $(BUILD_$(build_target)) endif @@ -77,6 +80,7 @@ ifeq ($(build_type),host) # Only build if host builds are supported. ifeq ($(build_host),true) LOCAL_CFLAGS += -Wno-extern-c-compat -fno-omit-frame-pointer + include $(LLVM_HOST_BUILD_MK) include $(BUILD_HOST_$(build_target)) endif endif diff --git a/libbacktrace/Android.mk b/libbacktrace/Android.mk index 395d67700..9c6742ecf 100644 --- a/libbacktrace/Android.mk +++ b/libbacktrace/Android.mk @@ -25,6 +25,7 @@ libbacktrace_common_conlyflags := \ libbacktrace_common_cppflags := \ -std=gnu++11 \ + -I external/libunwind/include/tdep \ # The latest clang (r230699) does not allow SP/PC to be declared in inline asm lists. libbacktrace_common_clang_cflags += \ @@ -37,6 +38,9 @@ build_host := true endif endif +LLVM_ROOT_PATH := external/llvm +include $(LLVM_ROOT_PATH)/llvm.mk + #------------------------------------------------------------------------- # The libbacktrace library. #------------------------------------------------------------------------- @@ -44,6 +48,7 @@ libbacktrace_src_files := \ Backtrace.cpp \ BacktraceCurrent.cpp \ BacktraceMap.cpp \ + BacktraceOffline.cpp \ BacktracePtrace.cpp \ thread_utils.c \ ThreadEntry.cpp \ @@ -56,6 +61,20 @@ libbacktrace_shared_libraries := \ liblog \ libunwind \ +# Use shared llvm library on device to save space. +libbacktrace_shared_libraries_target := \ + libLLVM \ + +# Use static llvm libraries on host to remove dependency on 32-bit llvm shared library +# which is not included in the prebuilt. +libbacktrace_static_libraries_host := \ + libLLVMObject \ + libLLVMBitReader \ + libLLVMMC \ + libLLVMMCParser \ + libLLVMCore \ + libLLVMSupport \ + libbacktrace_ldlibs_host := \ -lpthread \ -lrt \ @@ -86,6 +105,8 @@ libbacktrace_test_cflags := \ libbacktrace_test_src_files := \ backtrace_testlib.c \ +libbacktrace_test_strip_module := false + module := libbacktrace_test module_tag := debug build_type := target @@ -107,6 +128,7 @@ backtrace_test_cflags_target := \ -DENABLE_PSS_TESTS \ backtrace_test_src_files := \ + backtrace_offline_test.cpp \ backtrace_test.cpp \ GetPss.cpp \ thread_utils.c \ @@ -120,6 +142,7 @@ backtrace_test_shared_libraries := \ libbacktrace \ libbase \ libcutils \ + libunwind \ backtrace_test_shared_libraries_target += \ libdl \ @@ -127,6 +150,8 @@ backtrace_test_shared_libraries_target += \ backtrace_test_ldlibs_host += \ -ldl \ +backtrace_test_strip_module := false + module := backtrace_test module_tag := debug build_type := target diff --git a/libbacktrace/Backtrace.cpp b/libbacktrace/Backtrace.cpp index 97f0ef445..9ead452cf 100644 --- a/libbacktrace/Backtrace.cpp +++ b/libbacktrace/Backtrace.cpp @@ -30,6 +30,7 @@ #include #include "BacktraceLog.h" +#include "BacktraceOffline.h" #include "thread_utils.h" #include "UnwindCurrent.h" #include "UnwindPtrace.h" @@ -140,3 +141,8 @@ Backtrace* Backtrace::Create(pid_t pid, pid_t tid, BacktraceMap* map) { return new UnwindPtrace(pid, tid, map); } } + +Backtrace* Backtrace::CreateOffline(pid_t pid, pid_t tid, BacktraceMap* map, + const backtrace_stackinfo_t& stack, bool cache_file) { + return new BacktraceOffline(pid, tid, map, stack, cache_file); +} diff --git a/libbacktrace/BacktraceMap.cpp b/libbacktrace/BacktraceMap.cpp index ca47f6781..eac20fe29 100644 --- a/libbacktrace/BacktraceMap.cpp +++ b/libbacktrace/BacktraceMap.cpp @@ -144,3 +144,13 @@ BacktraceMap* BacktraceMap::Create(pid_t pid, bool /*uncached*/) { return map; } #endif + +BacktraceMap* BacktraceMap::Create(pid_t pid, const std::vector& maps) { + BacktraceMap* backtrace_map = new BacktraceMap(pid); + backtrace_map->maps_.insert(backtrace_map->maps_.begin(), maps.begin(), maps.end()); + std::sort(backtrace_map->maps_.begin(), backtrace_map->maps_.end(), + [](const backtrace_map_t& map1, const backtrace_map_t& map2) { + return map1.start < map2.start; + }); + return backtrace_map; +} diff --git a/libbacktrace/BacktraceOffline.cpp b/libbacktrace/BacktraceOffline.cpp new file mode 100644 index 000000000..27dfb8313 --- /dev/null +++ b/libbacktrace/BacktraceOffline.cpp @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BacktraceOffline.h" + +extern "C" { +#define UNW_REMOTE_ONLY +#include +} + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + +#include +#include +#include +#include + +#pragma clang diagnostic pop + +#include "BacktraceLog.h" + +void Space::Clear() { + start = 0; + end = 0; + data = nullptr; +} + +size_t Space::Read(uint64_t addr, uint8_t* buffer, size_t size) { + if (addr >= start && addr < end) { + size_t read_size = std::min(size, static_cast(end - addr)); + memcpy(buffer, data + (addr - start), read_size); + return read_size; + } + return 0; +} + +static int FindProcInfo(unw_addr_space_t addr_space, unw_word_t ip, unw_proc_info* proc_info, + int need_unwind_info, void* arg) { + BacktraceOffline* backtrace = reinterpret_cast(arg); + bool result = backtrace->FindProcInfo(addr_space, ip, proc_info, need_unwind_info); + return result ? 0 : -UNW_EINVAL; +} + +static void PutUnwindInfo(unw_addr_space_t, unw_proc_info_t*, void*) { +} + +static int GetDynInfoListAddr(unw_addr_space_t, unw_word_t*, void*) { + return -UNW_ENOINFO; +} + +static int AccessMem(unw_addr_space_t, unw_word_t addr, unw_word_t* value, int write, void* arg) { + if (write == 1) { + return -UNW_EINVAL; + } + BacktraceOffline* backtrace = reinterpret_cast(arg); + *value = 0; + size_t read_size = backtrace->Read(addr, reinterpret_cast(value), sizeof(unw_word_t)); + // Strictly we should check if read_size matches sizeof(unw_word_t), but it is possible in + // .eh_frame_hdr that the section can end at a position not aligned in sizeof(unw_word_t), and + // we should permit the read at the end of the section. + return (read_size > 0u ? 0 : -UNW_EINVAL); +} + +static int AccessReg(unw_addr_space_t, unw_regnum_t unwind_reg, unw_word_t* value, int write, + void* arg) { + if (write == 1) { + return -UNW_EINVAL; + } + BacktraceOffline* backtrace = reinterpret_cast(arg); + uint64_t reg_value; + bool result = backtrace->ReadReg(unwind_reg, ®_value); + if (result) { + *value = static_cast(reg_value); + } + return result ? 0 : -UNW_EINVAL; +} + +static int AccessFpReg(unw_addr_space_t, unw_regnum_t, unw_fpreg_t*, int, void*) { + return -UNW_EINVAL; +} + +static int Resume(unw_addr_space_t, unw_cursor_t*, void*) { + return -UNW_EINVAL; +} + +static int GetProcName(unw_addr_space_t, unw_word_t, char*, size_t, unw_word_t*, void*) { + return -UNW_EINVAL; +} + +static unw_accessors_t accessors = { + .find_proc_info = FindProcInfo, + .put_unwind_info = PutUnwindInfo, + .get_dyn_info_list_addr = GetDynInfoListAddr, + .access_mem = AccessMem, + .access_reg = AccessReg, + .access_fpreg = AccessFpReg, + .resume = Resume, + .get_proc_name = GetProcName, +}; + +bool BacktraceOffline::Unwind(size_t num_ignore_frames, ucontext_t* context) { + if (context == nullptr) { + BACK_LOGW("The context is needed for offline backtracing."); + return false; + } + context_ = context; + + unw_addr_space_t addr_space = unw_create_addr_space(&accessors, 0); + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, addr_space, this); + if (ret != 0) { + BACK_LOGW("unw_init_remote failed %d", ret); + unw_destroy_addr_space(addr_space); + return false; + } + 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_[num_frames]; + frame->num = num_frames; + frame->pc = static_cast(pc); + frame->sp = static_cast(sp); + frame->stack_size = 0; + + if (num_frames > 0) { + backtrace_frame_data_t* prev = &frames_[num_frames - 1]; + prev->stack_size = frame->sp - prev->sp; + } + frame->func_name = GetFunctionName(frame->pc, &frame->func_offset); + FillInMap(frame->pc, &frame->map); + num_frames++; + } else { + num_ignore_frames--; + } + ret = unw_step(&cursor); + } while (ret > 0 && num_frames < MAX_BACKTRACE_FRAMES); + + unw_destroy_addr_space(addr_space); + context_ = nullptr; + return true; +} + +bool BacktraceOffline::ReadWord(uintptr_t ptr, word_t* out_value) { + size_t bytes_read = Read(ptr, reinterpret_cast(out_value), sizeof(word_t)); + return bytes_read == sizeof(word_t); +} + +size_t BacktraceOffline::Read(uintptr_t addr, uint8_t* buffer, size_t bytes) { + // Normally, libunwind needs stack information and call frame information to do remote unwinding. + // If call frame information is stored in .debug_frame, libunwind can read it from file + // by itself. If call frame information is stored in .eh_frame, we need to provide data in + // .eh_frame/.eh_frame_hdr sections. + // The order of readings below doesn't matter, as the spaces don't overlap with each other. + size_t read_size = eh_frame_hdr_space_.Read(addr, buffer, bytes); + if (read_size != 0) { + return read_size; + } + read_size = eh_frame_space_.Read(addr, buffer, bytes); + if (read_size != 0) { + return read_size; + } + read_size = stack_space_.Read(addr, buffer, bytes); + return read_size; +} + +static bool FileOffsetToVaddr( + const std::vector& program_headers, + uint64_t file_offset, uint64_t* vaddr) { + for (auto& header : program_headers) { + if (file_offset >= header.file_offset && file_offset < header.file_offset + header.file_size) { + // TODO: Consider load_bias? + *vaddr = file_offset - header.file_offset + header.vaddr; + return true; + } + } + return false; +} + +bool BacktraceOffline::FindProcInfo(unw_addr_space_t addr_space, uint64_t ip, + unw_proc_info_t* proc_info, int need_unwind_info) { + backtrace_map_t map; + FillInMap(ip, &map); + if (!BacktraceMap::IsValid(map)) { + return false; + } + const std::string& filename = map.name; + DebugFrameInfo* debug_frame = GetDebugFrameInFile(filename); + if (debug_frame == nullptr) { + return false; + } + if (debug_frame->is_eh_frame) { + uint64_t ip_offset = ip - map.start + map.offset; + uint64_t ip_vaddr; // vaddr in the elf file. + bool result = FileOffsetToVaddr(debug_frame->eh_frame.program_headers, ip_offset, &ip_vaddr); + if (!result) { + return false; + } + // Calculate the addresses where .eh_frame_hdr and .eh_frame stay when the process was running. + eh_frame_hdr_space_.start = (ip - ip_vaddr) + debug_frame->eh_frame.eh_frame_hdr_vaddr; + eh_frame_hdr_space_.end = + eh_frame_hdr_space_.start + debug_frame->eh_frame.eh_frame_hdr_data.size(); + eh_frame_hdr_space_.data = debug_frame->eh_frame.eh_frame_hdr_data.data(); + + eh_frame_space_.start = (ip - ip_vaddr) + debug_frame->eh_frame.eh_frame_vaddr; + eh_frame_space_.end = eh_frame_space_.start + debug_frame->eh_frame.eh_frame_data.size(); + eh_frame_space_.data = debug_frame->eh_frame.eh_frame_data.data(); + + unw_dyn_info di; + memset(&di, '\0', sizeof(di)); + di.start_ip = map.start; + di.end_ip = map.end; + di.format = UNW_INFO_FORMAT_REMOTE_TABLE; + di.u.rti.name_ptr = 0; + di.u.rti.segbase = eh_frame_hdr_space_.start; + di.u.rti.table_data = + eh_frame_hdr_space_.start + debug_frame->eh_frame.fde_table_offset_in_eh_frame_hdr; + di.u.rti.table_len = (eh_frame_hdr_space_.end - di.u.rti.table_data) / sizeof(unw_word_t); + int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this); + return ret == 0; + } + + eh_frame_hdr_space_.Clear(); + eh_frame_space_.Clear(); + unw_dyn_info_t di; + unw_word_t segbase = map.start - map.offset; + int found = dwarf_find_debug_frame(0, &di, ip, segbase, filename.c_str(), map.start, map.end); + if (found == 1) { + int ret = dwarf_search_unwind_table(addr_space, ip, &di, proc_info, need_unwind_info, this); + return ret == 0; + } + return false; +} + +bool BacktraceOffline::ReadReg(size_t reg, uint64_t* value) { + bool result = true; +#if defined(__arm__) + switch (reg) { + case UNW_ARM_R0: + *value = context_->uc_mcontext.arm_r0; + break; + case UNW_ARM_R1: + *value = context_->uc_mcontext.arm_r1; + break; + case UNW_ARM_R2: + *value = context_->uc_mcontext.arm_r2; + break; + case UNW_ARM_R3: + *value = context_->uc_mcontext.arm_r3; + break; + case UNW_ARM_R4: + *value = context_->uc_mcontext.arm_r4; + break; + case UNW_ARM_R5: + *value = context_->uc_mcontext.arm_r5; + break; + case UNW_ARM_R6: + *value = context_->uc_mcontext.arm_r6; + break; + case UNW_ARM_R7: + *value = context_->uc_mcontext.arm_r7; + break; + case UNW_ARM_R8: + *value = context_->uc_mcontext.arm_r8; + break; + case UNW_ARM_R9: + *value = context_->uc_mcontext.arm_r9; + break; + case UNW_ARM_R10: + *value = context_->uc_mcontext.arm_r10; + break; + case UNW_ARM_R11: + *value = context_->uc_mcontext.arm_fp; + break; + case UNW_ARM_R12: + *value = context_->uc_mcontext.arm_ip; + break; + case UNW_ARM_R13: + *value = context_->uc_mcontext.arm_sp; + break; + case UNW_ARM_R14: + *value = context_->uc_mcontext.arm_lr; + break; + case UNW_ARM_R15: + *value = context_->uc_mcontext.arm_pc; + break; + default: + result = false; + } +#elif defined(__aarch64__) + if (reg <= UNW_AARCH64_PC) { + *value = context_->uc_mcontext.regs[reg]; + } else { + result = false; + } +#elif defined(__x86_64__) + switch (reg) { + case UNW_X86_64_R8: + *value = context_->uc_mcontext.gregs[REG_R8]; + break; + case UNW_X86_64_R9: + *value = context_->uc_mcontext.gregs[REG_R9]; + break; + case UNW_X86_64_R10: + *value = context_->uc_mcontext.gregs[REG_R10]; + break; + case UNW_X86_64_R11: + *value = context_->uc_mcontext.gregs[REG_R11]; + break; + case UNW_X86_64_R12: + *value = context_->uc_mcontext.gregs[REG_R12]; + break; + case UNW_X86_64_R13: + *value = context_->uc_mcontext.gregs[REG_R13]; + break; + case UNW_X86_64_R14: + *value = context_->uc_mcontext.gregs[REG_R14]; + break; + case UNW_X86_64_R15: + *value = context_->uc_mcontext.gregs[REG_R15]; + break; + case UNW_X86_64_RDI: + *value = context_->uc_mcontext.gregs[REG_RDI]; + break; + case UNW_X86_64_RSI: + *value = context_->uc_mcontext.gregs[REG_RSI]; + break; + case UNW_X86_64_RBP: + *value = context_->uc_mcontext.gregs[REG_RBP]; + break; + case UNW_X86_64_RBX: + *value = context_->uc_mcontext.gregs[REG_RBX]; + break; + case UNW_X86_64_RDX: + *value = context_->uc_mcontext.gregs[REG_RDX]; + break; + case UNW_X86_64_RAX: + *value = context_->uc_mcontext.gregs[REG_RAX]; + break; + case UNW_X86_64_RCX: + *value = context_->uc_mcontext.gregs[REG_RCX]; + break; + case UNW_X86_64_RSP: + *value = context_->uc_mcontext.gregs[REG_RSP]; + break; + case UNW_X86_64_RIP: + *value = context_->uc_mcontext.gregs[REG_RIP]; + break; + default: + result = false; + } +#elif defined(__i386__) + switch (reg) { + case UNW_X86_GS: + *value = context_->uc_mcontext.gregs[REG_GS]; + break; + case UNW_X86_FS: + *value = context_->uc_mcontext.gregs[REG_FS]; + break; + case UNW_X86_ES: + *value = context_->uc_mcontext.gregs[REG_ES]; + break; + case UNW_X86_DS: + *value = context_->uc_mcontext.gregs[REG_DS]; + break; + case UNW_X86_EAX: + *value = context_->uc_mcontext.gregs[REG_EAX]; + break; + case UNW_X86_EBX: + *value = context_->uc_mcontext.gregs[REG_EBX]; + break; + case UNW_X86_ECX: + *value = context_->uc_mcontext.gregs[REG_ECX]; + break; + case UNW_X86_EDX: + *value = context_->uc_mcontext.gregs[REG_EDX]; + break; + case UNW_X86_ESI: + *value = context_->uc_mcontext.gregs[REG_ESI]; + break; + case UNW_X86_EDI: + *value = context_->uc_mcontext.gregs[REG_EDI]; + break; + case UNW_X86_EBP: + *value = context_->uc_mcontext.gregs[REG_EBP]; + break; + case UNW_X86_EIP: + *value = context_->uc_mcontext.gregs[REG_EIP]; + break; + case UNW_X86_ESP: + *value = context_->uc_mcontext.gregs[REG_ESP]; + break; + case UNW_X86_TRAPNO: + *value = context_->uc_mcontext.gregs[REG_TRAPNO]; + break; + case UNW_X86_CS: + *value = context_->uc_mcontext.gregs[REG_CS]; + break; + case UNW_X86_EFLAGS: + *value = context_->uc_mcontext.gregs[REG_EFL]; + break; + case UNW_X86_SS: + *value = context_->uc_mcontext.gregs[REG_SS]; + break; + default: + result = false; + } +#endif + return result; +} + +std::string BacktraceOffline::GetFunctionNameRaw(uintptr_t, uintptr_t* offset) { + // We don't have enough information to support this. And it is expensive. + *offset = 0; + return ""; +} + +std::unordered_map> BacktraceOffline::debug_frames_; +std::unordered_set BacktraceOffline::debug_frame_missing_files_; + +static DebugFrameInfo* ReadDebugFrameFromFile(const std::string& filename); + +DebugFrameInfo* BacktraceOffline::GetDebugFrameInFile(const std::string& filename) { + if (cache_file_) { + auto it = debug_frames_.find(filename); + if (it != debug_frames_.end()) { + return it->second.get(); + } + if (debug_frame_missing_files_.find(filename) != debug_frame_missing_files_.end()) { + return nullptr; + } + } + DebugFrameInfo* debug_frame = ReadDebugFrameFromFile(filename); + if (cache_file_) { + if (debug_frame != nullptr) { + debug_frames_.emplace(filename, std::unique_ptr(debug_frame)); + } else { + debug_frame_missing_files_.insert(filename); + } + } else { + if (last_debug_frame_ != nullptr) { + delete last_debug_frame_; + } + last_debug_frame_ = debug_frame; + } + return debug_frame; +} + +static bool OmitEncodedValue(uint8_t encode, const uint8_t*& p) { + if (encode == DW_EH_PE_omit) { + return 0; + } + uint8_t format = encode & 0x0f; + switch (format) { + case DW_EH_PE_ptr: + p += sizeof(unw_word_t); + break; + case DW_EH_PE_uleb128: + case DW_EH_PE_sleb128: + while ((*p & 0x80) != 0) { + ++p; + } + ++p; + break; + case DW_EH_PE_udata2: + case DW_EH_PE_sdata2: + p += 2; + break; + case DW_EH_PE_udata4: + case DW_EH_PE_sdata4: + p += 4; + break; + case DW_EH_PE_udata8: + case DW_EH_PE_sdata8: + p += 8; + break; + default: + return false; + } + return true; +} + +static bool GetFdeTableOffsetInEhFrameHdr(const std::vector& data, + uint64_t* table_offset_in_eh_frame_hdr) { + const uint8_t* p = data.data(); + const uint8_t* end = p + data.size(); + if (p + 4 > end) { + return false; + } + uint8_t version = *p++; + if (version != 1) { + return false; + } + uint8_t eh_frame_ptr_encode = *p++; + uint8_t fde_count_encode = *p++; + uint8_t fde_table_encode = *p++; + + if (fde_table_encode != (DW_EH_PE_datarel | DW_EH_PE_sdata4)) { + return false; + } + + if (!OmitEncodedValue(eh_frame_ptr_encode, p) || !OmitEncodedValue(fde_count_encode, p)) { + return false; + } + if (p >= end) { + return false; + } + *table_offset_in_eh_frame_hdr = p - data.data(); + return true; +} + +using ProgramHeader = DebugFrameInfo::EhFrame::ProgramHeader; + +template +DebugFrameInfo* ReadDebugFrameFromELFFile(const llvm::object::ELFFile* elf) { + bool has_eh_frame_hdr = false; + uint64_t eh_frame_hdr_vaddr = 0; + std::vector eh_frame_hdr_data; + bool has_eh_frame = false; + uint64_t eh_frame_vaddr = 0; + std::vector eh_frame_data; + + for (auto it = elf->begin_sections(); it != elf->end_sections(); ++it) { + llvm::ErrorOr name = elf->getSectionName(&*it); + if (name) { + if (name.get() == ".debug_frame") { + DebugFrameInfo* debug_frame = new DebugFrameInfo; + debug_frame->is_eh_frame = false; + return debug_frame; + } + if (name.get() == ".eh_frame_hdr") { + has_eh_frame_hdr = true; + eh_frame_hdr_vaddr = it->sh_addr; + llvm::ErrorOr> data = elf->getSectionContents(&*it); + if (data) { + eh_frame_hdr_data.insert(eh_frame_hdr_data.begin(), data->data(), + data->data() + data->size()); + } else { + return nullptr; + } + } else if (name.get() == ".eh_frame") { + has_eh_frame = true; + eh_frame_vaddr = it->sh_addr; + llvm::ErrorOr> data = elf->getSectionContents(&*it); + if (data) { + eh_frame_data.insert(eh_frame_data.begin(), data->data(), data->data() + data->size()); + } else { + return nullptr; + } + } + } + } + if (!(has_eh_frame_hdr && has_eh_frame)) { + return nullptr; + } + uint64_t fde_table_offset; + if (!GetFdeTableOffsetInEhFrameHdr(eh_frame_hdr_data, &fde_table_offset)) { + return nullptr; + } + + std::vector program_headers; + for (auto it = elf->begin_program_headers(); it != elf->end_program_headers(); ++it) { + ProgramHeader header; + header.vaddr = it->p_vaddr; + header.file_offset = it->p_offset; + header.file_size = it->p_filesz; + program_headers.push_back(header); + } + DebugFrameInfo* debug_frame = new DebugFrameInfo; + debug_frame->is_eh_frame = true; + debug_frame->eh_frame.eh_frame_hdr_vaddr = eh_frame_hdr_vaddr; + debug_frame->eh_frame.eh_frame_vaddr = eh_frame_vaddr; + debug_frame->eh_frame.fde_table_offset_in_eh_frame_hdr = fde_table_offset; + debug_frame->eh_frame.eh_frame_hdr_data = std::move(eh_frame_hdr_data); + debug_frame->eh_frame.eh_frame_data = std::move(eh_frame_data); + debug_frame->eh_frame.program_headers = program_headers; + return debug_frame; +} + +static DebugFrameInfo* ReadDebugFrameFromFile(const std::string& filename) { + auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename)); + if (owning_binary.getError()) { + return nullptr; + } + llvm::object::Binary* binary = owning_binary.get().getBinary(); + auto obj = llvm::dyn_cast(binary); + if (obj == nullptr) { + return nullptr; + } + if (auto elf = llvm::dyn_cast(obj)) { + return ReadDebugFrameFromELFFile(elf->getELFFile()); + } + if (auto elf = llvm::dyn_cast(obj)) { + return ReadDebugFrameFromELFFile(elf->getELFFile()); + } + return nullptr; +} diff --git a/libbacktrace/BacktraceOffline.h b/libbacktrace/BacktraceOffline.h new file mode 100644 index 000000000..42f826da9 --- /dev/null +++ b/libbacktrace/BacktraceOffline.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBBACKTRACE_UNWIND_OFFLINE_H +#define _LIBBACKTRACE_UNWIND_OFFLINE_H + +#include +#include +#include +#include + +#include +#include + +#include + +struct Space { + uint64_t start; + uint64_t end; + const uint8_t* data; + + Space() { + Clear(); + } + + void Clear(); + size_t Read(uint64_t addr, uint8_t* buffer, size_t size); +}; + +struct DebugFrameInfo { + bool is_eh_frame; + struct EhFrame { + uint64_t eh_frame_hdr_vaddr; + uint64_t eh_frame_vaddr; + uint64_t fde_table_offset_in_eh_frame_hdr; + std::vector eh_frame_hdr_data; + std::vector eh_frame_data; + struct ProgramHeader { + uint64_t vaddr; + uint64_t file_offset; + uint64_t file_size; + }; + std::vector program_headers; + } eh_frame; +}; + +class BacktraceOffline : public Backtrace { + public: + BacktraceOffline(pid_t pid, pid_t tid, BacktraceMap* map, const backtrace_stackinfo_t& stack, + bool cache_file) + : Backtrace(pid, tid, map), + cache_file_(cache_file), + context_(nullptr), + last_debug_frame_(nullptr) { + stack_space_.start = stack.start; + stack_space_.end = stack.end; + stack_space_.data = stack.data; + } + + virtual ~BacktraceOffline() { + if (last_debug_frame_ != nullptr) { + delete last_debug_frame_; + } + } + + bool Unwind(size_t num_ignore_frames, ucontext_t* context) override; + + bool ReadWord(uintptr_t ptr, word_t* out_value) override; + + size_t Read(uintptr_t addr, uint8_t* buffer, size_t bytes) override; + + bool FindProcInfo(unw_addr_space_t addr_space, uint64_t ip, unw_proc_info_t* proc_info, + int need_unwind_info); + + bool ReadReg(size_t reg_index, uint64_t* value); + + protected: + std::string GetFunctionNameRaw(uintptr_t pc, uintptr_t* offset) override; + DebugFrameInfo* GetDebugFrameInFile(const std::string& filename); + + static std::unordered_map> debug_frames_; + static std::unordered_set debug_frame_missing_files_; + + bool cache_file_; + ucontext_t* context_; + Space eh_frame_hdr_space_; + Space eh_frame_space_; + Space stack_space_; + DebugFrameInfo* last_debug_frame_; +}; + +#endif // _LIBBACKTRACE_BACKTRACE_OFFLINE_H diff --git a/libbacktrace/backtrace_offline_test.cpp b/libbacktrace/backtrace_offline_test.cpp new file mode 100644 index 000000000..88a3533a2 --- /dev/null +++ b/libbacktrace/backtrace_offline_test.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +extern "C" { +// Prototypes for functions in the test library. +int test_level_one(int, int, int, int, void (*)(void*), void*); +int test_level_two(int, int, int, int, void (*)(void*), void*); +int test_level_three(int, int, int, int, void (*)(void*), void*); +int test_level_four(int, int, int, int, void (*)(void*), void*); +int test_recursive_call(int, void (*)(void*), void*); +} + +static volatile bool g_exit_flag = false; + +static void GetContextAndExit(void* arg) { + unw_context_t* unw_context = reinterpret_cast(arg); + unw_getcontext(unw_context); + // Don't touch the stack anymore. + while (!g_exit_flag) { + } +} + +struct OfflineThreadArg { + unw_context_t unw_context; + pid_t tid; + std::function function; +}; + +static void* OfflineThreadFunc(void* arg) { + OfflineThreadArg* fn_arg = reinterpret_cast(arg); + fn_arg->tid = gettid(); + fn_arg->function(GetContextAndExit, &fn_arg->unw_context); + return nullptr; +} + +static ucontext_t GetUContextFromUnwContext(const unw_context_t& unw_context) { + ucontext_t ucontext; + memset(&ucontext, 0, sizeof(ucontext)); +#if defined(__arm__) + ucontext.uc_mcontext.arm_r0 = unw_context.regs[0]; + ucontext.uc_mcontext.arm_r1 = unw_context.regs[1]; + ucontext.uc_mcontext.arm_r2 = unw_context.regs[2]; + ucontext.uc_mcontext.arm_r3 = unw_context.regs[3]; + ucontext.uc_mcontext.arm_r4 = unw_context.regs[4]; + ucontext.uc_mcontext.arm_r5 = unw_context.regs[5]; + ucontext.uc_mcontext.arm_r6 = unw_context.regs[6]; + ucontext.uc_mcontext.arm_r7 = unw_context.regs[7]; + ucontext.uc_mcontext.arm_r8 = unw_context.regs[8]; + ucontext.uc_mcontext.arm_r9 = unw_context.regs[9]; + ucontext.uc_mcontext.arm_r10 = unw_context.regs[10]; + ucontext.uc_mcontext.arm_fp = unw_context.regs[11]; + ucontext.uc_mcontext.arm_ip = unw_context.regs[12]; + ucontext.uc_mcontext.arm_sp = unw_context.regs[13]; + ucontext.uc_mcontext.arm_lr = unw_context.regs[14]; + ucontext.uc_mcontext.arm_pc = unw_context.regs[15]; +#else + ucontext.uc_mcontext = unw_context.uc_mcontext; +#endif + return ucontext; +} + +static void OfflineBacktraceFunctionCall(std::function function, + std::vector* pc_values) { + // Create a thread to generate the needed stack and registers information. + g_exit_flag = false; + const size_t stack_size = 1024 * 1024; + void* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, stack); + uintptr_t stack_addr = reinterpret_cast(stack); + pthread_attr_t attr; + ASSERT_EQ(0, pthread_attr_init(&attr)); + ASSERT_EQ(0, pthread_attr_setstack(&attr, reinterpret_cast(stack), stack_size)); + pthread_t thread; + OfflineThreadArg arg; + arg.function = function; + ASSERT_EQ(0, pthread_create(&thread, &attr, OfflineThreadFunc, &arg)); + // Wait for the offline thread to generate the stack and unw_context information. + sleep(1); + // Copy the stack information. + std::vector stack_data(reinterpret_cast(stack), + reinterpret_cast(stack) + stack_size); + g_exit_flag = true; + ASSERT_EQ(0, pthread_join(thread, nullptr)); + ASSERT_EQ(0, munmap(stack, stack_size)); + + // Do offline backtrace. + std::unique_ptr map(BacktraceMap::Create(getpid())); + ASSERT_TRUE(map != nullptr); + + backtrace_stackinfo_t stack_info; + stack_info.start = stack_addr; + stack_info.end = stack_addr + stack_size; + stack_info.data = stack_data.data(); + + std::unique_ptr backtrace( + Backtrace::CreateOffline(getpid(), arg.tid, map.get(), stack_info)); + ASSERT_TRUE(backtrace != nullptr); + + ucontext_t ucontext = GetUContextFromUnwContext(arg.unw_context); + ASSERT_TRUE(backtrace->Unwind(0, &ucontext)); + + // Collect pc values of the call stack frames. + for (size_t i = 0; i < backtrace->NumFrames(); ++i) { + pc_values->push_back(backtrace->GetFrame(i)->pc); + } +} + +// Return the name of the function which matches the address. Although we don't know the +// exact end of each function, it is accurate enough for the tests. +static std::string FunctionNameForAddress(uintptr_t addr) { + struct FunctionSymbol { + std::string name; + uintptr_t start; + uintptr_t end; + }; + + static std::vector symbols; + if (symbols.empty()) { + symbols = std::vector{ + {"unknown_start", 0, 0}, + {"test_level_one", reinterpret_cast(&test_level_one), 0}, + {"test_level_two", reinterpret_cast(&test_level_two), 0}, + {"test_level_three", reinterpret_cast(&test_level_three), 0}, + {"test_level_four", reinterpret_cast(&test_level_four), 0}, + {"test_recursive_call", reinterpret_cast(&test_recursive_call), 0}, + {"GetContextAndExit", reinterpret_cast(&GetContextAndExit), 0}, + {"unknown_end", static_cast(-1), static_cast(-1)}, + }; + std::sort( + symbols.begin(), symbols.end(), + [](const FunctionSymbol& s1, const FunctionSymbol& s2) { return s1.start < s2.start; }); + for (size_t i = 0; i + 1 < symbols.size(); ++i) { + symbols[i].end = symbols[i + 1].start; + } + } + for (auto& symbol : symbols) { + if (addr >= symbol.start && addr < symbol.end) { + return symbol.name; + } + } + return ""; +} + +TEST(libbacktrace, offline) { + std::function function = + std::bind(test_level_one, 1, 2, 3, 4, std::placeholders::_1, std::placeholders::_2); + std::vector pc_values; + OfflineBacktraceFunctionCall(function, &pc_values); + ASSERT_FALSE(pc_values.empty()); + ASSERT_LE(pc_values.size(), static_cast(MAX_BACKTRACE_FRAMES)); + + size_t test_one_index = 0; + for (size_t i = 0; i < pc_values.size(); ++i) { + if (FunctionNameForAddress(pc_values[i]) == "test_level_one") { + test_one_index = i; + break; + } + } + + ASSERT_GE(test_one_index, 3u); + ASSERT_EQ("test_level_one", FunctionNameForAddress(pc_values[test_one_index])); + ASSERT_EQ("test_level_two", FunctionNameForAddress(pc_values[test_one_index - 1])); + ASSERT_EQ("test_level_three", FunctionNameForAddress(pc_values[test_one_index - 2])); + ASSERT_EQ("test_level_four", FunctionNameForAddress(pc_values[test_one_index - 3])); +} + +TEST(libbacktrace, offline_max_trace) { + std::function function = std::bind( + test_recursive_call, MAX_BACKTRACE_FRAMES + 10, std::placeholders::_1, std::placeholders::_2); + std::vector pc_values; + OfflineBacktraceFunctionCall(function, &pc_values); + ASSERT_FALSE(pc_values.empty()); + ASSERT_EQ(static_cast(MAX_BACKTRACE_FRAMES), pc_values.size()); + ASSERT_EQ("test_recursive_call", FunctionNameForAddress(pc_values.back())); +}