// Copyright 2017 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 "snapshot/linux/exception_snapshot_linux.h" #include #include #include #include #include #include #include "base/bit_cast.h" #include "base/cxx17_backports.h" #include "base/macros.h" #include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "snapshot/cpu_architecture.h" #include "snapshot/linux/process_reader_linux.h" #include "snapshot/linux/signal_context.h" #include "sys/syscall.h" #include "test/errors.h" #include "test/linux/fake_ptrace_connection.h" #include "util/linux/address_types.h" #include "util/misc/clock.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" #include "util/synchronization/semaphore.h" namespace crashpad { namespace test { namespace { pid_t gettid() { return syscall(SYS_gettid); } #if defined(ARCH_CPU_X86) struct FxsaveUContext { ucontext_t ucontext; CPUContextX86::Fxsave fxsave; }; using NativeCPUContext = FxsaveUContext; void InitializeContext(NativeCPUContext* context) { context->ucontext.uc_mcontext.gregs[REG_EAX] = 0xabcd1234; context->ucontext.uc_mcontext.fpregs = &context->ucontext.__fpregs_mem; // glibc and bionic use an unsigned long for status, but the kernel treats // status as two uint16_t, with the upper 16 bits called "magic" which, if set // to X86_FXSR_MAGIC, indicate that an fxsave follows. reinterpret_cast(&context->ucontext.__fpregs_mem.status)[1] = X86_FXSR_MAGIC; memset(&context->fxsave, 43, sizeof(context->fxsave)); } void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { EXPECT_EQ(actual.architecture, kCPUArchitectureX86); EXPECT_EQ(actual.x86->eax, bit_cast(expected.ucontext.uc_mcontext.gregs[REG_EAX])); for (unsigned int byte_offset = 0; byte_offset < sizeof(actual.x86->fxsave); ++byte_offset) { SCOPED_TRACE(base::StringPrintf("byte offset = %u\n", byte_offset)); EXPECT_EQ(reinterpret_cast(&actual.x86->fxsave)[byte_offset], reinterpret_cast(&expected.fxsave)[byte_offset]); } } #elif defined(ARCH_CPU_X86_64) using NativeCPUContext = ucontext_t; void InitializeContext(NativeCPUContext* context) { context->uc_mcontext.gregs[REG_RAX] = 0xabcd1234abcd1234; context->uc_mcontext.fpregs = &context->__fpregs_mem; memset(&context->__fpregs_mem, 44, sizeof(context->__fpregs_mem)); } void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { EXPECT_EQ(actual.architecture, kCPUArchitectureX86_64); EXPECT_EQ(actual.x86_64->rax, bit_cast(expected.uc_mcontext.gregs[REG_RAX])); for (unsigned int byte_offset = 0; byte_offset < sizeof(actual.x86_64->fxsave); ++byte_offset) { SCOPED_TRACE(base::StringPrintf("byte offset = %u\n", byte_offset)); EXPECT_EQ( reinterpret_cast(&actual.x86_64->fxsave)[byte_offset], reinterpret_cast(&expected.__fpregs_mem)[byte_offset]); } } #elif defined(ARCH_CPU_ARMEL) // A native ucontext_t on ARM doesn't have enough regspace (yet) to hold all of // the different possible coprocessor contexts at once. However, the ABI allows // it and the native regspace may be expanded in the future. Append some extra // space so this is testable now. struct NativeCPUContext { ucontext_t ucontext; char extra[1024]; }; struct CrunchContext { uint32_t mvdx[16][2]; uint32_t mvax[4][3]; uint32_t dspsc[2]; }; struct IWMMXTContext { uint32_t save[38]; }; struct TestCoprocessorContext { struct { internal::CoprocessorContextHead head; CrunchContext context; } crunch; struct { internal::CoprocessorContextHead head; IWMMXTContext context; } iwmmxt; struct { internal::CoprocessorContextHead head; IWMMXTContext context; } dummy; struct { internal::CoprocessorContextHead head; internal::SignalVFPContext context; } vfp; internal::CoprocessorContextHead terminator; }; void InitializeContext(NativeCPUContext* context) { memset(context, 'x', sizeof(*context)); for (int index = 0; index < (&context->ucontext.uc_mcontext.fault_address - &context->ucontext.uc_mcontext.arm_r0); ++index) { (&context->ucontext.uc_mcontext.arm_r0)[index] = index; } static_assert( sizeof(TestCoprocessorContext) <= sizeof(context->ucontext.uc_regspace) + sizeof(context->extra), "Insufficient context space"); auto test_context = reinterpret_cast(context->ucontext.uc_regspace); test_context->crunch.head.magic = CRUNCH_MAGIC; test_context->crunch.head.size = sizeof(test_context->crunch); memset( &test_context->crunch.context, 'c', sizeof(test_context->crunch.context)); test_context->iwmmxt.head.magic = IWMMXT_MAGIC; test_context->iwmmxt.head.size = sizeof(test_context->iwmmxt); memset( &test_context->iwmmxt.context, 'i', sizeof(test_context->iwmmxt.context)); test_context->dummy.head.magic = DUMMY_MAGIC; test_context->dummy.head.size = sizeof(test_context->dummy); memset( &test_context->dummy.context, 'd', sizeof(test_context->dummy.context)); test_context->vfp.head.magic = VFP_MAGIC; test_context->vfp.head.size = sizeof(test_context->vfp); memset(&test_context->vfp.context, 'v', sizeof(test_context->vfp.context)); for (size_t reg = 0; reg < base::size(test_context->vfp.context.vfp.fpregs); ++reg) { test_context->vfp.context.vfp.fpregs[reg] = reg; } test_context->vfp.context.vfp.fpscr = 42; test_context->terminator.magic = 0; test_context->terminator.size = 0; } void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { EXPECT_EQ(actual.architecture, kCPUArchitectureARM); EXPECT_EQ(memcmp(actual.arm->regs, &expected.ucontext.uc_mcontext.arm_r0, sizeof(actual.arm->regs)), 0); EXPECT_EQ(actual.arm->fp, expected.ucontext.uc_mcontext.arm_fp); EXPECT_EQ(actual.arm->ip, expected.ucontext.uc_mcontext.arm_ip); EXPECT_EQ(actual.arm->sp, expected.ucontext.uc_mcontext.arm_sp); EXPECT_EQ(actual.arm->lr, expected.ucontext.uc_mcontext.arm_lr); EXPECT_EQ(actual.arm->pc, expected.ucontext.uc_mcontext.arm_pc); EXPECT_EQ(actual.arm->cpsr, expected.ucontext.uc_mcontext.arm_cpsr); EXPECT_FALSE(actual.arm->have_fpa_regs); EXPECT_TRUE(actual.arm->have_vfp_regs); auto test_context = reinterpret_cast( expected.ucontext.uc_regspace); EXPECT_EQ(memcmp(actual.arm->vfp_regs.vfp, &test_context->vfp.context.vfp, sizeof(actual.arm->vfp_regs.vfp)), 0); } #elif defined(ARCH_CPU_ARM64) using NativeCPUContext = ucontext_t; struct TestCoprocessorContext { esr_context esr; fpsimd_context fpsimd; _aarch64_ctx terminator; }; void InitializeContext(NativeCPUContext* context) { memset(context, 'x', sizeof(*context)); for (size_t index = 0; index < base::size(context->uc_mcontext.regs); ++index) { context->uc_mcontext.regs[index] = index; } context->uc_mcontext.sp = 1; context->uc_mcontext.pc = 2; context->uc_mcontext.pstate = 3; auto test_context = reinterpret_cast( context->uc_mcontext.__reserved); test_context->esr.head.magic = ESR_MAGIC; test_context->esr.head.size = sizeof(test_context->esr); memset(&test_context->esr.esr, 'e', sizeof(test_context->esr.esr)); test_context->fpsimd.head.magic = FPSIMD_MAGIC; test_context->fpsimd.head.size = sizeof(test_context->fpsimd); test_context->fpsimd.fpsr = 1; test_context->fpsimd.fpcr = 2; for (size_t reg = 0; reg < base::size(test_context->fpsimd.vregs); ++reg) { test_context->fpsimd.vregs[reg] = reg; } test_context->terminator.magic = 0; test_context->terminator.size = 0; } void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { EXPECT_EQ(actual.architecture, kCPUArchitectureARM64); EXPECT_EQ(memcmp(actual.arm64->regs, expected.uc_mcontext.regs, sizeof(actual.arm64->regs)), 0); EXPECT_EQ(actual.arm64->sp, expected.uc_mcontext.sp); EXPECT_EQ(actual.arm64->pc, expected.uc_mcontext.pc); EXPECT_EQ(actual.arm64->spsr, expected.uc_mcontext.pstate); auto test_context = reinterpret_cast( expected.uc_mcontext.__reserved); EXPECT_EQ(actual.arm64->fpsr, test_context->fpsimd.fpsr); EXPECT_EQ(actual.arm64->fpcr, test_context->fpsimd.fpcr); EXPECT_EQ(memcmp(actual.arm64->fpsimd, &test_context->fpsimd.vregs, sizeof(actual.arm64->fpsimd)), 0); } #elif defined(ARCH_CPU_MIPS_FAMILY) using NativeCPUContext = ucontext_t; void InitializeContext(NativeCPUContext* context) { for (size_t reg = 0; reg < base::size(context->uc_mcontext.gregs); ++reg) { context->uc_mcontext.gregs[reg] = reg; } memset(&context->uc_mcontext.fpregs, 44, sizeof(context->uc_mcontext.fpregs)); } void ExpectContext(const CPUContext& actual, const NativeCPUContext& expected) { #if defined(ARCH_CPU_MIPSEL) EXPECT_EQ(actual.architecture, kCPUArchitectureMIPSEL); #define CPU_ARCH_NAME mipsel #elif defined(ARCH_CPU_MIPS64EL) EXPECT_EQ(actual.architecture, kCPUArchitectureMIPS64EL); #define CPU_ARCH_NAME mips64 #endif for (size_t reg = 0; reg < base::size(expected.uc_mcontext.gregs); ++reg) { EXPECT_EQ(actual.CPU_ARCH_NAME->regs[reg], expected.uc_mcontext.gregs[reg]); } EXPECT_EQ(memcmp(&actual.CPU_ARCH_NAME->fpregs, &expected.uc_mcontext.fpregs, sizeof(actual.CPU_ARCH_NAME->fpregs)), 0); #undef CPU_ARCH_NAME } #else #error Port. #endif TEST(ExceptionSnapshotLinux, SelfBasic) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); siginfo_t siginfo; siginfo.si_signo = SIGSEGV; siginfo.si_errno = 42; siginfo.si_code = SEGV_MAPERR; siginfo.si_addr = reinterpret_cast(0xdeadbeef); NativeCPUContext context; InitializeContext(&context); internal::ExceptionSnapshotLinux exception; ASSERT_TRUE(exception.Initialize(&process_reader, FromPointerCast(&siginfo), FromPointerCast(&context), gettid())); EXPECT_EQ(exception.Exception(), static_cast(siginfo.si_signo)); EXPECT_EQ(exception.ExceptionInfo(), static_cast(siginfo.si_code)); EXPECT_EQ(exception.ExceptionAddress(), FromPointerCast(siginfo.si_addr)); ExpectContext(*exception.Context(), context); } class ScopedSigactionRestore { public: ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {} ~ScopedSigactionRestore() { Reset(); } bool Reset() { if (valid_) { int res = sigaction(signo_, &old_action_, nullptr); EXPECT_EQ(res, 0) << ErrnoMessage("sigaction"); if (res != 0) { return false; } } valid_ = false; signo_ = -1; return true; } bool ResetInstallHandler(int signo, Signals::Handler handler) { if (Reset() && Signals::InstallHandler(signo, handler, 0, &old_action_)) { signo_ = signo; valid_ = true; return true; } return false; } private: struct sigaction old_action_; int signo_; bool valid_; DISALLOW_COPY_AND_ASSIGN(ScopedSigactionRestore); }; class RaiseTest { public: static void Run() { test_complete_ = false; ScopedSigactionRestore sigrestore; ASSERT_TRUE(sigrestore.ResetInstallHandler(kSigno, HandleRaisedSignal)); EXPECT_EQ(raise(kSigno), 0) << ErrnoMessage("raise"); EXPECT_TRUE(test_complete_); } private: static void HandleRaisedSignal(int signo, siginfo_t* siginfo, void* context) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); internal::ExceptionSnapshotLinux exception; ASSERT_TRUE(exception.Initialize(&process_reader, FromPointerCast(siginfo), FromPointerCast(context), gettid())); EXPECT_EQ(exception.Exception(), static_cast(kSigno)); EXPECT_EQ(exception.Codes().size(), 3u); EXPECT_EQ(exception.Codes()[0], static_cast(getpid())); EXPECT_EQ(exception.Codes()[1], getuid()); // Codes()[2] is not set by kill, but we still expect to get it because some // interfaces may set it and we don't necessarily know where this signal // came // from. test_complete_ = true; } static constexpr uint32_t kSigno = SIGUSR1; static bool test_complete_; DISALLOW_IMPLICIT_CONSTRUCTORS(RaiseTest); }; bool RaiseTest::test_complete_ = false; TEST(ExceptionSnapshotLinux, Raise) { RaiseTest::Run(); } class TimerTest { public: TimerTest() : event_(), timer_(-1), test_complete_(false) { test_ = this; } ~TimerTest() { test_ = nullptr; } void Run() { ScopedSigactionRestore sigrestore; ASSERT_TRUE(sigrestore.ResetInstallHandler(kSigno, HandleTimer)); event_.sigev_notify = SIGEV_SIGNAL; event_.sigev_signo = kSigno; event_.sigev_value.sival_int = 42; ASSERT_EQ(syscall(SYS_timer_create, CLOCK_MONOTONIC, &event_, &timer_), 0); itimerspec spec; spec.it_interval.tv_sec = 0; spec.it_interval.tv_nsec = 0; spec.it_value.tv_sec = 0; spec.it_value.tv_nsec = 1; ASSERT_EQ(syscall(SYS_timer_settime, timer_, TIMER_ABSTIME, &spec, nullptr), 0); for (size_t attempt = 0; attempt < 3; ++attempt) { SleepNanoseconds(1); if (test_complete_) { return; } } ADD_FAILURE() << "signal not received"; } private: static void HandleTimer(int signo, siginfo_t* siginfo, void* context) { FakePtraceConnection connection; ASSERT_TRUE(connection.Initialize(getpid())); ProcessReaderLinux process_reader; ASSERT_TRUE(process_reader.Initialize(&connection)); internal::ExceptionSnapshotLinux exception; ASSERT_TRUE(exception.Initialize(&process_reader, FromPointerCast(siginfo), FromPointerCast(context), gettid())); EXPECT_EQ(exception.Exception(), static_cast(kSigno)); EXPECT_EQ(exception.Codes().size(), 3u); EXPECT_EQ(exception.Codes()[0], static_cast(test_->timer_)); int overruns = syscall(SYS_timer_getoverrun, test_->timer_); ASSERT_GE(overruns, 0); EXPECT_EQ(exception.Codes()[1], static_cast(overruns)); EXPECT_EQ(exception.Codes()[2], static_cast(test_->event_.sigev_value.sival_int)); test_->test_complete_ = true; } sigevent event_; __kernel_timer_t timer_; volatile bool test_complete_; static constexpr uint32_t kSigno = SIGALRM; static TimerTest* test_; DISALLOW_COPY_AND_ASSIGN(TimerTest); }; TimerTest* TimerTest::test_; TEST(ExceptionSnapshotLinux, SelfTimer) { TimerTest test; test.Run(); } } // namespace } // namespace test } // namespace crashpad