diff options
Diffstat (limited to 'src/processor')
-rwxr-xr-x | src/processor/minidump.cc | 191 | ||||
-rw-r--r-- | src/processor/minidump_processor.cc | 15 | ||||
-rw-r--r-- | src/processor/minidump_stackwalk.cc | 141 | ||||
-rw-r--r-- | src/processor/stack_frame_cpu.cc | 79 | ||||
-rw-r--r-- | src/processor/stackwalker.cc | 10 | ||||
-rw-r--r-- | src/processor/stackwalker_arm64.cc | 209 | ||||
-rw-r--r-- | src/processor/stackwalker_arm64.h | 102 | ||||
-rw-r--r-- | src/processor/stackwalker_arm64_unittest.cc | 536 |
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]); +} |