aboutsummaryrefslogtreecommitdiff
path: root/src/processor
diff options
context:
space:
mode:
authormark@chromium.org <mark@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2013-11-23 01:45:20 +0000
committermark@chromium.org <mark@chromium.org@4c0a9323-5329-0410-9bdc-e9ce6186880e>2013-11-23 01:45:20 +0000
commite9165f4353a50aee322ad4caf2f499d26d77ed6f (patch)
tree4cbc9e273536afe43f9b68cb617036170b381af8 /src/processor
parentGenerate minidumps for 64-bit ARM apps on iOS. (diff)
downloadbreakpad-e9165f4353a50aee322ad4caf2f499d26d77ed6f.tar.xz
Process minidumps generated on ARM64 in iOS apps.
Patch by Colin Blundell <blundell@chromium.org> BUG=542 Review URL: https://breakpad.appspot.com/704002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1236 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/processor')
-rwxr-xr-xsrc/processor/minidump.cc191
-rw-r--r--src/processor/minidump_processor.cc15
-rw-r--r--src/processor/minidump_stackwalk.cc141
-rw-r--r--src/processor/stack_frame_cpu.cc79
-rw-r--r--src/processor/stackwalker.cc10
-rw-r--r--src/processor/stackwalker_arm64.cc209
-rw-r--r--src/processor/stackwalker_arm64.h102
-rw-r--r--src/processor/stackwalker_arm64_unittest.cc536
8 files changed, 1276 insertions, 7 deletions
diff --git a/src/processor/minidump.cc b/src/processor/minidump.cc
index c7f2bcaa..1bb1bfcf 100755
--- a/src/processor/minidump.cc
+++ b/src/processor/minidump.cc
@@ -73,6 +73,30 @@ using std::ifstream;
using std::numeric_limits;
using std::vector;
+// Returns true iff |context_size| matches exactly one of the sizes of the
+// various MDRawContext* types.
+// TODO(blundell): This function can be removed once
+// http://code.google.com/p/google-breakpad/issues/detail?id=550 is fixed.
+static bool IsContextSizeUnique(uint32_t context_size) {
+ int num_matching_contexts = 0;
+ if (context_size == sizeof(MDRawContextX86))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextPPC))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextPPC64))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextAMD64))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextSPARC))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextARM))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextARM64))
+ num_matching_contexts++;
+ if (context_size == sizeof(MDRawContextMIPS))
+ num_matching_contexts++;
+ return num_matching_contexts == 1;
+}
//
// Swapping routines
@@ -361,6 +385,23 @@ MinidumpContext::~MinidumpContext() {
bool MinidumpContext::Read(uint32_t expected_size) {
valid_ = false;
+ // Certain raw context types are currently assumed to have unique sizes.
+ if (!IsContextSizeUnique(sizeof(MDRawContextAMD64))) {
+ BPLOG(ERROR) << "sizeof(MDRawContextAMD64) cannot match the size of any "
+ << "other raw context";
+ return false;
+ }
+ if (!IsContextSizeUnique(sizeof(MDRawContextPPC64))) {
+ BPLOG(ERROR) << "sizeof(MDRawContextPPC64) cannot match the size of any "
+ << "other raw context";
+ return false;
+ }
+ if (!IsContextSizeUnique(sizeof(MDRawContextARM64))) {
+ BPLOG(ERROR) << "sizeof(MDRawContextARM64) cannot match the size of any "
+ << "other raw context";
+ return false;
+ }
+
FreeContext();
// First, figure out what type of CPU this context structure is for.
@@ -390,9 +431,8 @@ bool MinidumpContext::Read(uint32_t expected_size) {
}
if (cpu_type != MD_CONTEXT_AMD64) {
- // TODO: fall through to switch below?
- // need a Tell method to be able to SeekSet back to beginning
- // http://code.google.com/p/google-breakpad/issues/detail?id=224
+ // TODO: Fall through to switch below.
+ // http://code.google.com/p/google-breakpad/issues/detail?id=550
BPLOG(ERROR) << "MinidumpContext not actually amd64 context";
return false;
}
@@ -483,6 +523,21 @@ bool MinidumpContext::Read(uint32_t expected_size) {
uint32_t cpu_type = context_flags & MD_CONTEXT_CPU_MASK;
scoped_ptr<MDRawContextPPC64> context_ppc64(new MDRawContextPPC64());
+ if (cpu_type == 0) {
+ if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
+ context_ppc64->context_flags |= cpu_type;
+ } else {
+ BPLOG(ERROR) << "Failed to preserve the current stream position";
+ return false;
+ }
+ }
+
+ if (cpu_type != MD_CONTEXT_PPC64) {
+ // TODO: Fall through to switch below.
+ // http://code.google.com/p/google-breakpad/issues/detail?id=550
+ BPLOG(ERROR) << "MinidumpContext not actually ppc64 context";
+ return false;
+ }
// Set the context_flags member, which has already been read, and
// read the rest of the structure beginning with the first member
@@ -548,6 +603,83 @@ bool MinidumpContext::Read(uint32_t expected_size) {
}
context_.ppc64 = context_ppc64.release();
+ context_flags_ = context_flags;
+ } else if (expected_size == sizeof(MDRawContextARM64)) {
+ // |context_flags| of MDRawContextARM64 is 64 bits, but other MDRawContext
+ // in the else case have 32 bits |context_flags|, so special case it here.
+ uint64_t context_flags;
+
+ BPLOG(INFO) << "MinidumpContext: looks like ARM64 context";
+
+ if (!minidump_->ReadBytes(&context_flags, sizeof(context_flags))) {
+ BPLOG(ERROR) << "MinidumpContext could not read context flags";
+ return false;
+ }
+ if (minidump_->swap())
+ Swap(&context_flags);
+
+ scoped_ptr<MDRawContextARM64> context_arm64(new MDRawContextARM64());
+
+ uint32_t cpu_type = context_flags & MD_CONTEXT_CPU_MASK;
+ if (cpu_type == 0) {
+ if (minidump_->GetContextCPUFlagsFromSystemInfo(&cpu_type)) {
+ context_arm64->context_flags |= cpu_type;
+ } else {
+ BPLOG(ERROR) << "Failed to preserve the current stream position";
+ return false;
+ }
+ }
+
+ if (cpu_type != MD_CONTEXT_ARM64) {
+ // TODO: Fall through to switch below.
+ // http://code.google.com/p/google-breakpad/issues/detail?id=550
+ BPLOG(ERROR) << "MinidumpContext not actually arm64 context";
+ return false;
+ }
+
+ // Set the context_flags member, which has already been read, and
+ // read the rest of the structure beginning with the first member
+ // after context_flags.
+ context_arm64->context_flags = context_flags;
+
+ size_t flags_size = sizeof(context_arm64->context_flags);
+ uint8_t* context_after_flags =
+ reinterpret_cast<uint8_t*>(context_arm64.get()) + flags_size;
+ if (!minidump_->ReadBytes(context_after_flags,
+ sizeof(MDRawContextARM64) - flags_size)) {
+ BPLOG(ERROR) << "MinidumpContext could not read arm64 context";
+ return false;
+ }
+
+ // Do this after reading the entire MDRawContext structure because
+ // GetSystemInfo may seek minidump to a new position.
+ if (!CheckAgainstSystemInfo(cpu_type)) {
+ BPLOG(ERROR) << "MinidumpContext arm64 does not match system info";
+ return false;
+ }
+
+ if (minidump_->swap()) {
+ // context_arm64->context_flags was already swapped.
+ for (unsigned int ireg_index = 0;
+ ireg_index < MD_CONTEXT_ARM64_GPR_COUNT;
+ ++ireg_index) {
+ Swap(&context_arm64->iregs[ireg_index]);
+ }
+ Swap(&context_arm64->cpsr);
+ Swap(&context_arm64->float_save.fpsr);
+ Swap(&context_arm64->float_save.fpcr);
+ for (unsigned int fpr_index = 0;
+ fpr_index < MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT;
+ ++fpr_index) {
+ // While ARM64 is bi-endian, iOS (currently the only platform
+ // for which ARM64 support has been brought up) uses ARM64 exclusively
+ // in little-endian mode.
+ Normalize128(&context_arm64->float_save.regs[fpr_index], false);
+ Swap(&context_arm64->float_save.regs[fpr_index]);
+ }
+ }
+ context_.arm64 = context_arm64.release();
+ context_flags_ = context_flags;
} else {
uint32_t context_flags;
if (!minidump_->ReadBytes(&context_flags, sizeof(context_flags))) {
@@ -954,6 +1086,9 @@ bool MinidumpContext::GetInstructionPointer(uint64_t* ip) const {
case MD_CONTEXT_ARM:
*ip = context_.arm->iregs[MD_CONTEXT_ARM_REG_PC];
break;
+ case MD_CONTEXT_ARM64:
+ *ip = context_.arm64->iregs[MD_CONTEXT_ARM64_REG_PC];
+ break;
case MD_CONTEXT_PPC:
*ip = context_.ppc->srr0;
break;
@@ -1033,6 +1168,15 @@ const MDRawContextARM* MinidumpContext::GetContextARM() const {
return context_.arm;
}
+const MDRawContextARM64* MinidumpContext::GetContextARM64() const {
+ if (GetContextCPU() != MD_CONTEXT_ARM64) {
+ BPLOG(ERROR) << "MinidumpContext cannot get arm64 context";
+ return NULL;
+ }
+
+ return context_.arm64;
+}
+
const MDRawContextMIPS* MinidumpContext::GetContextMIPS() const {
if (GetContextCPU() != MD_CONTEXT_MIPS) {
BPLOG(ERROR) << "MinidumpContext cannot get MIPS context";
@@ -1068,6 +1212,10 @@ void MinidumpContext::FreeContext() {
delete context_.arm;
break;
+ case MD_CONTEXT_ARM64:
+ delete context_.arm64;
+ break;
+
case MD_CONTEXT_MIPS:
delete context_.ctx_mips;
break;
@@ -1142,6 +1290,11 @@ bool MinidumpContext::CheckAgainstSystemInfo(uint32_t context_cpu_type) {
return_value = true;
break;
+ case MD_CONTEXT_ARM64:
+ if (system_info_cpu_type == MD_CPU_ARCHITECTURE_ARM64)
+ return_value = true;
+ break;
+
case MD_CONTEXT_MIPS:
if (system_info_cpu_type == MD_CPU_ARCHITECTURE_MIPS)
return_value = true;
@@ -1423,6 +1576,31 @@ void MinidumpContext::Print() {
break;
}
+
+ case MD_CONTEXT_ARM64: {
+ const MDRawContextARM64* context_arm64 = GetContextARM64();
+ printf("MDRawContextARM64\n");
+ printf(" context_flags = 0x%llx\n",
+ context_arm64->context_flags);
+ for (unsigned int ireg_index = 0;
+ ireg_index < MD_CONTEXT_ARM64_GPR_COUNT;
+ ++ireg_index) {
+ printf(" iregs[%2d] = 0x%llx\n",
+ ireg_index, context_arm64->iregs[ireg_index]);
+ }
+ printf(" cpsr = 0x%x\n", context_arm64->cpsr);
+ printf(" float_save.fpsr = 0x%x\n", context_arm64->float_save.fpsr);
+ printf(" float_save.fpcr = 0x%x\n", context_arm64->float_save.fpcr);
+
+ for (unsigned int freg_index = 0;
+ freg_index < MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT;
+ ++freg_index) {
+ uint128_struct fp_value = context_arm64->float_save.regs[freg_index];
+ printf(" float_save.regs[%2d] = 0x%llx%llx\n",
+ freg_index, fp_value.high, fp_value.low);
+ }
+ break;
+ }
case MD_CONTEXT_MIPS: {
const MDRawContextMIPS* context_mips = GetContextMIPS();
@@ -3530,6 +3708,10 @@ string MinidumpSystemInfo::GetCPU() {
cpu = "arm";
break;
+ case MD_CPU_ARCHITECTURE_ARM64:
+ cpu = "arm64";
+ break;
+
default:
BPLOG(ERROR) << "MinidumpSystemInfo unknown CPU for architecture " <<
HexString(system_info_.processor_architecture);
@@ -4265,6 +4447,9 @@ bool Minidump::GetContextCPUFlagsFromSystemInfo(uint32_t *context_cpu_flags) {
case MD_CPU_ARCHITECTURE_ARM:
*context_cpu_flags = MD_CONTEXT_ARM;
break;
+ case MD_CPU_ARCHITECTURE_ARM64:
+ *context_cpu_flags = MD_CONTEXT_ARM64;
+ break;
case MD_CPU_ARCHITECTURE_IA64:
*context_cpu_flags = MD_CONTEXT_IA64;
break;
diff --git a/src/processor/minidump_processor.cc b/src/processor/minidump_processor.cc
index 17daa997..84c5889b 100644
--- a/src/processor/minidump_processor.cc
+++ b/src/processor/minidump_processor.cc
@@ -505,6 +505,11 @@ bool MinidumpProcessor::GetCPUInfo(Minidump *dump, SystemInfo *info) {
break;
}
+ case MD_CPU_ARCHITECTURE_ARM64: {
+ info->cpu = "arm64";
+ break;
+ }
+
case MD_CPU_ARCHITECTURE_MIPS: {
info->cpu = "mips";
break;
@@ -668,7 +673,9 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, uint64_t *address) {
default:
// arm and ppc overlap
if (raw_system_info->processor_architecture ==
- MD_CPU_ARCHITECTURE_ARM) {
+ MD_CPU_ARCHITECTURE_ARM ||
+ raw_system_info->processor_architecture ==
+ MD_CPU_ARCHITECTURE_ARM64) {
switch (exception_flags) {
case MD_EXCEPTION_CODE_MAC_ARM_DA_ALIGN:
reason.append("EXC_ARM_DA_ALIGN");
@@ -708,7 +715,8 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, uint64_t *address) {
case MD_EXCEPTION_MAC_BAD_INSTRUCTION:
reason = "EXC_BAD_INSTRUCTION / ";
switch (raw_system_info->processor_architecture) {
- case MD_CPU_ARCHITECTURE_ARM: {
+ case MD_CPU_ARCHITECTURE_ARM:
+ case MD_CPU_ARCHITECTURE_ARM64: {
switch (exception_flags) {
case MD_EXCEPTION_CODE_MAC_ARM_UNDEFINED:
reason.append("EXC_ARM_UNDEFINED");
@@ -887,7 +895,8 @@ string MinidumpProcessor::GetCrashReason(Minidump *dump, uint64_t *address) {
case MD_EXCEPTION_MAC_BREAKPOINT:
reason = "EXC_BREAKPOINT / ";
switch (raw_system_info->processor_architecture) {
- case MD_CPU_ARCHITECTURE_ARM: {
+ case MD_CPU_ARCHITECTURE_ARM:
+ case MD_CPU_ARCHITECTURE_ARM64: {
switch (exception_flags) {
case MD_EXCEPTION_CODE_MAC_ARM_DA_ALIGN:
reason.append("EXC_ARM_DA_ALIGN");
diff --git a/src/processor/minidump_stackwalk.cc b/src/processor/minidump_stackwalk.cc
index a49ed061..d43edd37 100644
--- a/src/processor/minidump_stackwalk.cc
+++ b/src/processor/minidump_stackwalk.cc
@@ -72,6 +72,7 @@ using google_breakpad::StackFrameSPARC;
using google_breakpad::StackFrameX86;
using google_breakpad::StackFrameAMD64;
using google_breakpad::StackFrameARM;
+using google_breakpad::StackFrameARM64;
using google_breakpad::StackFrameMIPS;
// Separator character for machine readable output.
@@ -272,6 +273,144 @@ static void PrintStack(const CallStack *stack, const string &cpu) {
sequence = PrintRegister("lr", frame_arm->context.iregs[14], sequence);
if (frame_arm->context_validity & StackFrameARM::CONTEXT_VALID_PC)
sequence = PrintRegister("pc", frame_arm->context.iregs[15], sequence);
+ } else if (cpu == "arm64") {
+ const StackFrameARM64 *frame_arm64 =
+ reinterpret_cast<const StackFrameARM64*>(frame);
+
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X0) {
+ sequence =
+ PrintRegister64("x0", frame_arm64->context.iregs[0], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X1) {
+ sequence =
+ PrintRegister64("x1", frame_arm64->context.iregs[1], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X2) {
+ sequence =
+ PrintRegister64("x2", frame_arm64->context.iregs[2], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X3) {
+ sequence =
+ PrintRegister64("x3", frame_arm64->context.iregs[3], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X4) {
+ sequence =
+ PrintRegister64("x4", frame_arm64->context.iregs[4], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X5) {
+ sequence =
+ PrintRegister64("x5", frame_arm64->context.iregs[5], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X6) {
+ sequence =
+ PrintRegister64("x6", frame_arm64->context.iregs[6], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X7) {
+ sequence =
+ PrintRegister64("x7", frame_arm64->context.iregs[7], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X8) {
+ sequence =
+ PrintRegister64("x8", frame_arm64->context.iregs[8], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X9) {
+ sequence =
+ PrintRegister64("x9", frame_arm64->context.iregs[9], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X10) {
+ sequence =
+ PrintRegister64("x10", frame_arm64->context.iregs[10], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X11) {
+ sequence =
+ PrintRegister64("x11", frame_arm64->context.iregs[11], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X12) {
+ sequence =
+ PrintRegister64("x12", frame_arm64->context.iregs[12], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X13) {
+ sequence =
+ PrintRegister64("x13", frame_arm64->context.iregs[13], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X14) {
+ sequence =
+ PrintRegister64("x14", frame_arm64->context.iregs[14], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X15) {
+ sequence =
+ PrintRegister64("x15", frame_arm64->context.iregs[15], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X16) {
+ sequence =
+ PrintRegister64("x16", frame_arm64->context.iregs[16], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X17) {
+ sequence =
+ PrintRegister64("x17", frame_arm64->context.iregs[17], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X18) {
+ sequence =
+ PrintRegister64("x18", frame_arm64->context.iregs[18], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X19) {
+ sequence =
+ PrintRegister64("x19", frame_arm64->context.iregs[19], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X20) {
+ sequence =
+ PrintRegister64("x20", frame_arm64->context.iregs[20], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X21) {
+ sequence =
+ PrintRegister64("x21", frame_arm64->context.iregs[21], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X22) {
+ sequence =
+ PrintRegister64("x22", frame_arm64->context.iregs[22], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X23) {
+ sequence =
+ PrintRegister64("x23", frame_arm64->context.iregs[23], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X24) {
+ sequence =
+ PrintRegister64("x24", frame_arm64->context.iregs[24], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X25) {
+ sequence =
+ PrintRegister64("x25", frame_arm64->context.iregs[25], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X26) {
+ sequence =
+ PrintRegister64("x26", frame_arm64->context.iregs[26], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X27) {
+ sequence =
+ PrintRegister64("x27", frame_arm64->context.iregs[27], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_X28) {
+ sequence =
+ PrintRegister64("x28", frame_arm64->context.iregs[28], sequence);
+ }
+
+ // Registers with a dedicated or conventional purpose.
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_FP) {
+ sequence =
+ PrintRegister64("fp", frame_arm64->context.iregs[29], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_LR) {
+ sequence =
+ PrintRegister64("lr", frame_arm64->context.iregs[30], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_SP) {
+ sequence =
+ PrintRegister64("sp", frame_arm64->context.iregs[31], sequence);
+ }
+ if (frame_arm64->context_validity & StackFrameARM64::CONTEXT_VALID_PC) {
+ sequence =
+ PrintRegister64("pc", frame_arm64->context.iregs[32], sequence);
+ }
} else if (cpu == "mips") {
const StackFrameMIPS* frame_mips =
reinterpret_cast<const StackFrameMIPS*>(frame);
@@ -328,7 +467,7 @@ static void PrintStack(const CallStack *stack, const string &cpu) {
sequence = PrintRegister64("s7",
frame_mips->context.iregs[MD_CONTEXT_MIPS_REG_S7],
sequence);
- }
+ }
printf("\n Found by: %s\n", frame->trust_description().c_str());
}
}
diff --git a/src/processor/stack_frame_cpu.cc b/src/processor/stack_frame_cpu.cc
new file mode 100644
index 00000000..6175dc7f
--- /dev/null
+++ b/src/processor/stack_frame_cpu.cc
@@ -0,0 +1,79 @@
+// Copyright 2013 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// stack_frame_cpu.h: CPU-specific StackFrame extensions.
+//
+// See google_breakpad/processor/stack_frame_cpu.h for documentation.
+//
+// Author: Colin Blundell
+
+#include "google_breakpad/processor/stack_frame_cpu.h"
+
+namespace google_breakpad {
+
+const uint64_t StackFrameARM64::CONTEXT_VALID_X0;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X1;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X2;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X3;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X4;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X5;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X6;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X7;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X8;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X9;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X10;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X11;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X12;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X13;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X14;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X15;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X16;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X17;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X18;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X19;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X20;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X21;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X22;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X23;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X24;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X25;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X26;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X27;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X28;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X29;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X30;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X31;
+const uint64_t StackFrameARM64::CONTEXT_VALID_X32;
+const uint64_t StackFrameARM64::CONTEXT_VALID_FP;
+const uint64_t StackFrameARM64::CONTEXT_VALID_LR;
+const uint64_t StackFrameARM64::CONTEXT_VALID_SP;
+const uint64_t StackFrameARM64::CONTEXT_VALID_PC;
+const uint64_t StackFrameARM64::CONTEXT_VALID_ALL;
+
+} // namespace google_breakpad
diff --git a/src/processor/stackwalker.cc b/src/processor/stackwalker.cc
index 8a6b422d..2ca8f166 100644
--- a/src/processor/stackwalker.cc
+++ b/src/processor/stackwalker.cc
@@ -53,6 +53,7 @@
#include "processor/stackwalker_x86.h"
#include "processor/stackwalker_amd64.h"
#include "processor/stackwalker_arm.h"
+#include "processor/stackwalker_arm64.h"
#include "processor/stackwalker_mips.h"
namespace google_breakpad {
@@ -239,6 +240,7 @@ Stackwalker* Stackwalker::StackwalkerForCPU(
break;
case MD_CONTEXT_ARM:
+ {
int fp_register = -1;
if (system_info->os_short == "ios")
fp_register = MD_CONTEXT_ARM_REG_IOS_FP;
@@ -247,6 +249,14 @@ Stackwalker* Stackwalker::StackwalkerForCPU(
fp_register, memory, modules,
frame_symbolizer);
break;
+ }
+
+ case MD_CONTEXT_ARM64:
+ cpu_stackwalker = new StackwalkerARM64(system_info,
+ context->GetContextARM64(),
+ memory, modules,
+ frame_symbolizer);
+ break;
}
BPLOG_IF(ERROR, !cpu_stackwalker) << "Unknown CPU type " << HexString(cpu) <<
diff --git a/src/processor/stackwalker_arm64.cc b/src/processor/stackwalker_arm64.cc
new file mode 100644
index 00000000..f82c9cbf
--- /dev/null
+++ b/src/processor/stackwalker_arm64.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2013 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// stackwalker_arm64.cc: arm64-specific stackwalker.
+//
+// See stackwalker_arm64.h for documentation.
+//
+// Author: Mark Mentovai, Ted Mielczarek, Jim Blandy, Colin Blundell
+
+#include <vector>
+
+#include "common/scoped_ptr.h"
+#include "google_breakpad/processor/call_stack.h"
+#include "google_breakpad/processor/memory_region.h"
+#include "google_breakpad/processor/source_line_resolver_interface.h"
+#include "google_breakpad/processor/stack_frame_cpu.h"
+#include "processor/cfi_frame_info.h"
+#include "processor/logging.h"
+#include "processor/stackwalker_arm64.h"
+
+namespace google_breakpad {
+
+
+StackwalkerARM64::StackwalkerARM64(const SystemInfo* system_info,
+ const MDRawContextARM64* context,
+ MemoryRegion* memory,
+ const CodeModules* modules,
+ StackFrameSymbolizer* resolver_helper)
+ : Stackwalker(system_info, memory, modules, resolver_helper),
+ context_(context),
+ context_frame_validity_(StackFrameARM64::CONTEXT_VALID_ALL) { }
+
+
+StackFrame* StackwalkerARM64::GetContextFrame() {
+ if (!context_) {
+ BPLOG(ERROR) << "Can't get context frame without context";
+ return NULL;
+ }
+
+ StackFrameARM64* frame = new StackFrameARM64();
+
+ // The instruction pointer is stored directly in a register (x32), so pull it
+ // straight out of the CPU context structure.
+ frame->context = *context_;
+ frame->context_validity = context_frame_validity_;
+ frame->trust = StackFrame::FRAME_TRUST_CONTEXT;
+ frame->instruction = frame->context.iregs[MD_CONTEXT_ARM64_REG_PC];
+
+ return frame;
+}
+
+StackFrameARM64* StackwalkerARM64::GetCallerByCFIFrameInfo(
+ const vector<StackFrame*> &frames,
+ CFIFrameInfo* cfi_frame_info) {
+ // Obtaining the stack frame from CFI info is not yet supported for ARM64.
+ return NULL;
+}
+
+StackFrameARM64* StackwalkerARM64::GetCallerByStackScan(
+ const vector<StackFrame*> &frames) {
+ StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
+ uint64_t last_sp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP];
+ uint64_t caller_sp, caller_pc;
+
+ if (!ScanForReturnAddress(last_sp, &caller_sp, &caller_pc,
+ frames.size() == 1 /* is_context_frame */)) {
+ // No plausible return address was found.
+ return NULL;
+ }
+
+ // ScanForReturnAddress found a reasonable return address. Advance
+ // %sp to the location above the one where the return address was
+ // found.
+ caller_sp += 8;
+
+ // Create a new stack frame (ownership will be transferred to the caller)
+ // and fill it in.
+ StackFrameARM64* frame = new StackFrameARM64();
+
+ frame->trust = StackFrame::FRAME_TRUST_SCAN;
+ frame->context = last_frame->context;
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] = caller_pc;
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = caller_sp;
+ frame->context_validity = StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_SP;
+
+ return frame;
+}
+
+StackFrameARM64* StackwalkerARM64::GetCallerByFramePointer(
+ const vector<StackFrame*> &frames) {
+ StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
+
+ uint64_t last_fp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP];
+
+ uint64_t caller_fp = 0;
+ if (last_fp && !memory_->GetMemoryAtAddress(last_fp, &caller_fp)) {
+ BPLOG(ERROR) << "Unable to read caller_fp from last_fp: 0x"
+ << std::hex << last_fp;
+ return NULL;
+ }
+
+ uint64_t caller_lr = 0;
+ if (last_fp && !memory_->GetMemoryAtAddress(last_fp + 8, &caller_lr)) {
+ BPLOG(ERROR) << "Unable to read caller_lr from last_fp + 8: 0x"
+ << std::hex << (last_fp + 8);
+ return NULL;
+ }
+
+ uint64_t caller_sp = last_fp ? last_fp + 16 :
+ last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP];
+
+ // Create a new stack frame (ownership will be transferred to the caller)
+ // and fill it in.
+ StackFrameARM64* frame = new StackFrameARM64();
+
+ frame->trust = StackFrame::FRAME_TRUST_FP;
+ frame->context = last_frame->context;
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] = caller_fp;
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = caller_sp;
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] =
+ last_frame->context.iregs[MD_CONTEXT_ARM64_REG_LR];
+ frame->context.iregs[MD_CONTEXT_ARM64_REG_LR] = caller_lr;
+ frame->context_validity = StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_LR |
+ StackFrameARM64::CONTEXT_VALID_FP |
+ StackFrameARM64::CONTEXT_VALID_SP;
+ return frame;
+}
+
+StackFrame* StackwalkerARM64::GetCallerFrame(const CallStack* stack,
+ bool stack_scan_allowed) {
+ if (!memory_ || !stack) {
+ BPLOG(ERROR) << "Can't get caller frame without memory or stack";
+ return NULL;
+ }
+
+ const vector<StackFrame*> &frames = *stack->frames();
+ StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
+ scoped_ptr<StackFrameARM64> frame;
+
+ // See if there is DWARF call frame information covering this address.
+ scoped_ptr<CFIFrameInfo> cfi_frame_info(
+ frame_symbolizer_->FindCFIFrameInfo(last_frame));
+ if (cfi_frame_info.get())
+ frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get()));
+
+ // If CFI failed, or there wasn't CFI available, fall back to frame pointer.
+ if (!frame.get())
+ frame.reset(GetCallerByFramePointer(frames));
+
+ // If everything failed, fall back to stack scanning.
+ if (stack_scan_allowed && !frame.get())
+ frame.reset(GetCallerByStackScan(frames));
+
+ // If nothing worked, tell the caller.
+ if (!frame.get())
+ return NULL;
+
+ // An instruction address of zero marks the end of the stack.
+ if (frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] == 0)
+ return NULL;
+
+ // If the new stack pointer is at a lower address than the old, then
+ // that's clearly incorrect. Treat this as end-of-stack to enforce
+ // progress and avoid infinite loops.
+ if (frame->context.iregs[MD_CONTEXT_ARM64_REG_SP]
+ < last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP])
+ return NULL;
+
+ // The new frame's context's PC is the return address, which is one
+ // instruction past the instruction that caused us to arrive at the callee.
+ // ARM64 instructions have a uniform 4-byte encoding, so subtracting 4 off
+ // the return address gets back to the beginning of the call instruction.
+ // Callers that require the exact return address value may access
+ // frame->context.iregs[MD_CONTEXT_ARM64_REG_PC].
+ frame->instruction = frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] - 4;
+
+ return frame.release();
+}
+
+
+} // namespace google_breakpad
diff --git a/src/processor/stackwalker_arm64.h b/src/processor/stackwalker_arm64.h
new file mode 100644
index 00000000..fc7f776b
--- /dev/null
+++ b/src/processor/stackwalker_arm64.h
@@ -0,0 +1,102 @@
+// -*- mode: C++ -*-
+
+// Copyright (c) 2013 Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// stackwalker_arm64.h: arm64-specific stackwalker.
+//
+// Provides stack frames given arm64 register context and a memory region
+// corresponding to an arm64 stack.
+//
+// Author: Mark Mentovai, Ted Mielczarek, Colin Blundell
+
+
+#ifndef PROCESSOR_STACKWALKER_ARM64_H__
+#define PROCESSOR_STACKWALKER_ARM64_H__
+
+#include "google_breakpad/common/breakpad_types.h"
+#include "google_breakpad/common/minidump_format.h"
+#include "google_breakpad/processor/stackwalker.h"
+
+namespace google_breakpad {
+
+class CodeModules;
+
+class StackwalkerARM64 : public Stackwalker {
+ public:
+ // context is an arm64 context object that gives access to arm64-specific
+ // register state corresponding to the innermost called frame to be
+ // included in the stack. The other arguments are passed directly through
+ // to the base Stackwalker constructor.
+ StackwalkerARM64(const SystemInfo* system_info,
+ const MDRawContextARM64* context,
+ MemoryRegion* memory,
+ const CodeModules* modules,
+ StackFrameSymbolizer* frame_symbolizer);
+
+ // Change the context validity mask of the frame returned by
+ // GetContextFrame to VALID. This is only for use by unit tests; the
+ // default behavior is correct for all application code.
+ void SetContextFrameValidity(int valid) { context_frame_validity_ = valid; }
+
+ private:
+ // Implementation of Stackwalker, using arm64 context and stack conventions.
+ virtual StackFrame* GetContextFrame();
+ virtual StackFrame* GetCallerFrame(const CallStack* stack,
+ bool stack_scan_allowed);
+
+ // Use cfi_frame_info (derived from STACK CFI records) to construct
+ // the frame that called frames.back(). The caller takes ownership
+ // of the returned frame. Return NULL on failure.
+ StackFrameARM64* GetCallerByCFIFrameInfo(const vector<StackFrame*> &frames,
+ CFIFrameInfo* cfi_frame_info);
+
+ // Use the frame pointer. The caller takes ownership of the returned frame.
+ // Return NULL on failure.
+ StackFrameARM64* GetCallerByFramePointer(const vector<StackFrame*> &frames);
+
+ // Scan the stack for plausible return addresses. The caller takes ownership
+ // of the returned frame. Return NULL on failure.
+ StackFrameARM64* GetCallerByStackScan(const vector<StackFrame*> &frames);
+
+ // Stores the CPU context corresponding to the youngest stack frame, to
+ // be returned by GetContextFrame.
+ const MDRawContextARM64* context_;
+
+ // Validity mask for youngest stack frame. This is always
+ // CONTEXT_VALID_ALL in real use; it is only changeable for the sake of
+ // unit tests.
+ uint64_t context_frame_validity_;
+};
+
+
+} // namespace google_breakpad
+
+
+#endif // PROCESSOR_STACKWALKER_ARM64_H__
diff --git a/src/processor/stackwalker_arm64_unittest.cc b/src/processor/stackwalker_arm64_unittest.cc
new file mode 100644
index 00000000..1502c8f3
--- /dev/null
+++ b/src/processor/stackwalker_arm64_unittest.cc
@@ -0,0 +1,536 @@
+// Copyright (c) 2010, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
+
+// stackwalker_arm64_unittest.cc: Unit tests for StackwalkerARM64 class.
+
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include "breakpad_googletest_includes.h"
+#include "common/test_assembler.h"
+#include "common/using_std_string.h"
+#include "google_breakpad/common/minidump_format.h"
+#include "google_breakpad/processor/basic_source_line_resolver.h"
+#include "google_breakpad/processor/call_stack.h"
+#include "google_breakpad/processor/code_module.h"
+#include "google_breakpad/processor/source_line_resolver_interface.h"
+#include "google_breakpad/processor/stack_frame_cpu.h"
+#include "processor/stackwalker_unittest_utils.h"
+#include "processor/stackwalker_arm64.h"
+#include "processor/windows_frame_info.h"
+
+using google_breakpad::BasicSourceLineResolver;
+using google_breakpad::CallStack;
+using google_breakpad::CodeModule;
+using google_breakpad::StackFrameSymbolizer;
+using google_breakpad::StackFrame;
+using google_breakpad::StackFrameARM64;
+using google_breakpad::Stackwalker;
+using google_breakpad::StackwalkerARM64;
+using google_breakpad::SystemInfo;
+using google_breakpad::WindowsFrameInfo;
+using google_breakpad::test_assembler::kLittleEndian;
+using google_breakpad::test_assembler::Label;
+using google_breakpad::test_assembler::Section;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::Test;
+
+class StackwalkerARM64Fixture {
+ public:
+ StackwalkerARM64Fixture()
+ : stack_section(kLittleEndian),
+ // Give the two modules reasonable standard locations and names
+ // for tests to play with.
+ module1(0x40000000, 0x10000, "module1", "version1"),
+ module2(0x50000000, 0x10000, "module2", "version2") {
+ // Identify the system as an iOS system, since that is the only platform
+ // for which ARM64 support is currently enabled.
+ system_info.os = "iOS";
+ system_info.os_short = "ios";
+ system_info.cpu = "arm64";
+ system_info.cpu_info = "";
+
+ // Put distinctive values in the raw CPU context.
+ BrandContext(&raw_context);
+
+ // Create some modules with some stock debugging information.
+ modules.Add(&module1);
+ modules.Add(&module2);
+
+ // By default, none of the modules have symbol info; call
+ // SetModuleSymbols to override this.
+ EXPECT_CALL(supplier, GetCStringSymbolData(_, _, _, _, _))
+ .WillRepeatedly(Return(MockSymbolSupplier::NOT_FOUND));
+
+ // Avoid GMOCK WARNING "Uninteresting mock function call - returning
+ // directly" for FreeSymbolData().
+ EXPECT_CALL(supplier, FreeSymbolData(_)).Times(AnyNumber());
+
+ // Reset max_frames_scanned since it's static.
+ Stackwalker::set_max_frames_scanned(1024);
+ }
+
+ // Set the Breakpad symbol information that supplier should return for
+ // MODULE to INFO.
+ void SetModuleSymbols(MockCodeModule *module, const string &info) {
+ size_t buffer_size;
+ char *buffer = supplier.CopySymbolDataAndOwnTheCopy(info, &buffer_size);
+ EXPECT_CALL(supplier, GetCStringSymbolData(module, &system_info, _, _, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(buffer),
+ SetArgumentPointee<4>(buffer_size),
+ Return(MockSymbolSupplier::FOUND)));
+ }
+
+ // Populate stack_region with the contents of stack_section. Use
+ // stack_section.start() as the region's starting address.
+ void RegionFromSection() {
+ string contents;
+ ASSERT_TRUE(stack_section.GetContents(&contents));
+ stack_region.Init(stack_section.start().Value(), contents);
+ }
+
+ // Fill RAW_CONTEXT with pseudo-random data, for round-trip checking.
+ void BrandContext(MDRawContextARM64 *raw_context) {
+ uint8_t x = 173;
+ for (size_t i = 0; i < sizeof(*raw_context); i++)
+ reinterpret_cast<uint8_t *>(raw_context)[i] = (x += 17);
+ }
+
+ SystemInfo system_info;
+ MDRawContextARM64 raw_context;
+ Section stack_section;
+ MockMemoryRegion stack_region;
+ MockCodeModule module1;
+ MockCodeModule module2;
+ MockCodeModules modules;
+ MockSymbolSupplier supplier;
+ BasicSourceLineResolver resolver;
+ CallStack call_stack;
+ const vector<StackFrame *> *frames;
+};
+
+class SanityCheck: public StackwalkerARM64Fixture, public Test { };
+
+TEST_F(SanityCheck, NoResolver) {
+ // Since the context's frame pointer is garbage, the stack walk will end after
+ // the first frame.
+ StackFrameSymbolizer frame_symbolizer(NULL, NULL);
+ StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
+ &frame_symbolizer);
+ // This should succeed even without a resolver or supplier.
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(0U, modules_without_symbols.size());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(1U, frames->size());
+ StackFrameARM64 *frame = static_cast<StackFrameARM64 *>(frames->at(0));
+ // Check that the values from the original raw context made it
+ // through to the context in the stack frame.
+ EXPECT_EQ(0, memcmp(&raw_context, &frame->context, sizeof(raw_context)));
+}
+
+class GetContextFrame: public StackwalkerARM64Fixture, public Test { };
+
+// The stackwalker should be able to produce the context frame even
+// without stack memory present.
+TEST_F(GetContextFrame, NoStackMemory) {
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context, NULL, &modules,
+ &frame_symbolizer);
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(0U, modules_without_symbols.size());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(1U, frames->size());
+ StackFrameARM64 *frame = static_cast<StackFrameARM64 *>(frames->at(0));
+ // Check that the values from the original raw context made it
+ // through to the context in the stack frame.
+ EXPECT_EQ(0, memcmp(&raw_context, &frame->context, sizeof(raw_context)));
+}
+
+class GetCallerFrame: public StackwalkerARM64Fixture, public Test { };
+
+TEST_F(GetCallerFrame, ScanWithoutSymbols) {
+ // When the stack walker resorts to scanning the stack,
+ // only addresses located within loaded modules are
+ // considered valid return addresses.
+ // Force scanning through three frames to ensure that the
+ // stack pointer is set properly in scan-recovered frames.
+ stack_section.start() = 0x80000000;
+ uint64_t return_address1 = 0x50000100;
+ uint64_t return_address2 = 0x50000900;
+ Label frame1_sp, frame2_sp;
+ stack_section
+ // frame 0
+ .Append(16, 0) // space
+
+ .D64(0x40090000) // junk that's not
+ .D64(0x60000000) // a return address
+
+ .D64(return_address1) // actual return address
+ // frame 1
+ .Mark(&frame1_sp)
+ .Append(16, 0) // space
+
+ .D64(0xF0000000) // more junk
+ .D64(0x0000000D)
+
+ .D64(return_address2) // actual return address
+ // frame 2
+ .Mark(&frame2_sp)
+ .Append(64, 0); // end of stack
+ RegionFromSection();
+
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x40005510;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
+ &frame_symbolizer);
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(2U, modules_without_symbols.size());
+ ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
+ ASSERT_EQ("module2", modules_without_symbols[1]->debug_file());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(3U, frames->size());
+
+ StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
+ ASSERT_EQ(StackFrameARM64::CONTEXT_VALID_ALL,
+ frame0->context_validity);
+ EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
+
+ StackFrameARM64 *frame1 = static_cast<StackFrameARM64 *>(frames->at(1));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame1->context_validity);
+ EXPECT_EQ(return_address1, frame1->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+
+ StackFrameARM64 *frame2 = static_cast<StackFrameARM64 *>(frames->at(2));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame2->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame2->context_validity);
+ EXPECT_EQ(return_address2, frame2->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(frame2_sp.Value(), frame2->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+}
+
+TEST_F(GetCallerFrame, ScanWithFunctionSymbols) {
+ // During stack scanning, if a potential return address
+ // is located within a loaded module that has symbols,
+ // it is only considered a valid return address if it
+ // lies within a function's bounds.
+ stack_section.start() = 0x80000000;
+ uint64_t return_address = 0x50000200;
+ Label frame1_sp;
+
+ stack_section
+ // frame 0
+ .Append(16, 0) // space
+
+ .D64(0x40090000) // junk that's not
+ .D64(0x60000000) // a return address
+
+ .D64(0x40001000) // a couple of plausible addresses
+ .D64(0x5000F000) // that are not within functions
+
+ .D64(return_address) // actual return address
+ // frame 1
+ .Mark(&frame1_sp)
+ .Append(64, 0); // end of stack
+ RegionFromSection();
+
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x40000200;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
+
+ SetModuleSymbols(&module1,
+ // The youngest frame's function.
+ "FUNC 100 400 10 monotreme\n");
+ SetModuleSymbols(&module2,
+ // The calling frame's function.
+ "FUNC 100 400 10 marsupial\n");
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
+ &frame_symbolizer);
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(0U, modules_without_symbols.size());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(2U, frames->size());
+
+ StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
+ ASSERT_EQ(StackFrameARM64::CONTEXT_VALID_ALL,
+ frame0->context_validity);
+ EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
+ EXPECT_EQ("monotreme", frame0->function_name);
+ EXPECT_EQ(0x40000100ULL, frame0->function_base);
+
+ StackFrameARM64 *frame1 = static_cast<StackFrameARM64 *>(frames->at(1));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame1->context_validity);
+ EXPECT_EQ(return_address, frame1->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+ EXPECT_EQ("marsupial", frame1->function_name);
+ EXPECT_EQ(0x50000100ULL, frame1->function_base);
+}
+
+TEST_F(GetCallerFrame, ScanFirstFrame) {
+ // If the stackwalker resorts to stack scanning, it will scan much
+ // farther to find the caller of the context frame.
+ stack_section.start() = 0x80000000;
+ uint64_t return_address1 = 0x50000100;
+ uint64_t return_address2 = 0x50000900;
+ Label frame1_sp, frame2_sp;
+ stack_section
+ // frame 0
+ .Append(32, 0) // space
+
+ .D64(0x40090000) // junk that's not
+ .D64(0x60000000) // a return address
+
+ .Append(96, 0) // more space
+
+ .D64(return_address1) // actual return address
+ // frame 1
+ .Mark(&frame1_sp)
+ .Append(32, 0) // space
+
+ .D64(0xF0000000) // more junk
+ .D64(0x0000000D)
+
+ .Append(256, 0) // more space
+
+ .D64(return_address2) // actual return address
+ // (won't be found)
+ // frame 2
+ .Mark(&frame2_sp)
+ .Append(64, 0); // end of stack
+ RegionFromSection();
+
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x40005510;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
+ &frame_symbolizer);
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(2U, modules_without_symbols.size());
+ ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
+ ASSERT_EQ("module2", modules_without_symbols[1]->debug_file());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(2U, frames->size());
+
+ StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
+ ASSERT_EQ(StackFrameARM64::CONTEXT_VALID_ALL,
+ frame0->context_validity);
+ EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
+
+ StackFrameARM64 *frame1 = static_cast<StackFrameARM64 *>(frames->at(1));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame1->context_validity);
+ EXPECT_EQ(return_address1, frame1->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+}
+
+// Test that set_max_frames_scanned prevents using stack scanning
+// to find caller frames.
+TEST_F(GetCallerFrame, ScanningNotAllowed) {
+ // When the stack walker resorts to scanning the stack,
+ // only addresses located within loaded modules are
+ // considered valid return addresses.
+ stack_section.start() = 0x80000000;
+ uint64_t return_address1 = 0x50000100;
+ uint64_t return_address2 = 0x50000900;
+ Label frame1_sp, frame2_sp;
+ stack_section
+ // frame 0
+ .Append(16, 0) // space
+
+ .D64(0x40090000) // junk that's not
+ .D64(0x60000000) // a return address
+
+ .D64(return_address1) // actual return address
+ // frame 1
+ .Mark(&frame1_sp)
+ .Append(16, 0) // space
+
+ .D64(0xF0000000) // more junk
+ .D64(0x0000000D)
+
+ .D64(return_address2) // actual return address
+ // frame 2
+ .Mark(&frame2_sp)
+ .Append(64, 0); // end of stack
+ RegionFromSection();
+
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x40005510;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
+ &frame_symbolizer);
+ Stackwalker::set_max_frames_scanned(0);
+
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(1U, modules_without_symbols.size());
+ ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(1U, frames->size());
+
+ StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
+ ASSERT_EQ(StackFrameARM64::CONTEXT_VALID_ALL,
+ frame0->context_validity);
+ EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
+}
+
+class GetFramesByFramePointer: public StackwalkerARM64Fixture, public Test { };
+
+TEST_F(GetFramesByFramePointer, OnlyFramePointer) {
+ stack_section.start() = 0x80000000;
+ uint64_t return_address1 = 0x50000100;
+ uint64_t return_address2 = 0x50000900;
+ Label frame1_sp, frame2_sp;
+ Label frame1_fp, frame2_fp;
+ stack_section
+ // frame 0
+ .Append(64, 0) // Whatever values on the stack.
+ .D64(0x0000000D) // junk that's not
+ .D64(0xF0000000) // a return address.
+
+ .Mark(&frame1_fp) // Next fp will point to the next value.
+ .D64(frame2_fp) // Save current frame pointer.
+ .D64(return_address2) // Save current link register.
+ .Mark(&frame1_sp)
+
+ // frame 1
+ .Append(64, 0) // Whatever values on the stack.
+ .D64(0x0000000D) // junk that's not
+ .D64(0xF0000000) // a return address.
+
+ .Mark(&frame2_fp)
+ .D64(0)
+ .D64(0)
+ .Mark(&frame2_sp)
+
+ // frame 2
+ .Append(64, 0) // Whatever values on the stack.
+ .D64(0x0000000D) // junk that's not
+ .D64(0xF0000000); // a return address.
+ RegionFromSection();
+
+
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x40005510;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_LR] = return_address1;
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = frame1_fp.Value();
+ raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerARM64 walker(&system_info, &raw_context,
+ &stack_region, &modules, &frame_symbolizer);
+
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(2U, modules_without_symbols.size());
+ ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
+ ASSERT_EQ("module2", modules_without_symbols[1]->debug_file());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+ ASSERT_EQ(3U, frames->size());
+
+ StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
+ ASSERT_EQ(StackFrameARM64::CONTEXT_VALID_ALL,
+ frame0->context_validity);
+ EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
+
+ StackFrameARM64 *frame1 = static_cast<StackFrameARM64 *>(frames->at(1));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame1->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_LR |
+ StackFrameARM64::CONTEXT_VALID_FP |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame1->context_validity);
+ EXPECT_EQ(return_address1, frame1->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(return_address2, frame1->context.iregs[MD_CONTEXT_ARM64_REG_LR]);
+ EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+ EXPECT_EQ(frame2_fp.Value(),
+ frame1->context.iregs[MD_CONTEXT_ARM64_REG_FP]);
+
+ StackFrameARM64 *frame2 = static_cast<StackFrameARM64 *>(frames->at(2));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame2->trust);
+ ASSERT_EQ((StackFrameARM64::CONTEXT_VALID_PC |
+ StackFrameARM64::CONTEXT_VALID_LR |
+ StackFrameARM64::CONTEXT_VALID_FP |
+ StackFrameARM64::CONTEXT_VALID_SP),
+ frame2->context_validity);
+ EXPECT_EQ(return_address2, frame2->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
+ EXPECT_EQ(0U, frame2->context.iregs[MD_CONTEXT_ARM64_REG_LR]);
+ EXPECT_EQ(frame2_sp.Value(), frame2->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
+ EXPECT_EQ(0U, frame2->context.iregs[MD_CONTEXT_ARM64_REG_FP]);
+}