DebugAPI.h (18778B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef debugger_DebugAPI_h 8 #define debugger_DebugAPI_h 9 10 #include "js/Debug.h" 11 #include "vm/GlobalObject.h" 12 #include "vm/Interpreter.h" 13 #include "vm/JSContext.h" 14 #include "vm/Realm.h" 15 16 namespace js { 17 18 // This file contains the API which SpiderMonkey should use to interact with any 19 // active Debuggers. 20 21 class AbstractGeneratorObject; 22 class DebugScriptMap; 23 class PromiseObject; 24 25 namespace gc { 26 class AutoSuppressGC; 27 } // namespace gc 28 29 /** 30 * DebugAPI::onNativeCall allows the debugger to call callbacks just before 31 * some native functions are to be executed. It also allows the hooks 32 * themselves to affect the result of the call. This enum represents the 33 * various affects that DebugAPI::onNativeCall may perform. 34 */ 35 enum class NativeResumeMode { 36 /** 37 * If the debugger hook did not return a value to manipulate the result of 38 * the native call, execution can continue unchanged. 39 * 40 * Continue indicates that the native function should execute normally. 41 */ 42 Continue, 43 44 /** 45 * If the debugger hook returned an explicit return value that is meant to 46 * take the place of the native call's result, execution of the native 47 * function needs to be skipped in favor of the explicit result. 48 * 49 * Override indicates that the native function should be skipped and that 50 * the debugger has already stored the return value into the CallArgs. 51 */ 52 Override, 53 54 /** 55 * If the debugger hook returns an explicit termination or an explicit 56 * thrown exception, execution of the native function needs to be skipped 57 * in favor of handling the error condition. 58 * 59 * Abort indicates that the native function should be skipped and that 60 * execution should be terminated. The debugger may or may not have set a 61 * pending exception. 62 */ 63 Abort, 64 }; 65 66 class DebugScript; 67 class DebuggerVector; 68 69 class DebugAPI { 70 public: 71 friend class Debugger; 72 73 /*** Methods for interaction with the GC. ***********************************/ 74 75 /* 76 * Trace (inferred) owning edges from stack frames to Debugger.Frames, as part 77 * of root marking. 78 * 79 * Even if a Debugger.Frame for a live stack frame is entirely unreachable 80 * from JS, if it has onStep or onPop hooks set, then collecting it would have 81 * observable side effects - namely, the hooks would fail to run. The effect 82 * is the same as if the stack frame held an owning edge to its 83 * Debugger.Frame. 84 * 85 * Debugger.Frames must also be retained if the Debugger to which they belong 86 * is reachable, even if they have no hooks set, but we handle that elsewhere; 87 * this function is only concerned with the inferred roots from stack frames 88 * to Debugger.Frames that have hooks set. 89 */ 90 static void traceFramesWithLiveHooks(JSTracer* tracer); 91 92 /* 93 * Trace (inferred) owning edges from generator objects to Debugger.Frames. 94 * 95 * Even if a Debugger.Frame for a live suspended generator object is entirely 96 * unreachable from JS, if it has onStep or onPop hooks set, then collecting 97 * it would have observable side effects - namely, the hooks would fail to run 98 * if the generator is resumed. The effect is the same as if the generator 99 * object held an owning edge to its Debugger.Frame. 100 */ 101 static inline void traceGeneratorFrame(JSTracer* tracer, 102 AbstractGeneratorObject* generator); 103 104 // Trace cross compartment edges in all debuggers relevant to the current GC. 105 static void traceCrossCompartmentEdges(JSTracer* tracer); 106 107 // Trace all debugger-owned GC things unconditionally, during a moving GC. 108 static void traceAllForMovingGC(JSTracer* trc); 109 110 // Trace the debug script map. Called as part of tracing a zone's roots. 111 static void traceDebugScriptMap(JSTracer* trc, DebugScriptMap* map); 112 113 static void traceFromRealm(JSTracer* trc, Realm* realm); 114 115 // The garbage collector calls this after everything has been marked, but 116 // before anything has been finalized. We use this to clear Debugger / 117 // debuggee edges at a point where the parties concerned are all still 118 // initialized. This does not update edges to moved GC things which is handled 119 // via the other trace methods. 120 static void sweepAll(JS::GCContext* gcx); 121 122 // Add sweep group edges due to the presence of any debuggers. 123 [[nodiscard]] static bool findSweepGroupEdges(JSRuntime* rt); 124 125 // Remove the debugging information associated with a script. 126 static void removeDebugScript(JS::GCContext* gcx, JSScript* script); 127 128 // Delete a Zone's debug script map. Called when a zone is destroyed. 129 static void deleteDebugScriptMap(DebugScriptMap* map); 130 131 // Validate the debugging information in a script after a moving GC> 132 #ifdef JSGC_HASH_TABLE_CHECKS 133 static void checkDebugScriptAfterMovingGC(DebugScript* ds); 134 #endif 135 136 #ifdef DEBUG 137 static bool edgeIsInDebuggerWeakmap(JSRuntime* rt, JSObject* src, 138 JS::GCCellPtr dst); 139 #endif 140 141 /*** Methods for querying script breakpoint state. **************************/ 142 143 // Query information about whether any debuggers are observing a script. 144 static inline bool stepModeEnabled(JSScript* script); 145 static inline bool hasBreakpointsAt(JSScript* script, jsbytecode* pc); 146 static inline bool hasAnyBreakpointsOrStepMode(JSScript* script); 147 148 /*** Methods for interacting with the JITs. *********************************/ 149 150 // Update Debugger frames when an interpreter frame is replaced with a 151 // baseline frame. 152 [[nodiscard]] static bool handleBaselineOsr(JSContext* cx, 153 InterpreterFrame* from, 154 jit::BaselineFrame* to); 155 156 // Update Debugger frames when an Ion frame bails out and is replaced with a 157 // baseline frame. 158 [[nodiscard]] static bool handleIonBailout(JSContext* cx, 159 jit::RematerializedFrame* from, 160 jit::BaselineFrame* to); 161 162 // Detach any Debugger frames from an Ion frame after an error occurred while 163 // it bailed out. 164 static void handleUnrecoverableIonBailoutError( 165 JSContext* cx, jit::RematerializedFrame* frame); 166 167 // When doing on-stack-replacement of a debuggee interpreter frame with a 168 // baseline frame, ensure that the resulting frame can be observed by the 169 // debugger. 170 [[nodiscard]] static bool ensureExecutionObservabilityOfOsrFrame( 171 JSContext* cx, AbstractFramePtr osrSourceFrame); 172 173 // Describes a set of scripts or frames whose execution observability can 174 // change due to debugger activity. 175 class ExecutionObservableSet { 176 public: 177 using ZoneRange = HashSet<Zone*>::Range; 178 179 virtual Zone* singleZone() const { return nullptr; } 180 virtual JSScript* singleScriptForZoneInvalidation() const { 181 return nullptr; 182 } 183 virtual const HashSet<Zone*>* zones() const { return nullptr; } 184 185 virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0; 186 virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0; 187 }; 188 189 // This enum is converted to and compare with bool values; NotObserving 190 // must be 0 and Observing must be 1. 191 enum IsObserving { NotObserving = 0, Observing = 1 }; 192 193 /*** Methods for calling installed debugger handlers. ***********************/ 194 195 // Called when a new script becomes accessible to debuggers. 196 static void onNewScript(JSContext* cx, HandleScript script); 197 198 // Called when a new wasm instance becomes accessible to debuggers. 199 static inline void onNewWasmInstance( 200 JSContext* cx, Handle<WasmInstanceObject*> wasmInstance); 201 202 /* 203 * Announce to the debugger that the context has entered a new JavaScript 204 * frame, |frame|. Call whatever hooks have been registered to observe new 205 * frames. 206 */ 207 [[nodiscard]] static inline bool onEnterFrame(JSContext* cx, 208 AbstractFramePtr frame); 209 210 /* 211 * Like onEnterFrame, but for resuming execution of a generator or async 212 * function. `frame` is a new baseline or interpreter frame, but abstractly 213 * it can be identified with a particular generator frame that was 214 * suspended earlier. 215 * 216 * There is no separate user-visible Debugger.onResumeFrame hook; this 217 * fires .onEnterFrame (again, since we're re-entering the frame). 218 * 219 * Unfortunately, the interpreter and the baseline JIT arrange for this to 220 * be called in different ways. The interpreter calls it from JSOp::Resume, 221 * immediately after pushing the resumed frame; the JIT calls it from 222 * JSOp::AfterYield, just after the generator resumes. The difference 223 * should not be user-visible. 224 */ 225 [[nodiscard]] static inline bool onResumeFrame(JSContext* cx, 226 AbstractFramePtr frame); 227 228 // Called when Wasm frame is suspended by JS PI. 229 static void onSuspendWasmFrame(JSContext* cx, wasm::DebugFrame* debugFrame); 230 231 // Called when Wasm frame is resumed by JS PI. 232 static void onResumeWasmFrame(JSContext* cx, const FrameIter& iter); 233 234 static inline NativeResumeMode onNativeCall(JSContext* cx, 235 const CallArgs& args, 236 CallReason reason); 237 238 static inline bool shouldAvoidSideEffects(JSContext* cx); 239 240 /* 241 * Announce to the debugger a |debugger;| statement on has been 242 * encountered on the youngest JS frame on |cx|. Call whatever hooks have 243 * been registered to observe this. 244 * 245 * Note that this method is called for all |debugger;| statements, 246 * regardless of the frame's debuggee-ness. 247 */ 248 [[nodiscard]] static inline bool onDebuggerStatement(JSContext* cx, 249 AbstractFramePtr frame); 250 251 /* 252 * Announce to the debugger that an exception has been thrown and propagated 253 * to |frame|. Call whatever hooks have been registered to observe this. 254 */ 255 [[nodiscard]] static inline bool onExceptionUnwind(JSContext* cx, 256 AbstractFramePtr frame); 257 258 /* 259 * Announce to the debugger that the thread has exited a JavaScript frame, 260 * |frame|. If |ok| is true, the frame is returning normally; if |ok| is 261 * false, the frame is throwing an exception or terminating. 262 * 263 * Change cx's current exception and |frame|'s return value to reflect the 264 * changes in behavior the hooks request, if any. Return the new error/success 265 * value. 266 * 267 * This function may be called twice for the same outgoing frame; only the 268 * first call has any effect. (Permitting double calls simplifies some 269 * cases where an onPop handler's resumption value changes a return to a 270 * throw, or vice versa: we can redirect to a complete copy of the 271 * alternative path, containing its own call to onLeaveFrame.) 272 */ 273 [[nodiscard]] static inline bool onLeaveFrame(JSContext* cx, 274 AbstractFramePtr frame, 275 const jsbytecode* pc, bool ok); 276 277 // Call any breakpoint handlers for the current scripted location. 278 [[nodiscard]] static bool onTrap(JSContext* cx); 279 280 // Call any stepping handlers for the current scripted location. 281 [[nodiscard]] static bool onSingleStep(JSContext* cx); 282 283 // Notify any Debugger instances observing this promise's global that a new 284 // promise was allocated. 285 static inline void onNewPromise(JSContext* cx, 286 Handle<PromiseObject*> promise); 287 288 // Notify any Debugger instances observing this promise's global that the 289 // promise has settled (ie, it has either been fulfilled or rejected). Note 290 // that this is *not* equivalent to the promise resolution (ie, the promise's 291 // fate getting locked in) because you can resolve a promise with another 292 // pending promise, in which case neither promise has settled yet. 293 // 294 // This should never be called on the same promise more than once, because a 295 // promise can only make the transition from unsettled to settled once. 296 static inline void onPromiseSettled(JSContext* cx, 297 Handle<PromiseObject*> promise); 298 299 // Notify any Debugger instances that a new global object has been created. 300 static inline void onNewGlobalObject(JSContext* cx, 301 Handle<GlobalObject*> global); 302 303 /*** Methods for querying installed debugger handlers. **********************/ 304 305 // Whether any debugger is observing execution in a global. 306 static bool debuggerObservesAllExecution(GlobalObject* global); 307 308 // Whether any debugger is observing JS execution coverage in a global. 309 static bool debuggerObservesCoverage(GlobalObject* global); 310 311 // Whether any Debugger is observing asm.js execution in a global. 312 static bool debuggerObservesAsmJS(GlobalObject* global); 313 314 // Whether any Debugger is observing WebAssembly execution in a global. 315 static bool debuggerObservesWasm(GlobalObject* global); 316 317 // Whether any Debugger is observing native function call. 318 static bool debuggerObservesNativeCall(GlobalObject* global); 319 320 /* 321 * Return true if the given global is being observed by at least one 322 * Debugger that is tracking allocations. 323 */ 324 static bool isObservedByDebuggerTrackingAllocations( 325 const GlobalObject& debuggee); 326 327 // If any debuggers are tracking allocations for a global, return the 328 // probability that a given allocation should be tracked. Nothing otherwise. 329 static mozilla::Maybe<double> allocationSamplingProbability( 330 GlobalObject* global); 331 332 // Whether any debugger is observing exception unwinds in a realm. 333 static bool hasExceptionUnwindHook(GlobalObject* global); 334 335 // Whether any debugger is observing debugger statements in a realm. 336 static bool hasDebuggerStatementHook(GlobalObject* global); 337 338 /*** Assorted methods for interacting with the runtime. *********************/ 339 340 // Checks if the current compartment is allowed to execute code. 341 [[nodiscard]] static inline bool checkNoExecute(JSContext* cx, 342 HandleScript script); 343 344 /* 345 * Announce to the debugger that a generator object has been created, 346 * via JSOp::Generator. 347 * 348 * This does not fire user hooks, but it's needed for debugger bookkeeping. 349 */ 350 [[nodiscard]] static inline bool onNewGenerator( 351 JSContext* cx, AbstractFramePtr frame, 352 Handle<AbstractGeneratorObject*> genObj); 353 354 static inline void onGeneratorClosed(JSContext* cx, 355 AbstractGeneratorObject* genObj); 356 357 // If necessary, record an object that was just allocated for any observing 358 // debuggers. 359 [[nodiscard]] static inline bool onLogAllocationSite( 360 JSContext* cx, JSObject* obj, Handle<SavedFrame*> frame, 361 mozilla::TimeStamp when); 362 363 // Announce to the debugger that a global object is being collected by the 364 // specified major GC. 365 static inline void notifyParticipatesInGC(GlobalObject* global, 366 uint64_t majorGCNumber); 367 368 private: 369 static bool stepModeEnabledSlow(JSScript* script); 370 static bool hasBreakpointsAtSlow(JSScript* script, jsbytecode* pc); 371 static void slowPathOnNewGlobalObject(JSContext* cx, 372 Handle<GlobalObject*> global); 373 static void slowPathNotifyParticipatesInGC(uint64_t majorGCNumber, 374 JS::Realm::DebuggerVector& dbgs, 375 const JS::AutoRequireNoGC& nogc); 376 [[nodiscard]] static bool slowPathOnLogAllocationSite( 377 JSContext* cx, HandleObject obj, Handle<SavedFrame*> frame, 378 mozilla::TimeStamp when, JS::Realm::DebuggerVector& dbgs, 379 const gc::AutoSuppressGC& nogc); 380 [[nodiscard]] static bool slowPathOnLeaveFrame(JSContext* cx, 381 AbstractFramePtr frame, 382 const jsbytecode* pc, bool ok); 383 [[nodiscard]] static bool slowPathOnNewGenerator( 384 JSContext* cx, AbstractFramePtr frame, 385 Handle<AbstractGeneratorObject*> genObj); 386 static void slowPathOnGeneratorClosed(JSContext* cx, 387 AbstractGeneratorObject* genObj); 388 [[nodiscard]] static bool slowPathCheckNoExecute(JSContext* cx, 389 HandleScript script); 390 [[nodiscard]] static bool slowPathOnEnterFrame(JSContext* cx, 391 AbstractFramePtr frame); 392 [[nodiscard]] static bool slowPathOnResumeFrame(JSContext* cx, 393 AbstractFramePtr frame); 394 static NativeResumeMode slowPathOnNativeCall(JSContext* cx, 395 const CallArgs& args, 396 CallReason reason); 397 static bool slowPathShouldAvoidSideEffects(JSContext* cx); 398 [[nodiscard]] static bool slowPathOnDebuggerStatement(JSContext* cx, 399 AbstractFramePtr frame); 400 [[nodiscard]] static bool slowPathOnExceptionUnwind(JSContext* cx, 401 AbstractFramePtr frame); 402 static void slowPathOnNewWasmInstance( 403 JSContext* cx, Handle<WasmInstanceObject*> wasmInstance); 404 static void slowPathOnNewPromise(JSContext* cx, 405 Handle<PromiseObject*> promise); 406 static void slowPathOnPromiseSettled(JSContext* cx, 407 Handle<PromiseObject*> promise); 408 static bool inFrameMaps(AbstractFramePtr frame); 409 static void slowPathTraceGeneratorFrame(JSTracer* tracer, 410 AbstractGeneratorObject* generator); 411 }; 412 413 // Suppresses all debuggee NX checks, i.e., allow all execution. Used to allow 414 // certain whitelisted operations to execute code. 415 // 416 // WARNING 417 // WARNING Do not use this unless you know what you are doing! 418 // WARNING 419 class AutoSuppressDebuggeeNoExecuteChecks { 420 EnterDebuggeeNoExecute** stack_; 421 EnterDebuggeeNoExecute* prev_; 422 423 public: 424 explicit AutoSuppressDebuggeeNoExecuteChecks(JSContext* cx) { 425 stack_ = &cx->noExecuteDebuggerTop.ref(); 426 prev_ = *stack_; 427 *stack_ = nullptr; 428 } 429 430 ~AutoSuppressDebuggeeNoExecuteChecks() { 431 MOZ_ASSERT(!*stack_); 432 *stack_ = prev_; 433 } 434 }; 435 436 } /* namespace js */ 437 438 #endif /* debugger_DebugAPI_h */