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 | public: |
48 | static SigillCrashAnalyzer& instance(); |
49 | |
50 | enum class CrashSource { |
51 | Unknown, |
52 | JavaScriptCore, |
53 | Other, |
54 | }; |
55 | CrashSource analyze(SignalContext&); |
56 | |
57 | private: |
58 | SigillCrashAnalyzer() { } |
59 | void dumpCodeBlock(CodeBlock*, void* machinePC); |
60 | |
61 | #if USE(ARM64_DISASSEMBLER) |
62 | A64DOpcode m_arm64Opcode; |
63 | #endif |
64 | }; |
65 | |
66 | #if OS(DARWIN) |
67 | |
68 | #if USE(OS_LOG) |
69 | |
70 | #define log(format, ...) \ |
71 | os_log_info(OS_LOG_DEFAULT, format, ##__VA_ARGS__) |
72 | |
73 | #else // USE(OS_LOG) |
74 | |
75 | #define log(format, ...) \ |
76 | dataLogF(format, ##__VA_ARGS__) |
77 | |
78 | #endif // USE(OS_LOG) |
79 | |
80 | struct SignalContext { |
81 | private: |
82 | SignalContext(PlatformRegisters& registers, MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC) |
83 | : registers(registers) |
84 | , machinePC(machinePC) |
85 | , stackPointer(MachineContext::stackPointer(registers)) |
86 | , framePointer(MachineContext::framePointer(registers)) |
87 | { } |
88 | |
89 | public: |
90 | static Optional<SignalContext> tryCreate(PlatformRegisters& registers) |
91 | { |
92 | auto instructionPointer = MachineContext::instructionPointer(registers); |
93 | if (!instructionPointer) |
94 | return WTF::nullopt; |
95 | return SignalContext(registers, *instructionPointer); |
96 | } |
97 | |
98 | void dump() |
99 | { |
100 | #if CPU(X86_64) |
101 | #define FOR_EACH_REGISTER(v) \ |
102 | v(rax) \ |
103 | v(rbx) \ |
104 | v(rcx) \ |
105 | v(rdx) \ |
106 | v(rdi) \ |
107 | v(rsi) \ |
108 | v(rbp) \ |
109 | v(rsp) \ |
110 | v(r8) \ |
111 | v(r9) \ |
112 | v(r10) \ |
113 | v(r11) \ |
114 | v(r12) \ |
115 | v(r13) \ |
116 | v(r14) \ |
117 | v(r15) \ |
118 | v(rip) \ |
119 | v(rflags) \ |
120 | v(cs) \ |
121 | v(fs) \ |
122 | v(gs) |
123 | |
124 | #define DUMP_REGISTER(__reg) \ |
125 | log("Register " #__reg ": %p", reinterpret_cast<void*>(registers.__##__reg)); |
126 | FOR_EACH_REGISTER(DUMP_REGISTER) |
127 | #undef FOR_EACH_REGISTER |
128 | |
129 | #elif CPU(ARM64) && defined(__LP64__) |
130 | int i; |
131 | for (i = 0; i < 28; i += 4) { |
132 | log("x%d: %016llx x%d: %016llx x%d: %016llx x%d: %016llx" , |
133 | i, registers.__x[i], |
134 | i+1, registers.__x[i+1], |
135 | i+2, registers.__x[i+2], |
136 | i+3, registers.__x[i+3]); |
137 | } |
138 | ASSERT(i < 29); |
139 | log("x%d: %016llx fp: %016llx lr: %016llx" , |
140 | i, registers.__x[i], |
141 | MachineContext::framePointer<uint64_t>(registers), |
142 | MachineContext::linkRegister(registers).untaggedExecutableAddress<uint64_t>()); |
143 | log("sp: %016llx pc: %016llx cpsr: %08x" , |
144 | MachineContext::stackPointer<uint64_t>(registers), |
145 | machinePC.untaggedExecutableAddress<uint64_t>(), |
146 | registers.__cpsr); |
147 | #endif |
148 | } |
149 | |
150 | PlatformRegisters& registers; |
151 | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; |
152 | void* stackPointer; |
153 | void* framePointer; |
154 | }; |
155 | |
156 | static void installCrashHandler() |
157 | { |
158 | #if CPU(X86_64) || CPU(ARM64) |
159 | installSignalHandler(Signal::Ill, [] (Signal, SigInfo&, PlatformRegisters& registers) { |
160 | auto signalContext = SignalContext::tryCreate(registers); |
161 | if (!signalContext) |
162 | return SignalAction::NotHandled; |
163 | |
164 | void* machinePC = signalContext->machinePC.untaggedExecutableAddress(); |
165 | if (!isJITPC(machinePC)) |
166 | return SignalAction::NotHandled; |
167 | |
168 | SigillCrashAnalyzer& analyzer = SigillCrashAnalyzer::instance(); |
169 | analyzer.analyze(*signalContext); |
170 | return SignalAction::NotHandled; |
171 | }); |
172 | #endif |
173 | } |
174 | |
175 | #else // OS(DARWIN) |
176 | |
177 | #define log(format, ...) do { } while (false) |
178 | |
179 | struct SignalContext { |
180 | SignalContext() { } |
181 | |
182 | void dump() { } |
183 | |
184 | MacroAssemblerCodePtr<PlatformRegistersPCPtrTag> machinePC; |
185 | void* stackPointer; |
186 | void* framePointer; |
187 | }; |
188 | |
189 | static void installCrashHandler() |
190 | { |
191 | // Do nothing. Not supported for this platform. |
192 | } |
193 | |
194 | #endif // OS(DARWIN) |
195 | |
196 | SigillCrashAnalyzer& SigillCrashAnalyzer::instance() |
197 | { |
198 | static SigillCrashAnalyzer* analyzer; |
199 | static std::once_flag once; |
200 | std::call_once(once, [] { |
201 | installCrashHandler(); |
202 | analyzer = new SigillCrashAnalyzer; |
203 | }); |
204 | return *analyzer; |
205 | } |
206 | |
207 | void enableSigillCrashAnalyzer() |
208 | { |
209 | // Just instantiating the SigillCrashAnalyzer will enable it. |
210 | SigillCrashAnalyzer::instance(); |
211 | } |
212 | |
213 | auto SigillCrashAnalyzer::analyze(SignalContext& context) -> CrashSource |
214 | { |
215 | CrashSource crashSource = CrashSource::Unknown; |
216 | log("BEGIN SIGILL analysis" ); |
217 | |
218 | do { |
219 | // First, dump the signal context info so that we'll at least have the same info |
220 | // that the default crash handler would given us in case this crash analyzer |
221 | // itself crashes. |
222 | context.dump(); |
223 | |
224 | VMInspector& inspector = VMInspector::instance(); |
225 | |
226 | // Use a timeout period of 2 seconds. The client is about to crash, and we don't |
227 | // want to turn the crash into a hang by re-trying the lock for too long. |
228 | auto expectedLocker = inspector.lock(Seconds(2)); |
229 | if (!expectedLocker) { |
230 | ASSERT(expectedLocker.error() == VMInspector::Error::TimedOut); |
231 | log("ERROR: Unable to analyze SIGILL. Timed out while waiting to iterate VMs." ); |
232 | break; |
233 | } |
234 | auto& locker = expectedLocker.value(); |
235 | |
236 | void* pc = context.machinePC.untaggedExecutableAddress(); |
237 | auto isInJITMemory = inspector.isValidExecutableMemory(locker, pc); |
238 | if (!isInJITMemory) { |
239 | log("ERROR: Timed out: not able to determine if pc %p is in valid JIT executable memory" , pc); |
240 | break; |
241 | } |
242 | if (!isInJITMemory.value()) { |
243 | log("pc %p is NOT in valid JIT executable memory" , pc); |
244 | crashSource = CrashSource::Other; |
245 | break; |
246 | } |
247 | log("pc %p is in valid JIT executable memory" , pc); |
248 | crashSource = CrashSource::JavaScriptCore; |
249 | |
250 | #if CPU(ARM64) |
251 | size_t pcAsSize = reinterpret_cast<size_t>(pc); |
252 | if (pcAsSize != roundUpToMultipleOf<sizeof(uint32_t)>(pcAsSize)) { |
253 | log("pc %p is NOT properly aligned" , pc); |
254 | break; |
255 | } |
256 | |
257 | // We know it's safe to read the word at the PC because we're handling a SIGILL. |
258 | // Otherwise, we would have crashed with a SIGBUS instead. |
259 | uint32_t wordAtPC = *reinterpret_cast<uint32_t*>(pc); |
260 | log("instruction bits at pc %p is: 0x%08x" , pc, wordAtPC); |
261 | #endif |
262 | |
263 | auto expectedCodeBlock = inspector.codeBlockForMachinePC(locker, pc); |
264 | if (!expectedCodeBlock) { |
265 | if (expectedCodeBlock.error() == VMInspector::Error::TimedOut) |
266 | log("ERROR: Timed out: not able to determine if pc %p is in a valid CodeBlock" , pc); |
267 | else |
268 | log("The current thread does not own any VM JSLock" ); |
269 | break; |
270 | } |
271 | CodeBlock* codeBlock = expectedCodeBlock.value(); |
272 | if (!codeBlock) { |
273 | log("machine PC %p does not belong to any CodeBlock in the currently entered VM" , pc); |
274 | break; |
275 | } |
276 | |
277 | log("pc %p belongs to CodeBlock %p of type %s" , pc, codeBlock, JITCode::typeName(codeBlock->jitType())); |
278 | |
279 | dumpCodeBlock(codeBlock, pc); |
280 | } while (false); |
281 | |
282 | log("END SIGILL analysis" ); |
283 | return crashSource; |
284 | } |
285 | |
286 | void SigillCrashAnalyzer::dumpCodeBlock(CodeBlock* codeBlock, void* machinePC) |
287 | { |
288 | #if CPU(ARM64) && ENABLE(JIT) |
289 | JITCode* jitCode = codeBlock->jitCode().get(); |
290 | |
291 | // Dump the raw bits of the code. |
292 | uint32_t* start = reinterpret_cast<uint32_t*>(jitCode->start()); |
293 | uint32_t* end = reinterpret_cast<uint32_t*>(jitCode->end()); |
294 | log("JITCode %p [%p-%p]:" , jitCode, start, end); |
295 | if (start < end) { |
296 | uint32_t* p = start; |
297 | while (p + 8 <= end) { |
298 | 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]); |
299 | p += 8; |
300 | } |
301 | if (p + 7 <= end) |
302 | 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]); |
303 | else if (p + 6 <= end) |
304 | log("[%p-%p]: %08x %08x %08x %08x %08x %08x" , p, p+5, p[0], p[1], p[2], p[3], p[4], p[5]); |
305 | else if (p + 5 <= end) |
306 | log("[%p-%p]: %08x %08x %08x %08x %08x" , p, p+4, p[0], p[1], p[2], p[3], p[4]); |
307 | else if (p + 4 <= end) |
308 | log("[%p-%p]: %08x %08x %08x %08x" , p, p+3, p[0], p[1], p[2], p[3]); |
309 | if (p + 3 <= end) |
310 | log("[%p-%p]: %08x %08x %08x" , p, p+2, p[0], p[1], p[2]); |
311 | else if (p + 2 <= end) |
312 | log("[%p-%p]: %08x %08x" , p, p+1, p[0], p[1]); |
313 | else if (p + 1 <= end) |
314 | log("[%p-%p]: %08x" , p, p, p[0]); |
315 | } |
316 | |
317 | // Dump the disassembly of the code. |
318 | log("Disassembly:" ); |
319 | uint32_t* currentPC = reinterpret_cast<uint32_t*>(jitCode->executableAddress()); |
320 | size_t byteCount = jitCode->size(); |
321 | while (byteCount) { |
322 | char pcString[24]; |
323 | if (currentPC == machinePC) { |
324 | snprintf(pcString, sizeof(pcString), "* 0x%lx" , reinterpret_cast<uintptr_t>(currentPC)); |
325 | log("%20s: %s <=========================" , pcString, m_arm64Opcode.disassemble(currentPC)); |
326 | } else { |
327 | snprintf(pcString, sizeof(pcString), "0x%lx" , reinterpret_cast<uintptr_t>(currentPC)); |
328 | log("%20s: %s" , pcString, m_arm64Opcode.disassemble(currentPC)); |
329 | } |
330 | currentPC++; |
331 | byteCount -= sizeof(uint32_t); |
332 | } |
333 | #else |
334 | UNUSED_PARAM(codeBlock); |
335 | UNUSED_PARAM(machinePC); |
336 | // Not implemented yet. |
337 | #endif |
338 | } |
339 | |
340 | } // namespace JSC |
341 | |