diff --git a/util/BUILD.gn b/util/BUILD.gn index e8f2dc08..5649aec1 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -388,6 +388,7 @@ static_library("util") { if (crashpad_is_fuchsia) { sources += [ + "misc/capture_context_fuchsia.S", "misc/paths_fuchsia.cc", "net/http_transport_none.cc", "process/process_memory_fuchsia.cc", @@ -454,6 +455,7 @@ source_set("util_test") { "file/filesystem_test.cc", "file/string_file_test.cc", "misc/arraysize_unsafe_test.cc", + "misc/capture_context_test.cc", "misc/capture_context_test_util.h", "misc/clock_test.cc", "misc/from_pointer_cast_test.cc", @@ -488,14 +490,6 @@ source_set("util_test") { "thread/worker_thread_test.cc", ] - if (!crashpad_is_fuchsia) { - sources += [ - # No NativeCPUContext defined for Fuchsia yet. - # https://crashpad.chromium.org/bug/196. - "misc/capture_context_test.cc", - ] - } - if (!crashpad_is_android && !crashpad_is_fuchsia && (!crashpad_is_linux || crashpad_enable_http_transport_libcurl)) { # Android and Fuchsia will each require an HTTPTransport implementation @@ -561,6 +555,10 @@ source_set("util_test") { ] } + if (crashpad_is_fuchsia) { + sources += [ "misc/capture_context_test_util_fuchsia.cc" ] + } + if (crashpad_is_linux || crashpad_is_android || crashpad_is_fuchsia) { sources += [ # TODO: Port to all platforms. diff --git a/util/misc/capture_context.h b/util/misc/capture_context.h index 5c1838a1..73b80580 100644 --- a/util/misc/capture_context.h +++ b/util/misc/capture_context.h @@ -23,6 +23,8 @@ #include #elif defined(OS_LINUX) || defined(OS_ANDROID) #include +#elif defined(OS_FUCHSIA) +#include #endif // OS_MACOSX namespace crashpad { @@ -33,14 +35,10 @@ using NativeCPUContext = x86_thread_state; #endif #elif defined(OS_WIN) using NativeCPUContext = CONTEXT; -#elif defined(OS_LINUX) || defined(OS_ANDROID) +#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) using NativeCPUContext = ucontext_t; #endif // OS_MACOSX -// No NativeCPUContext defined for Fuchsia yet. -// https://crashpad.chromium.org/bug/196. -#if !defined(OS_FUCHSIA) - //! \brief Saves the CPU context. //! //! The CPU context will be captured as accurately and completely as possible, @@ -62,11 +60,11 @@ using NativeCPUContext = ucontext_t; //! register, preventing this fuction from saving the original value of that //! register. This occurs in the following circumstances: //! -//! OS | Architecture | Register -//! ------------|--------------|--------- -//! Win | x86_64 | `%%rcx` -//! macOS/Linux | x86_64 | `%%rdi` -//! Linux | ARM/ARM64 | `r0`/`x0` +//! OS | Architecture | Register +//! --------------------|--------------|--------- +//! Win | x86_64 | `%%rcx` +//! macOS/Linux/Fuchsia | x86_64 | `%%rdi` +//! Linux | ARM/ARM64 | `r0`/`x0` //! //! Additionally, the value `LR` on ARM/ARM64 will be the return address of //! this function. @@ -80,8 +78,6 @@ using NativeCPUContext = ucontext_t; //! \endcode void CaptureContext(NativeCPUContext* cpu_context); -#endif // !OS_FUCHSIA - } // namespace crashpad #endif // CRASHPAD_UTIL_MISC_CAPTURE_CONTEXT_H_ diff --git a/util/misc/capture_context_fuchsia.S b/util/misc/capture_context_fuchsia.S new file mode 100644 index 00000000..c8436ffc --- /dev/null +++ b/util/misc/capture_context_fuchsia.S @@ -0,0 +1,133 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// namespace crashpad { +// void CaptureContext(ucontext_t* context); +// } // namespace crashpad + +#define CAPTURECONTEXT_SYMBOL _ZN8crashpad14CaptureContextEP8ucontext + + .text + .globl CAPTURECONTEXT_SYMBOL +#if defined(__x86_64__) + .balign 16, 0x90 +#elif defined(__aarch64__) + .balign 4, 0x0 +#endif + +CAPTURECONTEXT_SYMBOL: + +#if defined(__x86_64__) + + .cfi_startproc + + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + + // Note that 16-byte stack alignment is not maintained because this function + // does not call out to any other. + + // pushfq first, because some instructions (but probably none used here) + // affect %rflags. %rflags will be in -8(%rbp). + pushfq + + // General-purpose registers whose values haven’t changed can be captured + // directly. + movq %r8, 0x28(%rdi) // context->uc_mcontext.r8 + movq %r9, 0x30(%rdi) // context->uc_mcontext.r9 + movq %r10, 0x38(%rdi) // context->uc_mcontext.r10 + movq %r11, 0x40(%rdi) // context->uc_mcontext.r11 + movq %r12, 0x48(%rdi) // context->uc_mcontext.r12 + movq %r13, 0x50(%rdi) // context->uc_mcontext.r13 + movq %r14, 0x58(%rdi) // context->uc_mcontext.r14 + movq %r15, 0x60(%rdi) // context->uc_mcontext.r15 + + // Because of the calling convention, there’s no way to recover the value of + // the caller’s %rdi as it existed prior to calling this function. This + // function captures a snapshot of the register state at its return, which + // involves %rdi containing a pointer to its first argument. Callers that + // require the value of %rdi prior to calling this function should obtain it + // separately. For example: + // uint64_t rdi; + // asm("movq %%rdi, %0" : "=m"(rdi)); + movq %rdi, 0x68(%rdi) // context->uc_mcontext.rdi + + movq %rsi, 0x70(%rdi) // context->uc_mcontext.rsi + + // Use %r8 as a scratch register now that it has been saved. + // The original %rbp was saved on the stack in this function’s prologue. + movq (%rbp), %r8 + movq %r8, 0x78(%rdi) // context->uc_mcontext.rbp + + // Save the remaining general-purpose registers. + movq %rbx, 0x80(%rdi) // context->uc_mcontext.rbx + movq %rdx, 0x88(%rdi) // context->uc_mcontext.rdx + movq %rax, 0x90(%rdi) // context->uc_mcontext.rax + movq %rcx, 0x98(%rdi) // context->uc_mcontext.rcx + + // %rsp was saved in %rbp in this function’s prologue, but the caller’s %rsp + // is 16 more than this value: 8 for the original %rbp saved on the stack in + // this function’s prologue, and 8 for the return address saved on the stack + // by the call instruction that reached this function. + leaq 16(%rbp), %r8 + movq %r8, 0xa0(%rdi) // context->uc_mcontext.rsp + + // The return address saved on the stack used by the call of this function is + // likely more useful than the current RIP here. + movq 8(%rbp), %r8 + movq %r8, 0xa8(%rdi) // context->uc_mcontext.rip + + // The original %rflags was saved on the stack above. + movq -8(%rbp), %r8 + movq %r8, 0xb0(%rdi) // context->uc_mcontext.eflags + + // Save the segment registers + movw %cs, 0xb8(%rdi) // context->uc_mcontext.cs + movw %gs, 0xba(%rdi) // context->uc_mcontext.gs + movw %fs, 0xbc(%rdi) // context->uc_mcontext.fs + + xorw %ax, %ax + movw %ax, 0xbe(%rdi) // context->uc_mcontext.padding + + // Zero out the remainder of the unused pseudo-registers + xorq %r8, %r8 + movq %r8, 0xc0(%rdi) // context->uc_mcontext.err + movq %r8, 0xc8(%rdi) // context->uc_mcontext.trapno + movq %r8, 0xd0(%rdi) // context->uc_mcontext.oldmask + movq %r8, 0xd8(%rdi) // context->uc_mcontext.cr2 + + // Clean up by restoring clobbered registers, even those considered volatile + // by the ABI, so that the captured context represents the state at this + // function’s exit. + movq 0x90(%rdi), %rax + movq 0x28(%rdi), %r8 + + // TODO(scottmg): save floating-point registers. + + popfq + + popq %rbp + + ret + + .cfi_endproc + +#elif defined(__aarch64__) + + #error TODO implement + +#endif // __x86_64__ diff --git a/util/misc/capture_context_test.cc b/util/misc/capture_context_test.cc index e31883e7..6ce81002 100644 --- a/util/misc/capture_context_test.cc +++ b/util/misc/capture_context_test.cc @@ -26,6 +26,15 @@ namespace crashpad { namespace test { namespace { +#if defined(OS_FUCHSIA) +// Fuchsia uses -fsanitize=safe-stack by default, which splits local variables +// and the call stack into separate regions (see +// https://clang.llvm.org/docs/SafeStack.html). Because this test would like to +// find an approximately valid stack pointer by comparing locals to the +// captured one, disable safe-stack for this function. +__attribute__((no_sanitize("safe-stack"))) +#endif + void TestCaptureContext() { NativeCPUContext context_1; CaptureContext(&context_1); diff --git a/util/misc/capture_context_test_util_fuchsia.cc b/util/misc/capture_context_test_util_fuchsia.cc new file mode 100644 index 00000000..7f9210ed --- /dev/null +++ b/util/misc/capture_context_test_util_fuchsia.cc @@ -0,0 +1,59 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/misc/capture_context_test_util.h" + +#include "base/logging.h" +#include "gtest/gtest.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { +namespace test { + +#if defined(ARCH_CPU_X86_64) +static_assert(offsetof(NativeCPUContext, uc_mcontext) == 0x28, + "unexpected mcontext offset"); +static_assert(offsetof(NativeCPUContext, uc_mcontext.gregs[REG_RSP]) == 0xa0, + "unexpected rsp offset"); +static_assert(offsetof(NativeCPUContext, uc_mcontext.gregs[REG_RIP]) == 0xa8, + "unexpected rip offset"); +#endif // ARCH_CPU_X86_64 + +void SanityCheckContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + EXPECT_EQ(context.uc_mcontext.gregs[REG_RDI], + FromPointerCast(&context)); +#elif defined(ARCH_CPU_ARM64) + EXPECT_EQ(context.uc_mcontext.regs[0], FromPointerCast(&context)); +#endif +} + +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RIP]; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.pc; +#endif +} + +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { +#if defined(ARCH_CPU_X86_64) + return context.uc_mcontext.gregs[REG_RSP]; +#elif defined(ARCH_CPU_ARM64) + return context.uc_mcontext.sp; +#endif +} + +} // namespace test +} // namespace crashpad