|  | /* | 
|  | * Copyright (C) 2017-2018 Apple 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: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. 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. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "SigillCrashAnalyzer.h" | 
|  |  | 
|  | #include "CallFrame.h" | 
|  | #include "CodeBlock.h" | 
|  | #include "MachineContext.h" | 
|  | #include "VMInspector.h" | 
|  | #include <mutex> | 
|  | #include <wtf/StdLibExtras.h> | 
|  |  | 
|  | #if USE(ARM64_DISASSEMBLER) | 
|  | #include "A64DOpcode.h" | 
|  | #endif | 
|  |  | 
|  | #include <wtf/threads/Signals.h> | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | struct SignalContext; | 
|  |  | 
|  | class SigillCrashAnalyzer { | 
|  | public: | 
|  | static SigillCrashAnalyzer& instance(); | 
|  |  | 
|  | enum class CrashSource { | 
|  | Unknown, | 
|  | JavaScriptCore, | 
|  | Other, | 
|  | }; | 
|  | CrashSource analyze(SignalContext&); | 
|  |  | 
|  | private: | 
|  | SigillCrashAnalyzer() { } | 
|  | void dumpCodeBlock(CodeBlock*, void* machinePC); | 
|  |  | 
|  | #if USE(ARM64_DISASSEMBLER) | 
|  | A64DOpcode m_arm64Opcode; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | #if OS(DARWIN) | 
|  |  | 
|  | #if USE(OS_LOG) | 
|  |  | 
|  | #define log(format, ...) \ | 
|  | os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__) | 
|  |  | 
|  | #else // USE(OS_LOG) | 
|  |  | 
|  | #define log(format, ...) \ | 
|  | dataLogF(format, ##__VA_ARGS__) | 
|  |  | 
|  | #endif // USE(OS_LOG) | 
|  |  | 
|  | struct SignalContext { | 
|  | private: | 
|  | SignalContext(PlatformRegisters& registers, MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC) | 
|  | : registers(registers) | 
|  | , machinePC(machinePC) | 
|  | , stackPointer(MachineContext::stackPointer(registers)) | 
|  | , framePointer(MachineContext::framePointer(registers)) | 
|  | { } | 
|  |  | 
|  | public: | 
|  | static Optional<SignalContext> tryCreate(PlatformRegisters& registers) | 
|  | { | 
|  | auto instructionPointer = MachineContext::instructionPointer(registers); | 
|  | if (!instructionPointer) | 
|  | return WTF::nullopt; | 
|  | return SignalContext(registers, *instructionPointer); | 
|  | } | 
|  |  | 
|  | void dump() | 
|  | { | 
|  | #if CPU(X86_64) | 
|  | #define FOR_EACH_REGISTER(v) \ | 
|  | v(rax) \ | 
|  | v(rbx) \ | 
|  | v(rcx) \ | 
|  | v(rdx) \ | 
|  | v(rdi) \ | 
|  | v(rsi) \ | 
|  | v(rbp) \ | 
|  | v(rsp) \ | 
|  | v(r8) \ | 
|  | v(r9) \ | 
|  | v(r10) \ | 
|  | v(r11) \ | 
|  | v(r12) \ | 
|  | v(r13) \ | 
|  | v(r14) \ | 
|  | v(r15) \ | 
|  | v(rip) \ | 
|  | v(rflags) \ | 
|  | v(cs) \ | 
|  | v(fs) \ | 
|  | v(gs) | 
|  |  | 
|  | #define DUMP_REGISTER(__reg) \ | 
|  | log("Register " #__reg ": %p", reinterpret_cast<void*>(registers.__##__reg)); | 
|  | FOR_EACH_REGISTER(DUMP_REGISTER) | 
|  | #undef FOR_EACH_REGISTER | 
|  |  | 
|  | #elif CPU(ARM64) && defined(__LP64__) | 
|  | int i; | 
|  | for (i = 0; i < 28; i += 4) { | 
|  | log("x%d: %016llx x%d: %016llx x%d: %016llx x%d: %016llx", | 
|  | i, registers.__x[i], | 
|  | i+1, registers.__x[i+1], | 
|  | i+2, registers.__x[i+2], | 
|  | i+3, registers.__x[i+3]); | 
|  | } | 
|  | ASSERT(i < 29); | 
|  | log("x%d: %016llx fp: %016llx lr: %016llx", | 
|  | i, registers.__x[i], | 
|  | MachineContext::framePointer<uint64_t>(registers), | 
|  | MachineContext::linkRegister(registers).untaggedExecutableAddress<uint64_t>()); | 
|  | log("sp: %016llx pc: %016llx cpsr: %08x", | 
|  | MachineContext::stackPointer<uint64_t>(registers), | 
|  | machinePC.untaggedExecutableAddress<uint64_t>(), | 
|  | registers.__cpsr); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | PlatformRegisters& registers; | 
|  | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; | 
|  | void* stackPointer; | 
|  | void* framePointer; | 
|  | }; | 
|  |  | 
|  | static void installCrashHandler() | 
|  | { | 
|  | #if CPU(X86_64) || CPU(ARM64) | 
|  | installSignalHandler(Signal::Ill, [] (Signal, SigInfo&, PlatformRegisters& registers) { | 
|  | auto signalContext = SignalContext::tryCreate(registers); | 
|  | if (!signalContext) | 
|  | return SignalAction::NotHandled; | 
|  |  | 
|  | void* machinePC = signalContext->machinePC.untaggedExecutableAddress(); | 
|  | if (!isJITPC(machinePC)) | 
|  | return SignalAction::NotHandled; | 
|  |  | 
|  | SigillCrashAnalyzer& analyzer = SigillCrashAnalyzer::instance(); | 
|  | analyzer.analyze(*signalContext); | 
|  | return SignalAction::NotHandled; | 
|  | }); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #else // OS(DARWIN) | 
|  |  | 
|  | #define log(format, ...) do { } while (false) | 
|  |  | 
|  | struct SignalContext { | 
|  | SignalContext() { } | 
|  |  | 
|  | void dump() { } | 
|  |  | 
|  | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; | 
|  | void* stackPointer; | 
|  | void* framePointer; | 
|  | }; | 
|  |  | 
|  | static void installCrashHandler() | 
|  | { | 
|  | // Do nothing. Not supported for this platform. | 
|  | } | 
|  |  | 
|  | #endif // OS(DARWIN) | 
|  |  | 
|  | SigillCrashAnalyzer& SigillCrashAnalyzer::instance() | 
|  | { | 
|  | static SigillCrashAnalyzer* analyzer; | 
|  | static std::once_flag once; | 
|  | std::call_once(once, [] { | 
|  | installCrashHandler(); | 
|  | analyzer = new SigillCrashAnalyzer; | 
|  | }); | 
|  | return *analyzer; | 
|  | } | 
|  |  | 
|  | void enableSigillCrashAnalyzer() | 
|  | { | 
|  | // Just instantiating the SigillCrashAnalyzer will enable it. | 
|  | SigillCrashAnalyzer::instance(); | 
|  | } | 
|  |  | 
|  | auto SigillCrashAnalyzer::analyze(SignalContext& context) -> CrashSource | 
|  | { | 
|  | CrashSource crashSource = CrashSource::Unknown; | 
|  | log("BEGIN SIGILL analysis"); | 
|  |  | 
|  | do { | 
|  | // First, dump the signal context info so that we'll at least have the same info | 
|  | // that the default crash handler would given us in case this crash analyzer | 
|  | // itself crashes. | 
|  | context.dump(); | 
|  |  | 
|  | VMInspector& inspector = VMInspector::instance(); | 
|  |  | 
|  | // Use a timeout period of 2 seconds. The client is about to crash, and we don't | 
|  | // want to turn the crash into a hang by re-trying the lock for too long. | 
|  | auto expectedLocker = inspector.lock(Seconds(2)); | 
|  | if (!expectedLocker) { | 
|  | ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut); | 
|  | log("ERROR: Unable to analyze SIGILL. Timed out while waiting to iterate VMs."); | 
|  | break; | 
|  | } | 
|  | auto& locker = expectedLocker.value(); | 
|  |  | 
|  | void* pc = context.machinePC.untaggedExecutableAddress(); | 
|  | auto isInJITMemory = inspector.isValidExecutableMemory(locker, pc); | 
|  | if (!isInJITMemory) { | 
|  | log("ERROR: Timed out: not able to determine if pc %p is in valid JIT executable memory", pc); | 
|  | break; | 
|  | } | 
|  | if (!isInJITMemory.value()) { | 
|  | log("pc %p is NOT in valid JIT executable memory", pc); | 
|  | crashSource = CrashSource::Other; | 
|  | break; | 
|  | } | 
|  | log("pc %p is in valid JIT executable memory", pc); | 
|  | crashSource = CrashSource::JavaScriptCore; | 
|  |  | 
|  | #if CPU(ARM64) | 
|  | size_t pcAsSize = reinterpret_cast<size_t>(pc); | 
|  | if (pcAsSize != roundUpToMultipleOf<sizeof(uint32_t)>(pcAsSize)) { | 
|  | log("pc %p is NOT properly aligned", pc); | 
|  | break; | 
|  | } | 
|  |  | 
|  | // We know it's safe to read the word at the PC because we're handling a SIGILL. | 
|  | // Otherwise, we would have crashed with a SIGBUS instead. | 
|  | uint32_t wordAtPC = *reinterpret_cast<uint32_t*>(pc); | 
|  | log("instruction bits at pc %p is: 0x%08x", pc, wordAtPC); | 
|  | #endif | 
|  |  | 
|  | auto expectedCodeBlock = inspector.codeBlockForMachinePC(locker, pc); | 
|  | if (!expectedCodeBlock) { | 
|  | if (expectedCodeBlock.error() == VMInspector::Error::TimedOut) | 
|  | log("ERROR: Timed out: not able to determine if pc %p is in a valid CodeBlock", pc); | 
|  | else | 
|  | log("The current thread does not own any VM JSLock"); | 
|  | break; | 
|  | } | 
|  | CodeBlock* codeBlock = expectedCodeBlock.value(); | 
|  | if (!codeBlock) { | 
|  | log("machine PC %p does not belong to any CodeBlock in the currently entered VM", pc); | 
|  | break; | 
|  | } | 
|  |  | 
|  | log("pc %p belongs to CodeBlock %p of type %s", pc, codeBlock, JITCode::typeName(codeBlock->jitType())); | 
|  |  | 
|  | dumpCodeBlock(codeBlock, pc); | 
|  | } while (false); | 
|  |  | 
|  | log("END SIGILL analysis"); | 
|  | return crashSource; | 
|  | } | 
|  |  | 
|  | void SigillCrashAnalyzer::dumpCodeBlock(CodeBlock* codeBlock, void* machinePC) | 
|  | { | 
|  | #if CPU(ARM64) && ENABLE(JIT) | 
|  | JITCode* jitCode = codeBlock->jitCode().get(); | 
|  |  | 
|  | // Dump the raw bits of the code. | 
|  | uint32_t* start = reinterpret_cast<uint32_t*>(jitCode->start()); | 
|  | uint32_t* end = reinterpret_cast<uint32_t*>(jitCode->end()); | 
|  | log("JITCode %p [%p-%p]:", jitCode, start, end); | 
|  | if (start < end) { | 
|  | uint32_t* p = start; | 
|  | while (p + 8 <= end) { | 
|  | log("[%p-%p]: %08x %08x %08x %08x %08x %08x %08x %08x", p, p+7, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); | 
|  | p += 8; | 
|  | } | 
|  | if (p + 7 <= end) | 
|  | log("[%p-%p]: %08x %08x %08x %08x %08x %08x %08x", p, p+6, p[0], p[1], p[2], p[3], p[4], p[5], p[6]); | 
|  | else if (p + 6 <= end) | 
|  | log("[%p-%p]: %08x %08x %08x %08x %08x %08x", p, p+5, p[0], p[1], p[2], p[3], p[4], p[5]); | 
|  | else if (p + 5 <= end) | 
|  | log("[%p-%p]: %08x %08x %08x %08x %08x", p, p+4, p[0], p[1], p[2], p[3], p[4]); | 
|  | else if (p + 4 <= end) | 
|  | log("[%p-%p]: %08x %08x %08x %08x", p, p+3, p[0], p[1], p[2], p[3]); | 
|  | if (p + 3 <= end) | 
|  | log("[%p-%p]: %08x %08x %08x", p, p+2, p[0], p[1], p[2]); | 
|  | else if (p + 2 <= end) | 
|  | log("[%p-%p]: %08x %08x", p, p+1, p[0], p[1]); | 
|  | else if (p + 1 <= end) | 
|  | log("[%p-%p]: %08x", p, p, p[0]); | 
|  | } | 
|  |  | 
|  | // Dump the disassembly of the code. | 
|  | log("Disassembly:"); | 
|  | uint32_t* currentPC = reinterpret_cast<uint32_t*>(jitCode->executableAddress()); | 
|  | size_t byteCount = jitCode->size(); | 
|  | while (byteCount) { | 
|  | char pcString[24]; | 
|  | if (currentPC == machinePC) { | 
|  | snprintf(pcString, sizeof(pcString), "* 0x%lx", reinterpret_cast<uintptr_t>(currentPC)); | 
|  | log("%20s: %s    <=========================", pcString, m_arm64Opcode.disassemble(currentPC)); | 
|  | } else { | 
|  | snprintf(pcString, sizeof(pcString), "0x%lx", reinterpret_cast<uintptr_t>(currentPC)); | 
|  | log("%20s: %s", pcString, m_arm64Opcode.disassemble(currentPC)); | 
|  | } | 
|  | currentPC++; | 
|  | byteCount -= sizeof(uint32_t); | 
|  | } | 
|  | #else | 
|  | UNUSED_PARAM(codeBlock); | 
|  | UNUSED_PARAM(machinePC); | 
|  | // Not implemented yet. | 
|  | #endif | 
|  | } | 
|  |  | 
|  | } // namespace JSC |