1 | /* |
2 | * Copyright (C) 2017-2018 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "SigillCrashAnalyzer.h" |
28 | |
29 | #include "CallFrame.h" |
30 | #include "CodeBlock.h" |
31 | #include "MachineContext.h" |
32 | #include "VMInspector.h" |
33 | #include <mutex> |
34 | #include <wtf/StdLibExtras.h> |
35 | |
36 | #if USE(ARM64_DISASSEMBLER) |
37 | #include "A64DOpcode.h" |
38 | #endif |
39 | |
40 | #include <wtf/threads/Signals.h> |
41 | |
42 | namespace JSC { |
43 | |
44 | struct SignalContext; |
45 | |
46 | class SigillCrashAnalyzer { |
47 | WTF_MAKE_FAST_ALLOCATED; |
48 | WTF_MAKE_NONCOPYABLE(SigillCrashAnalyzer); |
49 | public: |
50 | static SigillCrashAnalyzer& instance(); |
51 | |
52 | enum class CrashSource { |
53 | Unknown, |
54 | JavaScriptCore, |
55 | Other, |
56 | }; |
57 | CrashSource analyze(SignalContext&); |
58 | |
59 | private: |
60 | SigillCrashAnalyzer() { } |
61 | void dumpCodeBlock(CodeBlock*, void* machinePC); |
62 | |
63 | #if USE(ARM64_DISASSEMBLER) |
64 | A64DOpcode m_arm64Opcode; |
65 | #endif |
66 | }; |
67 | |
68 | #if OS(DARWIN) |
69 | |
70 | #if USE(OS_LOG) |
71 | |
72 | #define log(format, ...) \ |
73 | os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__) |
74 | |
75 | #else // USE(OS_LOG) |
76 | |
77 | #define log(format, ...) \ |
78 | dataLogF(format, ##__VA_ARGS__) |
79 | |
80 | #endif // USE(OS_LOG) |
81 | |
82 | struct SignalContext { |
83 | private: |
84 | SignalContext(PlatformRegisters& registers, MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC) |
85 | : registers(registers) |
86 | , machinePC(machinePC) |
87 | , stackPointer(MachineContext::stackPointer(registers)) |
88 | , framePointer(MachineContext::framePointer(registers)) |
89 | { } |
90 | |
91 | public: |
92 | static Optional<SignalContext> tryCreate(PlatformRegisters& registers) |
93 | { |
94 | auto instructionPointer = MachineContext::instructionPointer(registers); |
95 | if (!instructionPointer) |
96 | return WTF::nullopt; |
97 | return SignalContext(registers, *instructionPointer); |
98 | } |
99 | |
100 | void dump() |
101 | { |
102 | #if CPU(X86_64) |
103 | #define FOR_EACH_REGISTER(v) \ |
104 | v(rax) \ |
105 | v(rbx) \ |
106 | v(rcx) \ |
107 | v(rdx) \ |
108 | v(rdi) \ |
109 | v(rsi) \ |
110 | v(rbp) \ |
111 | v(rsp) \ |
112 | v(r8) \ |
113 | v(r9) \ |
114 | v(r10) \ |
115 | v(r11) \ |
116 | v(r12) \ |
117 | v(r13) \ |
118 | v(r14) \ |
119 | v(r15) \ |
120 | v(rip) \ |
121 | v(rflags) \ |
122 | v(cs) \ |
123 | v(fs) \ |
124 | v(gs) |
125 | |
126 | #define DUMP_REGISTER(__reg) \ |
127 | log("Register " #__reg ": %p", reinterpret_cast<void*>(registers.__##__reg)); |
128 | FOR_EACH_REGISTER(DUMP_REGISTER) |
129 | #undef FOR_EACH_REGISTER |
130 | |
131 | #elif CPU(ARM64) && defined(__LP64__) |
132 | int i; |
133 | for (i = 0; i < 28; i += 4) { |
134 | log("x%d: %016llx x%d: %016llx x%d: %016llx x%d: %016llx" , |
135 | i, registers.__x[i], |
136 | i+1, registers.__x[i+1], |
137 | i+2, registers.__x[i+2], |
138 | i+3, registers.__x[i+3]); |
139 | } |
140 | ASSERT(i < 29); |
141 | log("x%d: %016llx fp: %016llx lr: %016llx" , |
142 | i, registers.__x[i], |
143 | MachineContext::framePointer<uint64_t>(registers), |
144 | MachineContext::linkRegister(registers).untaggedExecutableAddress<uint64_t>()); |
145 | log("sp: %016llx pc: %016llx cpsr: %08x" , |
146 | MachineContext::stackPointer<uint64_t>(registers), |
147 | machinePC.untaggedExecutableAddress<uint64_t>(), |
148 | registers.__cpsr); |
149 | #endif |
150 | } |
151 | |
152 | PlatformRegisters& registers; |
153 | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; |
154 | void* stackPointer; |
155 | void* framePointer; |
156 | }; |
157 | |
158 | static void installCrashHandler() |
159 | { |
160 | #if CPU(X86_64) || CPU(ARM64) |
161 | installSignalHandler(Signal::Ill, [] (Signal, SigInfo&, PlatformRegisters& registers) { |
162 | auto signalContext = SignalContext::tryCreate(registers); |
163 | if (!signalContext) |
164 | return SignalAction::NotHandled; |
165 | |
166 | void* machinePC = signalContext->machinePC.untaggedExecutableAddress(); |
167 | if (!isJITPC(machinePC)) |
168 | return SignalAction::NotHandled; |
169 | |
170 | SigillCrashAnalyzer& analyzer = SigillCrashAnalyzer::instance(); |
171 | analyzer.analyze(*signalContext); |
172 | return SignalAction::NotHandled; |
173 | }); |
174 | #endif |
175 | } |
176 | |
177 | #else // OS(DARWIN) |
178 | |
179 | #define log(format, ...) do { } while (false) |
180 | |
181 | struct SignalContext { |
182 | SignalContext() { } |
183 | |
184 | void dump() { } |
185 | |
186 | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; |
187 | void* stackPointer; |
188 | void* framePointer; |
189 | }; |
190 | |
191 | static void installCrashHandler() |
192 | { |
193 | // Do nothing. Not supported for this platform. |
194 | } |
195 | |
196 | #endif // OS(DARWIN) |
197 | |
198 | SigillCrashAnalyzer& SigillCrashAnalyzer::instance() |
199 | { |
200 | static SigillCrashAnalyzer* analyzer; |
201 | static std::once_flag once; |
202 | std::call_once(once, [] { |
203 | installCrashHandler(); |
204 | analyzer = new SigillCrashAnalyzer; |
205 | }); |
206 | return *analyzer; |
207 | } |
208 | |
209 | void enableSigillCrashAnalyzer() |
210 | { |
211 | // Just instantiating the SigillCrashAnalyzer will enable it. |
212 | SigillCrashAnalyzer::instance(); |
213 | } |
214 | |
215 | auto SigillCrashAnalyzer::analyze(SignalContext& context) -> CrashSource |
216 | { |
217 | CrashSource crashSource = CrashSource::Unknown; |
218 | log("BEGIN SIGILL analysis" ); |
219 | |
220 | do { |
221 | // First, dump the signal context info so that we'll at least have the same info |
222 | // that the default crash handler would given us in case this crash analyzer |
223 | // itself crashes. |
224 | context.dump(); |
225 | |
226 | VMInspector& inspector = VMInspector::instance(); |
227 | |
228 | // Use a timeout period of 2 seconds. The client is about to crash, and we don't |
229 | // want to turn the crash into a hang by re-trying the lock for too long. |
230 | auto expectedLocker = inspector.lock(Seconds(2)); |
231 | if (!expectedLocker) { |
232 | ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut); |
233 | log("ERROR: Unable to analyze SIGILL. Timed out while waiting to iterate VMs." ); |
234 | break; |
235 | } |
236 | auto& locker = expectedLocker.value(); |
237 | |
238 | void* pc = context.machinePC.untaggedExecutableAddress(); |
239 | auto isInJITMemory = inspector.isValidExecutableMemory(locker, pc); |
240 | if (!isInJITMemory) { |
241 | log("ERROR: Timed out: not able to determine if pc %p is in valid JIT executable memory" , pc); |
242 | break; |
243 | } |
244 | if (!isInJITMemory.value()) { |
245 | log("pc %p is NOT in valid JIT executable memory" , pc); |
246 | crashSource = CrashSource::Other; |
247 | break; |
248 | } |
249 | log("pc %p is in valid JIT executable memory" , pc); |
250 | crashSource = CrashSource::JavaScriptCore; |
251 | |
252 | #if CPU(ARM64) |
253 | size_t pcAsSize = reinterpret_cast<size_t>(pc); |
254 | if (pcAsSize != roundUpToMultipleOf<sizeof(uint32_t)>(pcAsSize)) { |
255 | log("pc %p is NOT properly aligned" , pc); |
256 | break; |
257 | } |
258 | |
259 | // We know it's safe to read the word at the PC because we're handling a SIGILL. |
260 | // Otherwise, we would have crashed with a SIGBUS instead. |
261 | uint32_t wordAtPC = *reinterpret_cast<uint32_t*>(pc); |
262 | log("instruction bits at pc %p is: 0x%08x" , pc, wordAtPC); |
263 | #endif |
264 | |
265 | auto expectedCodeBlock = inspector.codeBlockForMachinePC(locker, pc); |
266 | if (!expectedCodeBlock) { |
267 | if (expectedCodeBlock.error() == VMInspector::Error::TimedOut) |
268 | log("ERROR: Timed out: not able to determine if pc %p is in a valid CodeBlock" , pc); |
269 | else |
270 | log("The current thread does not own any VM JSLock" ); |
271 | break; |
272 | } |
273 | CodeBlock* codeBlock = expectedCodeBlock.value(); |
274 | if (!codeBlock) { |
275 | log("machine PC %p does not belong to any CodeBlock in the currently entered VM" , pc); |
276 | break; |
277 | } |
278 | |
279 | log("pc %p belongs to CodeBlock %p of type %s" , pc, codeBlock, JITCode::typeName(codeBlock->jitType())); |
280 | |
281 | dumpCodeBlock(codeBlock, pc); |
282 | } while (false); |
283 | |
284 | log("END SIGILL analysis" ); |
285 | return crashSource; |
286 | } |
287 | |
288 | void SigillCrashAnalyzer::dumpCodeBlock(CodeBlock* codeBlock, void* machinePC) |
289 | { |
290 | #if CPU(ARM64) && ENABLE(JIT) |
291 | JITCode* jitCode = codeBlock->jitCode().get(); |
292 | |
293 | // Dump the raw bits of the code. |
294 | uint32_t* start = reinterpret_cast<uint32_t*>(jitCode->start()); |
295 | uint32_t* end = reinterpret_cast<uint32_t*>(jitCode->end()); |
296 | log("JITCode %p [%p-%p]:" , jitCode, start, end); |
297 | if (start < end) { |
298 | uint32_t* p = start; |
299 | while (p + 8 <= end) { |
300 | 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]); |
301 | p += 8; |
302 | } |
303 | if (p + 7 <= end) |
304 | 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]); |
305 | else if (p + 6 <= end) |
306 | log("[%p-%p]: %08x %08x %08x %08x %08x %08x" , p, p+5, p[0], p[1], p[2], p[3], p[4], p[5]); |
307 | else if (p + 5 <= end) |
308 | log("[%p-%p]: %08x %08x %08x %08x %08x" , p, p+4, p[0], p[1], p[2], p[3], p[4]); |
309 | else if (p + 4 <= end) |
310 | log("[%p-%p]: %08x %08x %08x %08x" , p, p+3, p[0], p[1], p[2], p[3]); |
311 | if (p + 3 <= end) |
312 | log("[%p-%p]: %08x %08x %08x" , p, p+2, p[0], p[1], p[2]); |
313 | else if (p + 2 <= end) |
314 | log("[%p-%p]: %08x %08x" , p, p+1, p[0], p[1]); |
315 | else if (p + 1 <= end) |
316 | log("[%p-%p]: %08x" , p, p, p[0]); |
317 | } |
318 | |
319 | // Dump the disassembly of the code. |
320 | log("Disassembly:" ); |
321 | uint32_t* currentPC = reinterpret_cast<uint32_t*>(jitCode->executableAddress()); |
322 | size_t byteCount = jitCode->size(); |
323 | while (byteCount) { |
324 | char pcString[24]; |
325 | if (currentPC == machinePC) { |
326 | snprintf(pcString, sizeof(pcString), "* 0x%lx" , reinterpret_cast<uintptr_t>(currentPC)); |
327 | log("%20s: %s <=========================" , pcString, m_arm64Opcode.disassemble(currentPC)); |
328 | } else { |
329 | snprintf(pcString, sizeof(pcString), "0x%lx" , reinterpret_cast<uintptr_t>(currentPC)); |
330 | log("%20s: %s" , pcString, m_arm64Opcode.disassemble(currentPC)); |
331 | } |
332 | currentPC++; |
333 | byteCount -= sizeof(uint32_t); |
334 | } |
335 | #else |
336 | UNUSED_PARAM(codeBlock); |
337 | UNUSED_PARAM(machinePC); |
338 | // Not implemented yet. |
339 | #endif |
340 | } |
341 | |
342 | } // namespace JSC |
343 | |