Commit 5ca76e90 authored by Siddhartha's avatar Siddhartha Committed by Commit Bot

Android: Get register context from signal handler

1. Gets the register context from signal handler and sets all registers
correctly before using libunwind. This avoids crashes if registers are
used for unwinding frames.
2. Removes the need for unwinding signal handler function since we get
the context of the stopped function directly.

BUG=859260,888434

Change-Id: Ib337f20c19e25e2f82e61b08f8e9ed4ac1195fde
Reviewed-on: https://chromium-review.googlesource.com/c/1307055
Commit-Queue: ssid <ssid@chromium.org>
Reviewed-by: default avatarMike Wittman <wittman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#605121}
parent c00450e6
......@@ -82,9 +82,6 @@ class ScopedEventSignaller {
AsyncSafeWaitableEvent* event_;
};
using JniMarker = jni_generator::JniJavaCallContextUnchecked;
using JniMarkers = std::vector<const JniMarker*>;
// 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
......@@ -92,13 +89,15 @@ using JniMarkers = std::vector<const JniMarker*>;
// create the right context for libunwind from chrome functions.
// TODO(ssid): This function should support unwinding from chrome to android
// libraries also.
size_t TraceStackWithContext(unw_cursor_t* cursor,
CFIBacktraceAndroid* cfi_unwinder,
const tracing::StackUnwinderAndroid* unwinder,
const uintptr_t stack_segment_base,
const JniMarkers& jni_markers,
const void** out_trace,
const size_t max_depth) {
size_t TraceStackWithContext(
unw_cursor_t* cursor,
CFIBacktraceAndroid* cfi_unwinder,
const tracing::StackUnwinderAndroid* unwinder,
const uintptr_t stack_segment_base,
const std::vector<const tracing::StackUnwinderAndroid::JniMarker*>&
jni_markers,
const void** out_trace,
const size_t max_depth) {
size_t depth = 0;
unw_word_t ip = 0, sp = 0;
unw_get_reg(cursor, UNW_REG_SP, &sp);
......@@ -161,13 +160,82 @@ size_t TraceStackWithContext(unw_cursor_t* cursor,
return depth;
}
// Returns the offset of stack pointer for the given program counter in chrome
// library.
bool GetCFIForPC(CFIBacktraceAndroid* cfi_unwinder,
uintptr_t pc,
CFIBacktraceAndroid::CFIRow* cfi) {
return cfi_unwinder->FindCFIRowForPC(
pc - CFIBacktraceAndroid::executable_start_addr(), cfi);
uintptr_t RewritePointerIfInOriginalStack(uintptr_t addr,
uintptr_t sp,
uintptr_t stack_size,
uintptr_t new_stack_top) {
if (addr >= sp && addr < sp + stack_size)
return addr - sp + new_stack_top;
return addr;
}
// 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 sp,
size_t stack_size,
uintptr_t new_stack_top,
const ucontext_t& signal_context,
unw_context_t* context,
unw_cursor_t* cursor) {
// Initialize an unwind cursor on copied stack.
if (unw_init_local(cursor, context) != 0)
return false;
uintptr_t return_sp = signal_context.uc_mcontext.arm_sp - sp + new_stack_top;
// Reset the unwind cursor to previous function and continue with libunwind.
unw_set_reg(cursor, UNW_REG_SP, return_sp); // 13
unw_set_reg(cursor, UNW_ARM_R0,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r0,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R1,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r1,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R3,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r2,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R3,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r3,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R4,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r4,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R5,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r5,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R6,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r6,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R7,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r7,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R8,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r8,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R9,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r9,
sp, stack_size, new_stack_top));
unw_set_reg(
cursor, UNW_ARM_R10,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_r10, sp,
stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R11,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_fp,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_R12,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_ip,
sp, stack_size, new_stack_top));
unw_set_reg(cursor, UNW_ARM_LR,
RewritePointerIfInOriginalStack(signal_context.uc_mcontext.arm_lr,
sp, stack_size, new_stack_top));
// 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, signal_context.uc_mcontext.arm_pc); // 15
return true;
}
// Struct to store the arguments to the signal handler.
......@@ -183,6 +251,8 @@ struct HandlerParams {
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;
......@@ -214,6 +284,8 @@ static void ThreadSignalHandler(int n, siginfo_t* siginfo, void* sigcontext) {
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 ||
......@@ -298,7 +370,8 @@ size_t StackUnwinderAndroid::TraceStack(const void** out_trace,
return 0;
return TraceStackWithContext(
&cursor, CFIBacktraceAndroid::GetInitializedInstance(), this,
/* stack_segment_base=*/0, JniMarkers(), out_trace, max_depth);
/* stack_segment_base=*/0, std::vector<const JniMarker*>(), out_trace,
max_depth);
}
size_t StackUnwinderAndroid::TraceStack(
......@@ -310,13 +383,74 @@ size_t StackUnwinderAndroid::TraceStack(
// copies the stack of the thread and returns. This function tries to unwind
// stack frames from the copied stack.
DCHECK(is_initialized_);
AsyncSafeWaitableEvent wait_event;
size_t stack_size;
bool copied = false;
unw_context_t context;
uintptr_t sp = 0;
HandlerParams params = {this, &wait_event, &copied, &context,
&sp, stack_buffer, &stack_size};
ucontext_t signal_context = {};
if (!SuspendThreadAndRecordStack(tid, stack_buffer, &sp, &stack_size,
&context, &signal_context)) {
RecordUnwindResult(SamplingProfilerUnwindResult::kStackCopyFailed);
return 0;
}
const uintptr_t new_stack_top =
reinterpret_cast<uintptr_t>(stack_buffer->buffer());
uintptr_t ip = signal_context.uc_mcontext.arm_pc;
uintptr_t return_sp = signal_context.uc_mcontext.arm_sp - sp + new_stack_top;
auto* cfi_unwinder = CFIBacktraceAndroid::GetInitializedInstance();
// Do not use libunwind if we stopped at chrome frame.
if (CFIBacktraceAndroid::is_chrome_address(ip)) {
return cfi_unwinder->Unwind(
ip, return_sp, signal_context.uc_mcontext.arm_lr, out_trace, max_depth);
}
std::vector<const JniMarker*> jni_markers =
RewritePointersAndGetMarkers(stack_buffer, sp, stack_size);
unw_cursor_t cursor;
if (!GetUnwindCursorForStack(sp, stack_size, new_stack_top, signal_context,
&context, &cursor)) {
RecordUnwindResult(SamplingProfilerUnwindResult::kUnwindInitFailed);
return 0;
}
return TraceStackWithContext(
&cursor, cfi_unwinder, this,
reinterpret_cast<uintptr_t>(stack_buffer->buffer()) + stack_size,
jni_markers, out_trace, max_depth);
}
uintptr_t StackUnwinderAndroid::GetEndAddressOfRegion(uintptr_t addr) const {
auto it =
std::lower_bound(regions_.begin(), regions_.end(), addr,
[](const MappedMemoryRegion& region, uintptr_t addr) {
return region.start < addr;
});
if (it == regions_.begin())
return 0;
--it;
if (it->start <= addr && 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>(&params));
......@@ -343,37 +477,27 @@ size_t StackUnwinderAndroid::TraceStack(
}
}
base::subtle::Release_Store(&g_handler_params, 0);
if (!copied) {
RecordUnwindResult(SamplingProfilerUnwindResult::kStackCopyFailed);
return 0;
}
// Context contains list of saved registers. Replace the SP and any register
// that points to address on the previous stack to point to the copied stack.
const uintptr_t relocation_offset =
reinterpret_cast<uintptr_t>(stack_buffer->buffer()) - sp;
bool replaced_sp = false;
uintptr_t* register_context = reinterpret_cast<uintptr_t*>(&context);
for (size_t i = 0; i < 16; ++i) {
if (register_context[i] >= sp && register_context[i] < sp + stack_size) {
replaced_sp = replaced_sp || register_context[i] == sp;
register_context[i] += relocation_offset;
}
}
DCHECK(replaced_sp);
return copied;
}
std::vector<const StackUnwinderAndroid::JniMarker*>
StackUnwinderAndroid::RewritePointersAndGetMarkers(
base::NativeStackSampler::StackBuffer* stack_buffer,
uintptr_t sp,
size_t stack_size) const {
std::vector<const JniMarker*> jni_markers;
uintptr_t* new_stack = reinterpret_cast<uintptr_t*>(stack_buffer->buffer());
constexpr uintptr_t marker_l =
jni_generator::kJniStackMarkerValue & 0xFFFFFFFF,
marker_r = jni_generator::kJniStackMarkerValue >> 32;
JniMarkers jni_markers;
const uintptr_t new_stack_top =
reinterpret_cast<uintptr_t>(stack_buffer->buffer());
for (size_t i = 0; i < stack_size / sizeof(uintptr_t); ++i) {
if (new_stack[i] == marker_r && i > 0 && new_stack[i - 1] == marker_l) {
// Note: JniJavaCallContext::sp will be replaced with offset below.
const JniMarker* marker =
reinterpret_cast<const JniMarker*>(new_stack + i - 1);
DCHECK_EQ(jni_generator::kJniStackMarkerValue,
jni_markers.back()->marker);
DCHECK_EQ(jni_generator::kJniStackMarkerValue, marker->marker);
if (marker->sp >= sp && marker->sp < sp + stack_size &&
CFIBacktraceAndroid::is_chrome_address(marker->pc)) {
jni_markers.push_back(marker);
......@@ -385,67 +509,9 @@ size_t StackUnwinderAndroid::TraceStack(
// Unwind can use address on the stack. So, replace them as well. See EHABI
// #7.5.4 table 3.
if (new_stack[i] >= sp && new_stack[i] < sp + stack_size)
new_stack[i] += relocation_offset;
new_stack[i] = new_stack[i] - sp + new_stack_top;
}
// Initialize an unwind cursor on copied stack.
unw_cursor_t cursor;
if (unw_init_local(&cursor, &context) != 0) {
RecordUnwindResult(SamplingProfilerUnwindResult::kUnwindInitFailed);
return 0;
}
uintptr_t ip = 0;
unw_get_reg(&cursor, UNW_REG_SP, &sp);
DCHECK_EQ(sp, reinterpret_cast<uintptr_t>(stack_buffer->buffer()));
unw_get_reg(&cursor, UNW_REG_IP, &ip);
// Unwind handler function (ThreadSignalHandler()) since libunwind cannot
// handle chrome functions. Then call either libunwind or use chrome's
// unwinder based on the next function in the stack.
auto* cfi_unwinder = CFIBacktraceAndroid::GetInitializedInstance();
static CFIBacktraceAndroid::CFIRow cfi;
static bool found = GetCFIForPC(cfi_unwinder, ip, &cfi);
if (!found) {
RecordUnwindResult(SamplingProfilerUnwindResult::kHandlerUnwindFailed);
return 0;
}
sp = sp + cfi.cfa_offset;
memcpy(&ip, reinterpret_cast<uintptr_t*>(sp - cfi.ra_offset),
sizeof(uintptr_t));
// Do not use libunwind if we stopped at chrome frame.
if (CFIBacktraceAndroid::is_chrome_address(ip))
return cfi_unwinder->Unwind(ip, sp, 0, out_trace, max_depth);
// Reset the unwind cursor to previous function and continue with libunwind.
// TODO(ssid): Dynamic allocation functions might require registers to be
// restored.
unw_set_reg(&cursor, UNW_REG_SP, sp);
unw_set_reg(&cursor, UNW_REG_IP, ip);
return TraceStackWithContext(
&cursor, cfi_unwinder, this,
reinterpret_cast<uintptr_t>(stack_buffer->buffer()) + stack_size,
jni_markers, out_trace, max_depth);
}
uintptr_t StackUnwinderAndroid::GetEndAddressOfRegion(uintptr_t addr) const {
auto it =
std::lower_bound(regions_.begin(), regions_.end(), addr,
[](const MappedMemoryRegion& region, uintptr_t addr) {
return region.start < addr;
});
if (it == regions_.begin())
return 0;
--it;
if (it->start <= addr && 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;
return jni_markers;
}
} // namespace tracing
......@@ -5,13 +5,21 @@
#ifndef COMPONENTS_TRACING_COMMON_STACK_UNWINDER_ANDROID_H_
#define COMPONENTS_TRACING_COMMON_STACK_UNWINDER_ANDROID_H_
#include <ucontext.h>
#include <map>
#include <vector>
#include "base/debug/proc_maps_linux.h"
#include "base/profiler/native_stack_sampler.h"
#include "base/threading/platform_thread.h"
#include "components/tracing/tracing_export.h"
namespace jni_generator {
struct JniJavaCallContextUnchecked;
}
struct unw_context_t;
namespace tracing {
// Utility to unwind stacks for current thread on ARM devices. Contains ability
......@@ -24,6 +32,8 @@ namespace tracing {
// instances of this class.
class TRACING_EXPORT StackUnwinderAndroid {
public:
using JniMarker = jni_generator::JniJavaCallContextUnchecked;
StackUnwinderAndroid();
~StackUnwinderAndroid();
......@@ -53,6 +63,24 @@ class TRACING_EXPORT StackUnwinderAndroid {
bool is_initialized() const { return is_initialized_; }
private:
// Sends a SIGURG signal to the thread with id |tid| and copies the stack
// segment of the thread, along with register context. Returns true on
// success.
bool 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;
// Replaces any pointers to the old stack to point to the new stack segment.
// Returns the jni markers found on stack while scanning stack for pointers.
std::vector<const JniMarker*> RewritePointersAndGetMarkers(
base::NativeStackSampler::StackBuffer* stack_buffer,
uintptr_t sp,
size_t stack_size) const;
bool is_initialized_ = false;
// Stores all the memory mapped regions in the current process, including all
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment