|
1
|
1
|
{-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP,
|
|
2
|
2
|
UnboxedTuples, LambdaCase, UnliftedFFITypes #-}
|
|
3
|
3
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
|
|
4
|
+{-# LANGUAGE BangPatterns #-}
|
|
4
|
5
|
|
|
5
|
6
|
-- |
|
|
6
|
7
|
-- Execute GHCi messages.
|
| ... |
... |
@@ -30,6 +31,7 @@ import GHCi.TH |
|
30
|
31
|
import GHCi.BreakArray
|
|
31
|
32
|
import GHCi.StaticPtrTable
|
|
32
|
33
|
|
|
|
34
|
+import qualified Data.Map as Map
|
|
33
|
35
|
import Control.Concurrent
|
|
34
|
36
|
import Control.DeepSeq
|
|
35
|
37
|
import Control.Exception
|
| ... |
... |
@@ -194,20 +196,22 @@ evalStringToString r str = do |
|
194
|
196
|
-- information about the breakpoint than the current iserv
|
|
195
|
197
|
-- process.
|
|
196
|
198
|
doSeq :: RemoteRef a -> IO (EvalStatus ())
|
|
197
|
|
-doSeq ref = do
|
|
|
199
|
+doSeq ref = clearEvalStatus <$> do
|
|
198
|
200
|
sandboxIO evalOptsSeq $ do
|
|
199
|
201
|
_ <- (void $ evaluate =<< localRef ref)
|
|
200
|
|
- return ()
|
|
|
202
|
+ return []
|
|
201
|
203
|
|
|
202
|
204
|
-- | Process a ResumeSeq message. Continue the :force processing #2950
|
|
203
|
205
|
-- after a breakpoint.
|
|
204
|
|
-resumeSeq :: RemoteRef (ResumeContext ()) -> IO (EvalStatus ())
|
|
205
|
|
-resumeSeq hvref = do
|
|
206
|
|
- ResumeContext{..} <- localRef hvref
|
|
207
|
|
- withBreakAction evalOptsSeq resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
|
|
|
206
|
+resumeSeq :: RemoteRef ThreadId -> IO (EvalStatus ())
|
|
|
207
|
+resumeSeq hvref = clearEvalStatus <$> do
|
|
|
208
|
+ resumeThreadId <- localRef hvref
|
|
|
209
|
+ withBreakAction evalOptsSeq (Just resumeThreadId) $ do
|
|
|
210
|
+ ResumeContext{..} <- getThreadResumeContext resumeThreadId
|
|
208
|
211
|
mask_ $ do
|
|
209
|
212
|
putMVar resumeBreakMVar () -- this awakens the stopped thread...
|
|
210
|
|
- redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
|
|
|
213
|
+ redirectInterrupts resumeThreadId $ do
|
|
|
214
|
+ takeMVar resumeStatusMVar
|
|
211
|
215
|
|
|
212
|
216
|
evalOptsSeq :: EvalOpts
|
|
213
|
217
|
evalOptsSeq = EvalOpts
|
| ... |
... |
@@ -228,21 +232,21 @@ evalOptsSeq = EvalOpts |
|
228
|
232
|
-- only while we execute the user's code. We can't afford to lose the final
|
|
229
|
233
|
-- putMVar, otherwise deadlock ensues. (#1583, #1922, #1946)
|
|
230
|
234
|
|
|
231
|
|
-sandboxIO :: EvalOpts -> IO a -> IO (EvalStatus a)
|
|
|
235
|
+sandboxIO :: EvalOpts -> IO [HValueRef] -> IO (EvalStatus [HValueRef])
|
|
232
|
236
|
sandboxIO opts io = do
|
|
233
|
237
|
-- We are running in uninterruptibleMask
|
|
234
|
|
- breakMVar <- newEmptyMVar
|
|
235
|
|
- statusMVar <- newEmptyMVar
|
|
236
|
|
- withBreakAction opts breakMVar statusMVar Nothing $ do
|
|
|
238
|
+ withBreakAction opts Nothing $ do
|
|
237
|
239
|
let runIt = measureAlloc $ tryEval $ rethrow opts $ clearCCS io
|
|
238
|
240
|
if useSandboxThread opts
|
|
239
|
241
|
then do
|
|
240
|
242
|
tid <- forkIO $ do
|
|
241
|
243
|
tid <- myThreadId
|
|
|
244
|
+ ResumeContext{..} <- getThreadResumeContext tid
|
|
242
|
245
|
labelThread tid "GHCi sandbox"
|
|
243
|
|
- unsafeUnmask runIt >>= putMVar statusMVar
|
|
|
246
|
+ unsafeUnmask runIt >>= putMVar resumeStatusMVar
|
|
244
|
247
|
-- empty: can't block
|
|
245
|
|
- redirectInterrupts tid $ unsafeUnmask $ takeMVar statusMVar
|
|
|
248
|
+ ResumeContext{..} <- getThreadResumeContext tid
|
|
|
249
|
+ redirectInterrupts tid $ unsafeUnmask $ takeMVar resumeStatusMVar
|
|
246
|
250
|
else
|
|
247
|
251
|
-- GLUT on OS X needs to run on the main thread. If you
|
|
248
|
252
|
-- try to use it from another thread then you just get a
|
| ... |
... |
@@ -328,71 +332,47 @@ tryEval io = do |
|
328
|
332
|
-- resets everything when the computation has stopped running. This
|
|
329
|
333
|
-- is a not-very-good way to ensure that only the interactive
|
|
330
|
334
|
-- evaluation should generate breakpoints.
|
|
331
|
|
-withBreakAction :: EvalOpts -> MVar ()
|
|
332
|
|
- -> MVar (EvalStatus b)
|
|
|
335
|
+withBreakAction :: EvalOpts
|
|
333
|
336
|
-> Maybe ThreadId -- ^ If resuming, the current threadId
|
|
334
|
337
|
-> IO a -> IO a
|
|
335
|
|
-withBreakAction opts breakMVar statusMVar mtid act
|
|
|
338
|
+withBreakAction opts mtid act
|
|
336
|
339
|
= bracket setBreakAction resetBreakAction (\_ -> act)
|
|
337
|
340
|
where
|
|
338
|
341
|
setBreakAction = do
|
|
339
|
|
- stablePtr <- newStablePtr onBreak
|
|
340
|
|
- poke breakPointIOAction stablePtr
|
|
|
342
|
+ poke breakPointIOAction globalBreakStablePtr -- TODO: This is thread unsafe, as one thread might be accessing this global variable while this thread tries to overwrite it.
|
|
341
|
343
|
when (breakOnException opts) $ poke exceptionFlag 1
|
|
342
|
|
- when (singleStep opts) rts_enableStopNextBreakpointAll
|
|
|
344
|
+ when (singleStep opts) $ do
|
|
|
345
|
+ case mtid of
|
|
|
346
|
+ Nothing -> rts_enableStopNextBreakpointAll
|
|
|
347
|
+ Just (ThreadId tid) -> do
|
|
|
348
|
+ rts_enableStopNextBreakpoint tid
|
|
343
|
349
|
when (stepOut opts) $ do
|
|
344
|
350
|
case mtid of
|
|
345
|
351
|
Nothing -> rts_enableStopNextBreakpointAll -- just enable single-step when no thread is stopped
|
|
346
|
352
|
Just (ThreadId tid) -> do
|
|
347
|
353
|
rts_enableStopAfterReturn tid
|
|
348
|
|
- return stablePtr
|
|
|
354
|
+ return ()
|
|
349
|
355
|
-- Breaking on exceptions is not enabled by default, since it
|
|
350
|
356
|
-- might be a bit surprising. The exception flag is turned off
|
|
351
|
357
|
-- as soon as it is hit, or in resetBreakAction below.
|
|
352
|
358
|
|
|
353
|
|
- onBreak :: BreakpointCallback
|
|
354
|
|
- onBreak info_mod# info_mod_uid# infox# is_exception apStack = do
|
|
355
|
|
- tid <- myThreadId
|
|
356
|
|
- let resume = ResumeContext
|
|
357
|
|
- { resumeBreakMVar = breakMVar
|
|
358
|
|
- , resumeStatusMVar = statusMVar
|
|
359
|
|
- , resumeThreadId = tid }
|
|
360
|
|
- resume_r <- mkRemoteRef resume
|
|
361
|
|
- apStack_r <- mkRemoteRef apStack
|
|
362
|
|
- ccs <- toRemotePtr <$> getCCSOf apStack
|
|
363
|
|
- breakpoint <-
|
|
364
|
|
- if is_exception
|
|
365
|
|
- then pure Nothing
|
|
366
|
|
- else do
|
|
367
|
|
- info_mod <- BS.packCString (Ptr info_mod#)
|
|
368
|
|
- info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
|
|
369
|
|
- pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
|
|
370
|
|
- putMVar statusMVar $ EvalBreak apStack_r breakpoint resume_r ccs
|
|
371
|
|
-
|
|
372
|
|
- -- Block until this thread is resumed (by the thread which took the
|
|
373
|
|
- -- `ResumeContext` from the `statusMVar`).
|
|
374
|
|
- --
|
|
375
|
|
- -- The `onBreak` function must have been called from `rts/Interpreter.c`
|
|
376
|
|
- -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
|
|
377
|
|
- -- returns to the continuation on the stack which is where the interpreter
|
|
378
|
|
- -- was stopped.
|
|
379
|
|
- takeMVar breakMVar
|
|
380
|
|
-
|
|
381
|
|
- resetBreakAction stablePtr = do
|
|
382
|
|
- poke breakPointIOAction noBreakStablePtr
|
|
|
359
|
+ resetBreakAction {-stablePtr-}() = do
|
|
|
360
|
+ -- poke breakPointIOAction noBreakStablePtr -- Never unset it.
|
|
|
361
|
+ -- TODO: What about evaluating a thunk with breakpoints? when did we get "ignoring breakpoint..." before?
|
|
383
|
362
|
poke exceptionFlag 0
|
|
384
|
363
|
rts_disableStopNextBreakpointAll
|
|
385
|
364
|
case mtid of
|
|
386
|
365
|
Just (ThreadId tid) -> rts_disableStopAfterReturn tid
|
|
387
|
366
|
_ -> pure ()
|
|
388
|
|
- freeStablePtr stablePtr
|
|
|
367
|
+ -- freeStablePtr stablePtr
|
|
389
|
368
|
|
|
390
|
369
|
resumeStmt
|
|
391
|
|
- :: EvalOpts -> RemoteRef (ResumeContext [HValueRef])
|
|
|
370
|
+ :: EvalOpts -> RemoteRef ThreadId
|
|
392
|
371
|
-> IO (EvalStatus [HValueRef])
|
|
393
|
|
-resumeStmt opts hvref = do
|
|
394
|
|
- ResumeContext{..} <- localRef hvref
|
|
395
|
|
- withBreakAction opts resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
|
|
|
372
|
+resumeStmt opts rtid = do
|
|
|
373
|
+ resumeThreadId <- localRef rtid
|
|
|
374
|
+ ResumeContext{..} <- getThreadResumeContext resumeThreadId
|
|
|
375
|
+ withBreakAction opts (Just resumeThreadId) $
|
|
396
|
376
|
mask_ $ do
|
|
397
|
377
|
putMVar resumeBreakMVar () -- this awakens the stopped thread...
|
|
398
|
378
|
redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
|
| ... |
... |
@@ -409,20 +389,42 @@ resumeStmt opts hvref = do |
|
409
|
389
|
-- step is necessary to prevent race conditions with
|
|
410
|
390
|
-- -fbreak-on-exception (see #5975).
|
|
411
|
391
|
-- See test break010.
|
|
412
|
|
-abandonStmt :: RemoteRef (ResumeContext [HValueRef]) -> IO ()
|
|
|
392
|
+abandonStmt :: RemoteRef ThreadId -> IO ()
|
|
413
|
393
|
abandonStmt hvref = do
|
|
414
|
|
- ResumeContext{..} <- localRef hvref
|
|
415
|
|
- killThread resumeThreadId
|
|
|
394
|
+ tid <- localRef hvref
|
|
|
395
|
+ ResumeContext{..} <- getThreadResumeContext tid
|
|
|
396
|
+ killThread tid
|
|
416
|
397
|
putMVar resumeBreakMVar ()
|
|
417
|
398
|
_ <- takeMVar resumeStatusMVar
|
|
418
|
399
|
return ()
|
|
419
|
400
|
|
|
420
|
|
-noBreakStablePtr :: StablePtr BreakpointCallback
|
|
421
|
|
-noBreakStablePtr = unsafePerformIO $ newStablePtr noBreakAction
|
|
422
|
|
-
|
|
423
|
|
-noBreakAction :: BreakpointCallback
|
|
424
|
|
-noBreakAction _ _ _ False _ = putStrLn "*** Ignoring breakpoint"
|
|
425
|
|
-noBreakAction _ _ _ True _ = return () -- exception: just continue
|
|
|
401
|
+globalBreakStablePtr :: StablePtr BreakpointCallback
|
|
|
402
|
+globalBreakStablePtr = unsafePerformIO $ newStablePtr globalBreakAction
|
|
|
403
|
+
|
|
|
404
|
+globalBreakAction :: BreakpointCallback
|
|
|
405
|
+globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
|
|
|
406
|
+ tid <- myThreadId
|
|
|
407
|
+ ResumeContext{..} <- getThreadResumeContext tid
|
|
|
408
|
+ resume_r <- mkRemoteRef tid
|
|
|
409
|
+ apStack_r <- mkRemoteRef apStack
|
|
|
410
|
+ ccs <- toRemotePtr <$> getCCSOf apStack
|
|
|
411
|
+ breakpoint <-
|
|
|
412
|
+ if is_exception
|
|
|
413
|
+ then pure Nothing
|
|
|
414
|
+ else do
|
|
|
415
|
+ info_mod <- BS.packCString (Ptr info_mod#)
|
|
|
416
|
+ info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
|
|
|
417
|
+ pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
|
|
|
418
|
+ putMVar resumeStatusMVar $ EvalBreak apStack_r breakpoint resume_r ccs
|
|
|
419
|
+
|
|
|
420
|
+ -- Block until this thread is resumed (by the thread which took the
|
|
|
421
|
+ -- `ResumeContext` from the `statusMVar`).
|
|
|
422
|
+ --
|
|
|
423
|
+ -- The `onBreak` function must have been called from `rts/Interpreter.c`
|
|
|
424
|
+ -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
|
|
|
425
|
+ -- returns to the continuation on the stack which is where the interpreter
|
|
|
426
|
+ -- was stopped.
|
|
|
427
|
+ takeMVar resumeBreakMVar
|
|
426
|
428
|
|
|
427
|
429
|
-- Malloc and copy the bytes. We don't have any way to monitor the
|
|
428
|
430
|
-- lifetime of this memory, so it just leaks.
|
| ... |
... |
@@ -463,3 +465,32 @@ getIdValFromApStack apStack (I# stackDepth) = do |
|
463
|
465
|
case ok of
|
|
464
|
466
|
0# -> return Nothing -- AP_STACK not found
|
|
465
|
467
|
_ -> return (Just (unsafeCoerce# result))
|
|
|
468
|
+
|
|
|
469
|
+clearEvalStatus :: EvalStatus a -> EvalStatus ()
|
|
|
470
|
+clearEvalStatus = \case
|
|
|
471
|
+ EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
|
|
|
472
|
+ EvalComplete w (EvalSuccess _) -> EvalComplete w (EvalSuccess ())
|
|
|
473
|
+ EvalBreak ap mb rt rccs -> EvalBreak ap mb rt rccs
|
|
|
474
|
+
|
|
|
475
|
+--------------------------------------------------------------------------------
|
|
|
476
|
+-- Global Debugger Per-Thread Context
|
|
|
477
|
+-- TODO: when to clean?
|
|
|
478
|
+
|
|
|
479
|
+getThreadResumeContext :: ThreadId -> IO (ResumeContext [HValueRef])
|
|
|
480
|
+getThreadResumeContext tid = modifyMVar globalBreakActionMap $ \gbm0 -> do
|
|
|
481
|
+ case Map.lookup tid gbm0 of
|
|
|
482
|
+ Nothing -> do
|
|
|
483
|
+ r <- ResumeContext <$> newEmptyMVar <*> newEmptyMVar
|
|
|
484
|
+ let !gbm1 = Map.insert tid r gbm0
|
|
|
485
|
+ pure (gbm1, r)
|
|
|
486
|
+ Just ctx -> do
|
|
|
487
|
+ pure (gbm0, ctx)
|
|
|
488
|
+
|
|
|
489
|
+globalBreakActionMap :: MVar (Map.Map ThreadId (ResumeContext [HValueRef]))
|
|
|
490
|
+globalBreakActionMap = unsafePerformIO $ newMVar Map.empty
|
|
|
491
|
+{-# NOINLINE globalBreakActionMap #-}
|
|
|
492
|
+
|
|
|
493
|
+data ResumeContext a = ResumeContext
|
|
|
494
|
+ { resumeBreakMVar :: MVar ()
|
|
|
495
|
+ , resumeStatusMVar :: MVar (EvalStatus a)
|
|
|
496
|
+ } |