| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/tracing/common/stack_unwinder_android.h" |
| |
| #include <linux/futex.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <syscall.h> |
| #include <unistd.h> |
| #include "link.h" |
| |
| #include <algorithm> |
| |
| #include "base/android/jni_generator/jni_generator_helper.h" |
| #include "base/debug/proc_maps_linux.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/trace_event/cfi_backtrace_android.h" |
| #include "libunwind.h" |
| |
| using base::trace_event::CFIBacktraceAndroid; |
| using base::debug::MappedMemoryRegion; |
| |
| namespace { |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class SamplingProfilerUnwindResult { |
| kFutexSignalFailed = 0, |
| kStackCopyFailed = 1, |
| kUnwindInitFailed = 2, |
| kHandlerUnwindFailed = 3, |
| kFirstFrameUnmapped = 4, |
| kMaxValue = kFirstFrameUnmapped, |
| }; |
| |
| void RecordUnwindResult(SamplingProfilerUnwindResult result) { |
| UMA_HISTOGRAM_ENUMERATION("BackgroundTracing.SamplingProfilerUnwindResult", |
| result); |
| } |
| |
| // Waitable event implementation with futex and without DCHECK(s), since signal |
| // handlers cannot allocate memory or use pthread api. |
| class AsyncSafeWaitableEvent { |
| public: |
| AsyncSafeWaitableEvent() { base::subtle::Release_Store(&futex_, 0); } |
| ~AsyncSafeWaitableEvent() {} |
| |
| bool Wait() { |
| // futex() can wake up spuriously if this memory address was previously used |
| // for a pthread mutex. So, also check the condition. |
| while (true) { |
| int res = syscall(SYS_futex, &futex_, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, |
| nullptr, nullptr, 0); |
| if (base::subtle::Acquire_Load(&futex_) != 0) |
| return true; |
| if (res != 0) |
| return false; |
| } |
| } |
| |
| void Signal() { |
| base::subtle::Release_Store(&futex_, 1); |
| syscall(SYS_futex, &futex_, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, nullptr, |
| nullptr, 0); |
| } |
| |
| private: |
| int futex_; |
| }; |
| |
| // Scoped signal event that calls Signal on the AsyncSafeWaitableEvent at |
| // destructor. |
| class ScopedEventSignaller { |
| public: |
| ScopedEventSignaller(AsyncSafeWaitableEvent* event) : event_(event) {} |
| ~ScopedEventSignaller() { event_->Signal(); } |
| |
| private: |
| AsyncSafeWaitableEvent* event_; |
| }; |
| |
| // Helper class to unwind stack. See Unwind() method for details. |
| class UnwindHelper { |
| public: |
| UnwindHelper(bool use_libunwind, |
| const tracing::StackUnwinderAndroid* unwinder, |
| uintptr_t original_sp, |
| size_t stack_size, |
| base::NativeStackSampler::StackBuffer* stack_buffer, |
| const void** out_trace, |
| size_t max_depth) |
| : use_libunwind_(use_libunwind), |
| cfi_unwinder_(CFIBacktraceAndroid::GetInitializedInstance()), |
| unwinder_(unwinder), |
| initial_sp_(stack_buffer |
| ? reinterpret_cast<uintptr_t>(stack_buffer->buffer()) |
| : original_sp), |
| stack_segment_base_(initial_sp_ + stack_size), |
| stack_size_(stack_size), |
| max_depth_(max_depth), |
| cursor_({}), |
| current_sp_(0), |
| current_ip_(0), |
| out_trace_(out_trace), |
| depth_(0) {} |
| |
| // Unwinds from given |cursor| readable by libunwind, and returns |
| // the number of frames added to the output. This function can unwind through |
| // android framework and then chrome functions. It cannot handle the cases |
| // when the chrome functions are called by android framework again, since we |
| // cannot create the right context for libunwind from chrome functions. |
| // TODO(ssid): This function should support unwinding from chrome to android |
| // libraries also. |
| size_t Unwind(uintptr_t original_sp, |
| unw_context_t* context, |
| const ucontext_t& signal_context, |
| base::NativeStackSampler::StackBuffer* stack_buffer) { |
| const uintptr_t new_stack_top = initial_sp_; |
| // Set the frame to the return frame from signal handler. |
| current_ip_ = signal_context.uc_mcontext.arm_pc; |
| current_sp_ = |
| signal_context.uc_mcontext.arm_sp - original_sp + new_stack_top; |
| |
| // Do not use libunwind if we stopped at chrome frame. |
| if (CFIBacktraceAndroid::is_chrome_address(current_ip_)) { |
| return cfi_unwinder_->Unwind(current_ip_, current_sp_, |
| signal_context.uc_mcontext.arm_lr, |
| out_trace_, max_depth_); |
| } |
| |
| if (stack_buffer) |
| RewritePointersAndGetMarkers(stack_buffer, original_sp, stack_size_); |
| |
| if (!GetUnwindCursorForStack(original_sp, signal_context, context)) { |
| RecordUnwindResult(SamplingProfilerUnwindResult::kUnwindInitFailed); |
| return 0; |
| } |
| |
| bool try_stack_search = true; |
| |
| if (use_libunwind_) |
| UnwindWithLibunwind(); |
| |
| if (CFIBacktraceAndroid::is_chrome_address(current_ip_)) { |
| // Continue unwinding CFI unwinder if we found stack frame from chrome |
| // library. |
| uintptr_t lr = 0; |
| unw_get_reg(&cursor_, UNW_ARM_LR, &lr); |
| depth_ += cfi_unwinder_->Unwind(current_ip_, current_sp_, lr, |
| out_trace_ + depth_, max_depth_ - depth_); |
| try_stack_search = false; |
| } |
| |
| if (depth_ >= max_depth_) |
| return depth_; |
| |
| // Try unwinding the rest of frames from Jni markers on stack if present. |
| // This is to skip trying to unwind art frames which do not have unwind |
| // information. |
| for (const auto* const marker : jni_markers_) { |
| // Skip if we already walked past this marker. |
| if (current_sp_ > marker->sp) |
| continue; |
| depth_ += cfi_unwinder_->Unwind(marker->pc, marker->sp, /*lr=*/0, |
| out_trace_ + depth_, max_depth_ - depth_); |
| try_stack_search = false; |
| if (depth_ >= max_depth_) |
| break; |
| } |
| |
| // We tried all possible ways to unwind and failed. So, scan the stack to |
| // find all chrome addresses and add them to stack trace. This would give us |
| // a lot of false frames on the trace. The idea is to try to sanitize the |
| // trace on server side or try unwinding after each search. The current |
| // version just sends back all PCs until we figure out what is the best way |
| // to sanitize the stack trace. |
| if (try_stack_search) |
| ScanStackAndFillFrames(); |
| |
| if (depth_ == 0) |
| RecordUnwindResult(SamplingProfilerUnwindResult::kFirstFrameUnmapped); |
| return depth_; |
| } |
| |
| private: |
| // Unwinds from the given context using libunwind. |
| void UnwindWithLibunwind() { |
| uintptr_t previous_sp = 0; |
| do { |
| unw_get_reg(&cursor_, UNW_REG_IP, ¤t_ip_); |
| unw_get_reg(&cursor_, UNW_REG_SP, ¤t_sp_); |
| DCHECK_GE(current_sp_, initial_sp_); |
| if (stack_segment_base_ > 0) |
| DCHECK_LT(current_sp_, stack_segment_base_); |
| |
| // If SP and IP did not change from previous frame, then unwinding failed. |
| if (previous_sp == current_sp_ && |
| current_ip_ == reinterpret_cast<uintptr_t>(out_trace_[depth_ - 1])) { |
| break; |
| } |
| previous_sp = current_sp_; |
| |
| // If address is in chrome library, then use CFI unwinder since chrome |
| // might not have EHABI unwind tables. |
| if (CFIBacktraceAndroid::is_chrome_address(current_ip_)) |
| break; |
| |
| // Break if pc is not from any mapped region. Something went wrong while |
| // unwinding. |
| if (!unwinder_->IsAddressMapped(current_ip_)) |
| break; |
| |
| // If it is chrome address, the cfi unwinder will include it. |
| out_trace_[depth_++] = reinterpret_cast<void*>(current_ip_); |
| } while (unw_step(&cursor_) && depth_ < max_depth_ - 1); |
| } |
| |
| void ScanStackAndFillFrames() { |
| // Search from beginning of stack, in case unwinding obtained bad offsets. |
| uintptr_t* stack = reinterpret_cast<uintptr_t*>(initial_sp_); |
| // Add a nullptr to differentiate addresses found by unwinding and scanning. |
| out_trace_[depth_++] = nullptr; |
| while (depth_ < max_depth_ && |
| reinterpret_cast<uintptr_t>(stack) < stack_segment_base_) { |
| if (CFIBacktraceAndroid::is_chrome_address( |
| reinterpret_cast<uintptr_t>(*stack))) { |
| out_trace_[depth_++] = reinterpret_cast<void*>(*stack); |
| } |
| ++stack; |
| } |
| } |
| |
| // Creates unwind cursor for the copied stack, which points to the function |
| // frame in which the sampled thread was stopped. We get information about |
| // this frame from signal context. Replaces registers in the context and |
| // cursor to point to the new stack's top function frame. |
| bool GetUnwindCursorForStack(uintptr_t original_sp, |
| const ucontext_t& signal_context, |
| unw_context_t* context) { |
| // Initialize an unwind cursor on copied stack. |
| if (unw_init_local(&cursor_, context) != 0) |
| return false; |
| |
| // If we never copied stack, then no need to replace all the registers. |
| if (original_sp == initial_sp_) |
| return true; |
| |
| // Reset the unwind cursor to previous function and continue with libunwind. |
| unw_set_reg(&cursor_, UNW_REG_SP, current_sp_); // 13 |
| unw_set_reg(&cursor_, UNW_ARM_R0, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r0, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R1, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r1, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R3, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r2, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R3, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r3, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R4, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r4, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R5, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r5, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R6, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r6, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R7, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r7, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R8, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r8, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R9, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r9, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R10, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_r10, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R11, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_fp, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_R12, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_ip, original_sp)); |
| unw_set_reg(&cursor_, UNW_ARM_LR, |
| RewritePointerIfInOriginalStack( |
| signal_context.uc_mcontext.arm_lr, original_sp)); |
| |
| // Setting the IP register might cause adjustments in SP register. So, this |
| // must be set after setting SP to the right value. |
| unw_set_reg(&cursor_, UNW_REG_IP, current_ip_); // 15 |
| |
| return true; |
| } |
| |
| uintptr_t RewritePointerIfInOriginalStack(uintptr_t ptr_address, |
| uintptr_t sp) { |
| const uintptr_t new_stack_top = initial_sp_; |
| if (ptr_address >= sp && ptr_address < sp + stack_size_) |
| return ptr_address - sp + new_stack_top; |
| return ptr_address; |
| } |
| |
| void RewritePointersAndGetMarkers( |
| base::NativeStackSampler::StackBuffer* stack_buffer, |
| uintptr_t original_sp, |
| size_t stack_size) { |
| jni_markers_.clear(); |
| uintptr_t* new_stack = reinterpret_cast<uintptr_t*>(stack_buffer->buffer()); |
| constexpr uint32_t marker_l = |
| jni_generator::kJniStackMarkerValue & 0xFFFFFFFF; |
| constexpr uint32_t marker_r = jni_generator::kJniStackMarkerValue >> 32; |
| const uintptr_t new_stack_top = |
| reinterpret_cast<uintptr_t>(stack_buffer->buffer()); |
| const size_t ptrs_to_rewrite = stack_size / sizeof(uintptr_t); |
| for (size_t i = 0; i < ptrs_to_rewrite; ++i) { |
| // Scanning needs to be fixed for 64 bit version. |
| DCHECK_EQ(4u, sizeof(uintptr_t)); |
| if (i < ptrs_to_rewrite - 1 && new_stack[i] == marker_l && |
| new_stack[i + 1] == marker_r) { |
| // Note: JniJavaCallContext::sp will be replaced with offset below. |
| const tracing::StackUnwinderAndroid::JniMarker* marker = |
| reinterpret_cast<const tracing::StackUnwinderAndroid::JniMarker*>( |
| new_stack + i); |
| DCHECK_EQ(jni_generator::kJniStackMarkerValue, marker->marker); |
| if (marker->sp >= original_sp && |
| marker->sp < original_sp + stack_size && |
| CFIBacktraceAndroid::is_chrome_address(marker->pc)) { |
| jni_markers_.push_back(marker); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| // Unwind can use address on the stack. So, replace them as well. See |
| // EHABI #7.5.4 table 3. |
| if (new_stack[i] >= original_sp && |
| new_stack[i] < original_sp + stack_size) { |
| new_stack[i] = new_stack[i] - original_sp + new_stack_top; |
| } |
| } |
| } |
| |
| // If false then only chrome unwinder and stack scanning are used to unwind. |
| const bool use_libunwind_; |
| CFIBacktraceAndroid* cfi_unwinder_; // not const because of cache |
| const tracing::StackUnwinderAndroid* unwinder_; |
| |
| // Set to the stack pointer of the copied stack in case of unwinding other |
| // thread. Otherwise stack pointer of the unwind method. |
| const uintptr_t initial_sp_; |
| // The end of stack segment, till which we scan for frames. |
| const uintptr_t stack_segment_base_; |
| // The size of the stack segment copied, |stack_segment_base_| - |
| // |initial_sp_|. |
| const size_t stack_size_; |
| // Maximum depth of frames to unwind. |
| const size_t max_depth_; |
| |
| // Current libunwind cursor. |
| unw_cursor_t cursor_; |
| |
| // The current value of stack pointer and instruction pointer while unwinding. |
| uintptr_t current_sp_; |
| uintptr_t current_ip_; |
| |
| // A list of JNI markers on stack found by scanning. |
| std::vector<const tracing::StackUnwinderAndroid::JniMarker*> jni_markers_; |
| |
| // Output stack trace and depth: |
| const void** out_trace_; |
| size_t depth_ = 0; |
| }; |
| |
| // Struct to store the arguments to the signal handler. |
| struct HandlerParams { |
| const tracing::StackUnwinderAndroid* unwinder; |
| // The event is signalled when signal handler is done executing. |
| AsyncSafeWaitableEvent* event; |
| |
| // Return values: |
| // Successfully copied the stack segment. |
| bool* success; |
| // The register context of the thread used by libunwind. |
| unw_context_t* context; |
| // The value of Stack pointer of the thread. |
| uintptr_t* sp; |
| // The context of the return function from signal context. |
| ucontext_t* ucontext; |
| // Buffer to copy the stack segment. |
| base::NativeStackSampler::StackBuffer* stack_buffer; |
| size_t* stack_size; |
| }; |
| |
| // Argument passed to the ThreadSignalHandler() from the sampling thread to the |
| // sampled (stopped) thread. This value is set just before sending kill signal |
| // to the thread and reset when handler is done. |
| base::subtle::AtomicWord g_handler_params; |
| |
| // The signal handler is called on the stopped thread as an additional stack |
| // frame. This relies on no alternate sigaltstack() being set. This function |
| // skips the handler frame on stack and unwinds the rest of the stack frames. |
| // This function should use async-safe functions only. The only call that could |
| // allocate memory on heap would be the cache in cfi unwinder. We need to ensure |
| // that AllocateCacheForCurrentThread() is called on the stopped thread before |
| // trying to get stack trace from the thread. See |
| // https://www.gnu.org/software/libc/manual/html_node/Nonreentrancy.html#Nonreentrancy. |
| static void ThreadSignalHandler(int n, siginfo_t* siginfo, void* sigcontext) { |
| HandlerParams* params = reinterpret_cast<HandlerParams*>( |
| base::subtle::Acquire_Load(&g_handler_params)); |
| ScopedEventSignaller e(params->event); |
| *params->success = false; |
| |
| uintptr_t sp = 0; |
| if (unw_getcontext(params->context) != 0) |
| return; |
| |
| asm volatile("mov %0, sp" : "=r"(sp)); |
| *params->sp = sp; |
| |
| memcpy(params->ucontext, sigcontext, sizeof(ucontext_t)); |
| |
| uintptr_t stack_base_addr = params->unwinder->GetEndAddressOfRegion(sp); |
| *params->stack_size = stack_base_addr - sp; |
| if (stack_base_addr == 0 || |
| *params->stack_size > params->stack_buffer->size()) |
| return; |
| memcpy(params->stack_buffer->buffer(), reinterpret_cast<void*>(sp), |
| *params->stack_size); |
| *params->success = true; |
| } |
| |
| // ARM EXIDX table contains addresses in sorted order with unwind data, each of |
| // 32 bits. |
| struct FakeExidx { |
| uintptr_t pc; |
| uintptr_t index_data; |
| }; |
| |
| } // namespace |
| |
| extern "C" { |
| |
| _Unwind_Ptr __real_dl_unwind_find_exidx(_Unwind_Ptr, int*); |
| |
| // Override the default |dl_unwind_find_exidx| function used by libunwind to |
| // give a fake unwind table just for the handler function. Otherwise call the |
| // original function. Libunwind marks the cursor invalid if it finds even one |
| // frame without unwind info. Mocking the info keeps the unwind cursor valid |
| // after unwind_init_local() within ThreadSignalHandler(). |
| __attribute__((visibility("default"), noinline)) _Unwind_Ptr |
| __wrap_dl_unwind_find_exidx(_Unwind_Ptr pc, int* length) { |
| if (!CFIBacktraceAndroid::is_chrome_address(pc)) { |
| return __real_dl_unwind_find_exidx(pc, length); |
| } |
| // Fake exidx table that is passed to libunwind to work with chrome functions. |
| // 0x80000000 has high bit set to 1. This means the unwind data is inline and |
| // not in exception table (section 5 EHABI). 0 on the second high byte causes |
| // a 0 proceedure to be lsda. But this is never executed since the pc and sp |
| // will be overridden, before calling unw_step. |
| static const FakeExidx chrome_exidx_data[] = { |
| {CFIBacktraceAndroid::executable_start_addr(), 0x80000000}, |
| {CFIBacktraceAndroid::executable_end_addr(), 0x80000000}}; |
| *length = sizeof(chrome_exidx_data); |
| return reinterpret_cast<_Unwind_Ptr>(chrome_exidx_data); |
| } |
| |
| } // extern "C" |
| |
| namespace tracing { |
| |
| // static |
| // Disable usage of libunwind till crbug/888434 if fixed. |
| const bool StackUnwinderAndroid::kUseLibunwind = false; |
| |
| StackUnwinderAndroid::StackUnwinderAndroid() {} |
| StackUnwinderAndroid::~StackUnwinderAndroid() {} |
| |
| void StackUnwinderAndroid::Initialize() { |
| is_initialized_ = true; |
| |
| // Ensure Chrome unwinder is initialized. |
| CFIBacktraceAndroid::GetInitializedInstance(); |
| |
| // Parses /proc/self/maps. |
| std::string contents; |
| if (!base::debug::ReadProcMaps(&contents)) |
| NOTREACHED(); |
| std::vector<base::debug::MappedMemoryRegion> regions; |
| if (!base::debug::ParseProcMaps(contents, ®ions)) |
| NOTREACHED(); |
| |
| // Remove any regions mapped to art java code, so that unwinder doesn't try to |
| // walk past java frames. Walking java frames causes crashes, crbug/888434. |
| base::EraseIf(regions, [](const base::debug::MappedMemoryRegion& region) { |
| return region.path.empty() || |
| base::EndsWith(region.path, ".art", base::CompareCase::SENSITIVE) || |
| base::EndsWith(region.path, ".oat", base::CompareCase::SENSITIVE) || |
| base::EndsWith(region.path, ".jar", base::CompareCase::SENSITIVE) || |
| base::EndsWith(region.path, ".vdex", base::CompareCase::SENSITIVE); |
| }); |
| std::sort(regions.begin(), regions.end(), |
| [](const MappedMemoryRegion& a, const MappedMemoryRegion& b) { |
| return a.start < b.start; |
| }); |
| regions_.swap(regions); |
| } |
| |
| size_t StackUnwinderAndroid::TraceStack(const void** out_trace, |
| size_t max_depth) const { |
| DCHECK(is_initialized_); |
| uintptr_t sp = 0; |
| ucontext_t sigcontext = {}; |
| unw_context_t context; |
| |
| if (unw_getcontext(&context) != 0) |
| return 0; |
| |
| asm volatile("mov %0, sp" : "=r"(sp)); |
| // If unwind goes beyond 4MB from top of stack, we stop. |
| const size_t kMaxStackSize = 4 * 1024 * 1024; |
| UnwindHelper helper(/*use_libunwind=*/true, this, sp, kMaxStackSize, nullptr, |
| out_trace, max_depth); |
| return helper.Unwind(sp, &context, sigcontext, nullptr); |
| } |
| |
| size_t StackUnwinderAndroid::TraceStack( |
| base::PlatformThreadId tid, |
| base::NativeStackSampler::StackBuffer* stack_buffer, |
| const void** out_trace, |
| size_t max_depth) const { |
| // Stops the thread with given tid with a signal handler. The signal handler |
| // copies the stack of the thread and returns. This function tries to unwind |
| // stack frames from the copied stack. |
| DCHECK(is_initialized_); |
| size_t stack_size; |
| unw_context_t context; |
| uintptr_t sp = 0; |
| ucontext_t signal_context = {}; |
| if (!SuspendThreadAndRecordStack(tid, stack_buffer, &sp, &stack_size, |
| &context, &signal_context)) { |
| RecordUnwindResult(SamplingProfilerUnwindResult::kStackCopyFailed); |
| return 0; |
| } |
| |
| UnwindHelper helper(kUseLibunwind, this, sp, stack_size, stack_buffer, |
| out_trace, max_depth); |
| return helper.Unwind(sp, &context, signal_context, stack_buffer); |
| } |
| |
| uintptr_t StackUnwinderAndroid::GetEndAddressOfRegion(uintptr_t addr) const { |
| auto it = |
| std::upper_bound(regions_.begin(), regions_.end(), addr, |
| [](uintptr_t addr, const MappedMemoryRegion& region) { |
| return addr < region.start; |
| }); |
| if (it == regions_.begin()) |
| return 0; |
| --it; |
| if (it->end > addr) |
| return it->end; |
| return 0; |
| } |
| |
| bool StackUnwinderAndroid::IsAddressMapped(uintptr_t pc) const { |
| // TODO(ssid): We only need to check regions which are file mapped. |
| return GetEndAddressOfRegion(pc) != 0; |
| } |
| |
| bool StackUnwinderAndroid::SuspendThreadAndRecordStack( |
| base::PlatformThreadId tid, |
| base::NativeStackSampler::StackBuffer* stack_buffer, |
| uintptr_t* sp, |
| size_t* stack_size, |
| unw_context_t* context, |
| ucontext_t* signal_context) const { |
| AsyncSafeWaitableEvent wait_event; |
| bool copied = false; |
| HandlerParams params = {this, &wait_event, &copied, context, |
| sp, signal_context, stack_buffer, stack_size}; |
| base::subtle::Release_Store(&g_handler_params, |
| reinterpret_cast<uintptr_t>(¶ms)); |
| |
| // Change the signal handler for the thread to unwind function, which should |
| // execute on the stack so that we will be able to unwind. |
| struct sigaction act; |
| struct sigaction oact; |
| memset(&act, 0, sizeof(act)); |
| act.sa_sigaction = ThreadSignalHandler; |
| act.sa_flags = SA_RESTART | SA_SIGINFO; |
| sigemptyset(&act.sa_mask); |
| // SIGURG is chosen here because we observe no crashes with this signal and |
| // neither Chrome or the AOSP sets up a special handler for this signal. |
| if (!sigaction(SIGURG, &act, &oact)) { |
| // Android renderer process knows the pid of the process. This might need to |
| // be fixed for use on Linux. |
| static const int kPid = getpid(); |
| if (tgkill(kPid, tid, SIGURG) != 0) { |
| NOTREACHED(); |
| return false; |
| } |
| bool finished_waiting = wait_event.Wait(); |
| |
| bool changed = sigaction(SIGURG, &oact, &act) == 0; |
| DCHECK(changed); |
| if (!finished_waiting) { |
| RecordUnwindResult(SamplingProfilerUnwindResult::kFutexSignalFailed); |
| NOTREACHED(); |
| return false; |
| } |
| } |
| base::subtle::Release_Store(&g_handler_params, 0); |
| return copied; |
| } |
| |
| } // namespace tracing |