tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */