Rodrigo Mesquita pushed to branch wip/romes/per-thread-break-on-exception at Glasgow Haskell Compiler / GHC
Commits:
-
2103dc2f
by Rodrigo Mesquita at 2026-11-03T13:58:37+00:00
12 changed files:
- libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingDisabled.hsc
- libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingEnabled.hsc
- libraries/ghc-heap/tests/parse_tso_flags.hs
- libraries/ghc-internal/src/GHC/Internal/Heap/Closures.hs
- libraries/ghci/GHCi/Debugger.hs
- libraries/ghci/GHCi/Run.hs
- rts/Exception.cmm
- rts/Interpreter.c
- rts/Interpreter.h
- rts/RtsSymbols.c
- rts/include/rts/Constants.h
- rts/include/stg/MiscClosures.h
Changes:
| ... | ... | @@ -93,6 +93,7 @@ parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset |
| 93 | 93 | #if __GLASGOW_HASKELL__ >= 913
|
| 94 | 94 | | isSet (#const TSO_STOP_NEXT_BREAKPOINT) w = TsoStopNextBreakpoint : parseTsoFlags (unset (#const TSO_STOP_NEXT_BREAKPOINT) w)
|
| 95 | 95 | | isSet (#const TSO_STOP_AFTER_RETURN) w = TsoStopAfterReturn : parseTsoFlags (unset (#const TSO_STOP_AFTER_RETURN) w)
|
| 96 | + | isSet (#const TSO_STOP_ON_EXCEPTION) w = TsoStopOnException : parseTsoFlags (unset (#const TSO_STOP_ON_EXCEPTION) w)
|
|
| 96 | 97 | #endif
|
| 97 | 98 | parseTsoFlags 0 = []
|
| 98 | 99 | parseTsoFlags w = [TsoFlagsUnknownValue w]
|
| ... | ... | @@ -93,6 +93,7 @@ parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset |
| 93 | 93 | #if __GLASGOW_HASKELL__ >= 913
|
| 94 | 94 | | isSet (#const TSO_STOP_NEXT_BREAKPOINT) w = TsoStopNextBreakpoint : parseTsoFlags (unset (#const TSO_STOP_NEXT_BREAKPOINT) w)
|
| 95 | 95 | | isSet (#const TSO_STOP_AFTER_RETURN) w = TsoStopAfterReturn : parseTsoFlags (unset (#const TSO_STOP_AFTER_RETURN) w)
|
| 96 | + | isSet (#const TSO_STOP_ON_EXCEPTION) w = TsoStopOnException : parseTsoFlags (unset (#const TSO_STOP_ON_EXCEPTION) w)
|
|
| 96 | 97 | #endif
|
| 97 | 98 | parseTsoFlags 0 = []
|
| 98 | 99 | parseTsoFlags w = [TsoFlagsUnknownValue w]
|
| ... | ... | @@ -15,5 +15,6 @@ main = do |
| 15 | 15 | assertEqual (parseTsoFlags 256) [TsoAllocLimit]
|
| 16 | 16 | assertEqual (parseTsoFlags 512) [TsoStopNextBreakpoint]
|
| 17 | 17 | assertEqual (parseTsoFlags 1024) [TsoStopAfterReturn]
|
| 18 | + assertEqual (parseTsoFlags 2048) [TsoStopOnException]
|
|
| 18 | 19 | |
| 19 | 20 | assertEqual (parseTsoFlags 6) [TsoLocked, TsoBlockx] |
| ... | ... | @@ -632,6 +632,7 @@ data TsoFlags |
| 632 | 632 | | TsoAllocLimit
|
| 633 | 633 | | TsoStopNextBreakpoint
|
| 634 | 634 | | TsoStopAfterReturn
|
| 635 | + | TsoStopOnException
|
|
| 635 | 636 | | TsoFlagsUnknownValue Word32 -- ^ Please report this as a bug
|
| 636 | 637 | deriving (Eq, Show, Generic, Ord)
|
| 637 | 638 |
| ... | ... | @@ -13,7 +13,10 @@ module GHCi.Debugger |
| 13 | 13 | , rts_disableStopAfterReturn
|
| 14 | 14 | |
| 15 | 15 | -- * Stop on exception
|
| 16 | - , exceptionFlag
|
|
| 16 | + , rts_enableStopOnException
|
|
| 17 | + , rts_disableStopOnException
|
|
| 18 | + , rts_enableStopOnExceptionAll
|
|
| 19 | + , rts_disableStopOnExceptionAll
|
|
| 17 | 20 | |
| 18 | 21 | -- * Breakpoint Callback
|
| 19 | 22 | , BreakpointCallback
|
| ... | ... | @@ -23,7 +26,6 @@ module GHCi.Debugger |
| 23 | 26 | import Prelude -- See note [Why do we import Prelude here?]
|
| 24 | 27 | |
| 25 | 28 | import GHC.Base (ThreadId#, Addr#, Int#)
|
| 26 | -import Foreign.C (CInt)
|
|
| 27 | 29 | import Foreign (StablePtr, Ptr)
|
| 28 | 30 | import GHCi.RemoteTypes (HValue)
|
| 29 | 31 | |
| ... | ... | @@ -60,7 +62,19 @@ foreign import ccall unsafe "rts_disableStopAfterReturn" |
| 60 | 62 | |
| 61 | 63 | --------------------------------------------------------------------------------
|
| 62 | 64 | |
| 63 | -foreign import ccall "&rts_stop_on_exception" exceptionFlag :: Ptr CInt
|
|
| 65 | +-- | Make this thread break when an exception is hit
|
|
| 66 | +foreign import ccall unsafe "rts_enableStopOnException" rts_enableStopOnException :: ThreadId# -> IO ()
|
|
| 67 | + |
|
| 68 | +-- | No longer have this thread break when an exception is hit
|
|
| 69 | +foreign import ccall unsafe "rts_disableStopOnException" rts_disableStopOnException :: ThreadId# -> IO ()
|
|
| 70 | + |
|
| 71 | +-- | Enables global break-on-exception for all threads
|
|
| 72 | +foreign import ccall unsafe "rts_enableStopOnExceptionAll" rts_enableStopOnExceptionAll :: IO ()
|
|
| 73 | + |
|
| 74 | +-- | Disables the global break-on-exception for all threads. Individual threads
|
|
| 75 | +-- with break-on-exception will still break on exception if the global flag is
|
|
| 76 | +-- disabled.
|
|
| 77 | +foreign import ccall unsafe "rts_disableStopOnExceptionAll" rts_disableStopOnExceptionAll :: IO ()
|
|
| 64 | 78 | |
| 65 | 79 | --------------------------------------------------------------------------------
|
| 66 | 80 |
| ... | ... | @@ -269,14 +269,17 @@ rethrow EvalOpts{..} io = |
| 269 | 269 | catchNoPropagate io $ \(ExceptionWithContext cx se) -> do
|
| 270 | 270 | -- If -fbreak-on-error, we break unconditionally,
|
| 271 | 271 | -- but with care of not breaking twice
|
| 272 | + ThreadId mytid <- myThreadId
|
|
| 272 | 273 | if breakOnError && not breakOnException
|
| 273 | - then poke exceptionFlag 1
|
|
| 274 | + then rts_enableStopOnException mytid
|
|
| 274 | 275 | else case fromException se of
|
| 275 | 276 | -- If it is a "UserInterrupt" exception, we allow
|
| 276 | 277 | -- a possible break by way of -fbreak-on-exception
|
| 277 | 278 | Just UserInterrupt -> return ()
|
| 278 | 279 | -- In any other case, we don't want to break
|
| 279 | - _ -> poke exceptionFlag 0
|
|
| 280 | + _ -> do
|
|
| 281 | + rts_disableStopOnException mytid
|
|
| 282 | + rts_disableStopOnExceptionAll
|
|
| 280 | 283 | rethrowIO (ExceptionWithContext cx se)
|
| 281 | 284 | |
| 282 | 285 | --
|
| ... | ... | @@ -338,7 +341,11 @@ withBreakAction opts breakMVar statusMVar mtid act |
| 338 | 341 | setBreakAction = do
|
| 339 | 342 | stablePtr <- newStablePtr onBreak
|
| 340 | 343 | poke breakPointIOAction stablePtr
|
| 341 | - when (breakOnException opts) $ poke exceptionFlag 1
|
|
| 344 | + when (breakOnException opts) $ do
|
|
| 345 | + case mtid of
|
|
| 346 | + Nothing -> rts_enableStopOnExceptionAll
|
|
| 347 | + Just (ThreadId tid) -> do
|
|
| 348 | + rts_enableStopOnException tid
|
|
| 342 | 349 | when (singleStep opts) rts_enableStopNextBreakpointAll
|
| 343 | 350 | when (stepOut opts) $ do
|
| 344 | 351 | case mtid of
|
| ... | ... | @@ -380,7 +387,9 @@ withBreakAction opts breakMVar statusMVar mtid act |
| 380 | 387 | |
| 381 | 388 | resetBreakAction stablePtr = do
|
| 382 | 389 | poke breakPointIOAction noBreakStablePtr
|
| 383 | - poke exceptionFlag 0
|
|
| 390 | + case mtid of
|
|
| 391 | + Just (ThreadId tid) -> rts_disableStopOnException tid
|
|
| 392 | + _ -> rts_disableStopOnExceptionAll
|
|
| 384 | 393 | rts_disableStopNextBreakpointAll
|
| 385 | 394 | case mtid of
|
| 386 | 395 | Just (ThreadId tid) -> rts_disableStopAfterReturn tid
|
| ... | ... | @@ -523,14 +523,16 @@ retry_pop_stack: |
| 523 | 523 | if (W_[no_break_on_exception] != 0) {
|
| 524 | 524 | W_[no_break_on_exception] = 0;
|
| 525 | 525 | } else {
|
| 526 | - if (TO_W_(CInt[rts_stop_on_exception]) != 0) {
|
|
| 526 | + if (TO_W_(CInt[rts_stop_on_exception]) != 0 ||
|
|
| 527 | + (TO_W_(StgTSO_flags(CurrentTSO)) & TSO_STOP_ON_EXCEPTION) != 0) {
|
|
| 527 | 528 | W_ ioAction;
|
| 528 | 529 | // we don't want any further exceptions to be caught,
|
| 529 | 530 | // until GHCi is ready to handle them. This prevents
|
| 530 | 531 | // deadlock if an exception is raised in InteractiveUI,
|
| 531 | - // for example. Perhaps the stop_on_exception flag should
|
|
| 532 | - // be per-thread.
|
|
| 532 | + // for example.
|
|
| 533 | 533 | CInt[rts_stop_on_exception] = 0;
|
| 534 | + StgTSO_flags(CurrentTSO) = %lobits32(
|
|
| 535 | + TO_W_(StgTSO_flags(CurrentTSO)) & ~TSO_STOP_ON_EXCEPTION);
|
|
| 534 | 536 | ("ptr" ioAction) = ccall deRefStablePtr (W_[rts_breakpoint_io_action] "ptr");
|
| 535 | 537 | Sp = Sp - WDS(11);
|
| 536 | 538 | Sp(10) = exception;
|
| ... | ... | @@ -388,8 +388,6 @@ is_ret_bco_frame(const StgPtr frame_head) { |
| 388 | 388 | );
|
| 389 | 389 | }
|
| 390 | 390 | |
| 391 | -int rts_stop_on_exception = 0;
|
|
| 392 | - |
|
| 393 | 391 | /* ---------------------------------------------------------------------------
|
| 394 | 392 | * Enabling and disabling global single step mode
|
| 395 | 393 | * ------------------------------------------------------------------------ */
|
| ... | ... | @@ -398,7 +396,7 @@ int rts_stop_on_exception = 0; |
| 398 | 396 | * Unlike `TSO_STOP_NEXT_BREAKPOINT`, which sets single-step mode per-thread,
|
| 399 | 397 | * `rts_stop_next_breakpoint` globally enables single-step mode. If enabled, we
|
| 400 | 398 | * will stop at the immediate next breakpoint regardless of what thread it is in. */
|
| 401 | -int rts_stop_next_breakpoint = 0;
|
|
| 399 | +static int rts_stop_next_breakpoint = 0;
|
|
| 402 | 400 | |
| 403 | 401 | void rts_enableStopNextBreakpointAll(void)
|
| 404 | 402 | {
|
| ... | ... | @@ -473,7 +471,50 @@ A limitation of this approach is that stepping-out of a function that was |
| 473 | 471 | tail-called will skip its caller since no stack frame is pushed for a tail
|
| 474 | 472 | call (i.e. a tail call returns directly to its caller's first non-tail caller).
|
| 475 | 473 | */
|
| 476 | -/* -------------------------------------------------------------------------- */
|
|
| 474 | + |
|
| 475 | +/* ---------------------------------------------------------------------------
|
|
| 476 | + * Enabling and disabling global break-on-exception
|
|
| 477 | + * ------------------------------------------------------------------------ */
|
|
| 478 | + |
|
| 479 | +/* A global toggle for break-on-exception
|
|
| 480 | + |
|
| 481 | + Unlike `TSO_STOP_ON_EXCEPTION`, which breaks when an exception is raised in
|
|
| 482 | + this thread, the global toggle makes an exception raised in any thread yield
|
|
| 483 | + a breakpoint in that thread.
|
|
| 484 | + |
|
| 485 | + Currently, this is necessary if we ever want to break on an exception raised
|
|
| 486 | + in thread forked by the main debuggee thead: we can only toggle TSO_STOP_ON_EXCEPTION
|
|
| 487 | + on threads we control, and those forked by the debuggee we don't.
|
|
| 488 | + */
|
|
| 489 | +int rts_stop_on_exception = 0;
|
|
| 490 | + |
|
| 491 | +void rts_enableStopOnExceptionAll(void)
|
|
| 492 | +{
|
|
| 493 | + rts_stop_on_exception = 1;
|
|
| 494 | +}
|
|
| 495 | + |
|
| 496 | +void rts_disableStopOnExceptionAll(void)
|
|
| 497 | +{
|
|
| 498 | + rts_stop_on_exception = 0;
|
|
| 499 | +}
|
|
| 500 | + |
|
| 501 | +/* ---------------------------------------------------------------------------
|
|
| 502 | + * Enabling and disabling per-thread break-on-exception
|
|
| 503 | + * ------------------------------------------------------------------------ */
|
|
| 504 | + |
|
| 505 | +void rts_enableStopOnException(StgTSO* tso)
|
|
| 506 | +{
|
|
| 507 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 508 | + setThreadFlag(cap, tso, TSO_STOP_ON_EXCEPTION);
|
|
| 509 | +}
|
|
| 510 | + |
|
| 511 | +void rts_disableStopOnException(StgTSO* tso)
|
|
| 512 | +{
|
|
| 513 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 514 | + unsetThreadFlag(cap, tso, TSO_STOP_ON_EXCEPTION);
|
|
| 515 | +}
|
|
| 516 | + |
|
| 517 | +/* ------------------------------------------------------------------------ */
|
|
| 477 | 518 | |
| 478 | 519 | #if defined(INTERP_STATS)
|
| 479 | 520 |
| ... | ... | @@ -12,6 +12,11 @@ RTS_PRIVATE Capability *interpretBCO (Capability* cap); |
| 12 | 12 | void interp_startup ( void );
|
| 13 | 13 | void interp_shutdown ( void );
|
| 14 | 14 | |
| 15 | +// The following debugger functions are exported from here, but not from the
|
|
| 16 | +// RTS public api (RtsApi.h). They are used by the `ghci` library to implement
|
|
| 17 | +// debugger commands on the interpreter like step-next, step-out, and
|
|
| 18 | +// break-on-exception.
|
|
| 19 | + |
|
| 15 | 20 | // Debugger single step
|
| 16 | 21 | void rts_enableStopNextBreakpointAll ( void );
|
| 17 | 22 | void rts_disableStopNextBreakpointAll ( void );
|
| ... | ... | @@ -21,3 +26,9 @@ void rts_disableStopNextBreakpoint ( StgTSO* ); |
| 21 | 26 | // Debugger step out
|
| 22 | 27 | void rts_enableStopAfterReturn ( StgTSO* );
|
| 23 | 28 | void rts_disableStopAfterReturn ( StgTSO* );
|
| 29 | + |
|
| 30 | +// Debugger stop on exception
|
|
| 31 | +void rts_enableStopOnException ( StgTSO* );
|
|
| 32 | +void rts_disableStopOnException ( StgTSO* );
|
|
| 33 | +void rts_enableStopOnExceptionAll ( void );
|
|
| 34 | +void rts_disableStopOnExceptionAll ( void ); |
| ... | ... | @@ -942,14 +942,16 @@ extern char **environ; |
| 942 | 942 | SymI_HasProto(revertCAFs) \
|
| 943 | 943 | SymI_HasDataProto(RtsFlags) \
|
| 944 | 944 | SymI_NeedsDataProto(rts_breakpoint_io_action) \
|
| 945 | - SymI_NeedsDataProto(rts_stop_next_breakpoint) \
|
|
| 946 | - SymI_NeedsDataProto(rts_stop_on_exception) \
|
|
| 947 | 945 | SymI_NeedsProto(rts_enableStopNextBreakpointAll) \
|
| 948 | 946 | SymI_NeedsProto(rts_disableStopNextBreakpointAll) \
|
| 949 | 947 | SymI_NeedsProto(rts_enableStopNextBreakpoint) \
|
| 950 | 948 | SymI_NeedsProto(rts_disableStopNextBreakpoint) \
|
| 951 | 949 | SymI_NeedsProto(rts_enableStopAfterReturn) \
|
| 952 | 950 | SymI_NeedsProto(rts_disableStopAfterReturn) \
|
| 951 | + SymI_NeedsProto(rts_enableStopOnException) \
|
|
| 952 | + SymI_NeedsProto(rts_disableStopOnException) \
|
|
| 953 | + SymI_NeedsProto(rts_enableStopOnExceptionAll) \
|
|
| 954 | + SymI_NeedsProto(rts_disableStopOnExceptionAll) \
|
|
| 953 | 955 | SymI_HasProto(stopTimer) \
|
| 954 | 956 | SymI_HasDataProto(n_capabilities) \
|
| 955 | 957 | SymI_HasDataProto(max_n_capabilities) \
|
| ... | ... | @@ -394,6 +394,11 @@ |
| 394 | 394 | */
|
| 395 | 395 | #define TSO_STOP_AFTER_RETURN 1024
|
| 396 | 396 | |
| 397 | +/*
|
|
| 398 | + * Enables break-on-exception mode for this thread
|
|
| 399 | + */
|
|
| 400 | +#define TSO_STOP_ON_EXCEPTION 2048
|
|
| 401 | + |
|
| 397 | 402 | /*
|
| 398 | 403 | * The number of times we spin in a spin lock before yielding (see
|
| 399 | 404 | * #3758). To tune this value, use the benchmark in #3758: run the
|
| ... | ... | @@ -583,7 +583,6 @@ RTS_FUN_DECL(stg_annotateStackzh); |
| 583 | 583 | #if IN_STG_CODE && !IN_STGCRUN
|
| 584 | 584 | |
| 585 | 585 | // Interpreter.c
|
| 586 | -extern StgWord rts_stop_next_breakpoint[];
|
|
| 587 | 586 | extern StgWord rts_stop_on_exception[];
|
| 588 | 587 | extern StgWord rts_breakpoint_io_action[];
|
| 589 | 588 |