Skip to content

Commit abf9a62

Browse files
committed
Progress towards callstacks and multi-threaded debugging
- Use proper threadId when stopped at a breakpoint - Get threadId as stackframe question - Report actual threads on 'getThreads' - Filter 'getThreads' threads by not-dead ones - package: Add ghc-heap dependency
1 parent 0173c35 commit abf9a62

File tree

12 files changed

+371
-35
lines changed

12 files changed

+371
-35
lines changed

haskell-debugger.cabal

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ library
6262
GHC.Debugger.Logger,
6363
GHC.Debugger.Stopped,
6464
GHC.Debugger.Stopped.Variables,
65-
GHC.Debugger.Utils,
65+
6666
GHC.Debugger.Runtime,
6767
GHC.Debugger.Runtime.Instances,
6868
GHC.Debugger.Runtime.Instances.Discover,
@@ -71,7 +71,11 @@ library
7171
GHC.Debugger.Runtime.Term.Key,
7272
GHC.Debugger.Runtime.Term.Cache,
7373

74+
GHC.Debugger.Runtime.Thread,
75+
GHC.Debugger.Runtime.Thread.Map,
76+
7477
GHC.Debugger.Monad,
78+
GHC.Debugger.Utils,
7579

7680
GHC.Debugger.Session,
7781
GHC.Debugger.Session.Builtin,
@@ -82,6 +86,7 @@ library
8286
ghc >= 9.14 && < 9.16, ghci >= 9.14 && < 9.16,
8387
ghc-boot-th >= 9.14 && < 9.16,
8488
ghc-boot >= 9.14 && < 9.16,
89+
ghc-heap >= 9.14 && < 9.16,
8590
array >= 0.5.8 && < 0.6,
8691
containers >= 0.7 && < 0.9,
8792
mtl >= 2.3 && < 3,

haskell-debugger/GHC/Debugger.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ execute recorder = \case
2626
DidSetBreakpoint <$> setBreakpoint brk (condBreakEnableStatus hitCount condition)
2727
DelBreakpoint bp -> DidRemoveBreakpoint <$> setBreakpoint bp BreakpointDisabled
2828
GetBreakpointsAt bp -> DidGetBreakpoints <$> getBreakpointsAt bp
29-
GetStacktrace -> GotStacktrace <$> getStacktrace
29+
GetThreads -> GotThreads <$> getThreads
30+
GetStacktrace i -> GotStacktrace <$> getStacktrace i
3031
GetScopes -> GotScopes <$> getScopes
3132
GetVariables kind -> GotVariables <$> getVariables kind
3233
DoEval exp_s -> DidEval <$> doEval exp_s

haskell-debugger/GHC/Debugger/Evaluation.hs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import GHC.Debugger.Utils
4343
import GHC.Debugger.Interface.Messages
4444
import GHC.Debugger.Logger as Logger
4545
import qualified GHC.Debugger.Breakpoint.Map as BM
46+
import GHC.Debugger.Runtime.Thread
4647

4748
data EvalLog
4849
= LogEvalModule GHC.Module
@@ -207,10 +208,12 @@ handleExecResult = \case
207208
Just VarInfo{varValue, varType, varRef} -> do
208209
return (EvalCompleted varValue varType varRef)
209210
Nothing -> liftIO $ fail "doEval failed"
210-
ExecBreak {breakNames = _, breakPointId = Nothing} ->
211+
ExecBreak {breakNames = _, breakPointId = Nothing} -> do
211212
-- Stopped at an exception
212213
-- TODO: force the exception to display string with Backtrace?
213-
return EvalStopped{breakId = Nothing}
214+
rt_id <- getRemoteThreadIdFromContext
215+
return EvalStopped{ breakId = Nothing
216+
, breakThread = rt_id }
214217
ExecBreak {breakNames = _, breakPointId = Just bid} -> do
215218
bm <- liftIO . readIORef =<< asks activeBreakpoints
216219
case BM.lookup bid bm of
@@ -222,8 +225,10 @@ handleExecResult = \case
222225
EvalStopped{} -> error "impossible for doEval"
223226
EvalCompleted { resultVal, resultType } ->
224227
if resultType == "Bool" then do
225-
if resultVal == "True" then
226-
return EvalStopped{breakId = Just bid}
228+
if resultVal == "True" then do
229+
rt_id <- getRemoteThreadIdFromContext
230+
return EvalStopped{ breakId = Just bid
231+
, breakThread = rt_id }
227232
else
228233
resume
229234
else do
@@ -237,7 +242,10 @@ handleExecResult = \case
237242
resume
238243

239244
-- Unconditionally 'EvalStopped' in all other cases
240-
_ -> return EvalStopped{breakId = Just bid}
245+
_ -> do
246+
rt_id <- getRemoteThreadIdFromContext
247+
return EvalStopped{ breakId = Just bid
248+
, breakThread = rt_id }
241249

242250
-- | Get the value and type of a given 'Name' as rendered strings in 'VarInfo'.
243251
inspectName :: Name -> Debugger (Maybe VarInfo)
@@ -248,3 +256,9 @@ inspectName n = do
248256
pure Nothing
249257
Just tt -> Just <$> tyThingToVarInfo tt
250258

259+
getRemoteThreadIdFromContext :: Debugger RemoteThreadId
260+
getRemoteThreadIdFromContext = do
261+
GHC.getResumeContext >>= \case
262+
resume1:_ ->
263+
getRemoteThreadIdFromRemoteContext $ GHC.resumeContext resume1
264+
_ -> error "No resumes but stopped?!?"

haskell-debugger/GHC/Debugger/Interface/Messages.hs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ data Command
3939
-- | Clear all function breakpoints
4040
| ClearFunctionBreakpoints
4141

42+
-- | Get all threads
43+
| GetThreads
44+
4245
-- | Get the evaluation stacktrace until the current breakpoint.
43-
| GetStacktrace
46+
| GetStacktrace RemoteThreadId
4447

4548
-- | Get the list of available scopes at the current breakpoint
4649
| GetScopes
@@ -208,6 +211,7 @@ data Response
208211
| DidContinue EvalResult
209212
| DidStep EvalResult
210213
| DidExec EvalResult
214+
| GotThreads [DebuggeeThread]
211215
| GotStacktrace [StackFrame]
212216
| GotScopes [ScopeInfo]
213217
| GotVariables (Either VarInfo [VarInfo])
@@ -234,6 +238,16 @@ data BreakFound
234238
| ManyBreaksFound [BreakFound]
235239
deriving (Show)
236240

241+
-- | A reference to a remote thread by remote id
242+
-- See 'getRemoteThreadId'.
243+
newtype RemoteThreadId = RemoteThreadId
244+
{ remoteThreadIntRef :: Int
245+
-- ^ The number identifier of the thread on the (remote) interpreter. To
246+
-- find the proper remote 'ThreadId' corresponding to this numeric
247+
-- identifier, lookup the 'remoteThreadIntRef' in the 'ThreadMap'
248+
}
249+
deriving Show
250+
237251
data EvalResult
238252
= EvalCompleted { resultVal :: String
239253
, resultType :: String
@@ -243,11 +257,24 @@ data EvalResult
243257
-- that the user can expand as a normal variable.
244258
}
245259
| EvalException { resultVal :: String, resultType :: String }
246-
| EvalStopped { breakId :: Maybe GHC.InternalBreakpointId {-^ Did we stop at an exception (@Nothing@) or at a breakpoint (@Just@)? -} }
260+
| EvalStopped { breakId :: Maybe GHC.InternalBreakpointId
261+
-- ^ Did we stop at an exception (@Nothing@) or at a breakpoint (@Just@)?
262+
, breakThread :: RemoteThreadId
263+
-- ^ In which thread did we hit the breakpoint?
264+
}
247265
-- | Evaluation failed for some reason other than completed/completed-with-exception/stopped.
248266
| EvalAbortedWith String
249267
deriving (Show)
250268

269+
data DebuggeeThread
270+
= DebuggeeThread
271+
{ tId :: !RemoteThreadId
272+
-- ^ An identifier for a thread on the (possibly remote) debuggee process
273+
, tName :: !(Maybe String)
274+
-- ^ Thread label, if there is one
275+
}
276+
deriving (Show)
277+
251278
data StackFrame
252279
= StackFrame
253280
{ name :: String

haskell-debugger/GHC/Debugger/Monad.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import GHC.Debugger.Runtime.Term.Key
5959
import GHC.Debugger.Session
6060
import GHC.Debugger.Session.Builtin
6161
import qualified GHC.Debugger.Breakpoint.Map as BM
62+
import qualified GHC.Debugger.Runtime.Thread.Map as TM
6263

6364
import {-# SOURCE #-} GHC.Debugger.Runtime.Instances.Discover (RuntimeInstancesCache, emptyRuntimeInstancesCache)
6465

@@ -90,6 +91,9 @@ data DebuggerState = DebuggerState
9091
, rtinstancesCache :: IORef RuntimeInstancesCache
9192
-- ^ RuntimeInstancesCache
9293

94+
, threadMap :: IORef TM.ThreadMap
95+
-- ^ 'ThreadMap' for threads spawned by the debuggee
96+
9397
, genUniq :: IORef Int
9498
-- ^ Generates unique ints
9599

@@ -478,6 +482,7 @@ initialDebuggerState l hsDbgViewUid =
478482
DebuggerState <$> liftIO (newIORef BM.empty)
479483
<*> liftIO (newIORef mempty)
480484
<*> liftIO (newIORef emptyRuntimeInstancesCache)
485+
<*> liftIO (newIORef TM.emptyThreadMap)
481486
<*> liftIO (newIORef 0)
482487
<*> pure hsDbgViewUid
483488
<*> pure l

haskell-debugger/GHC/Debugger/Runtime/Instances/Discover.hs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ module GHC.Debugger.Runtime.Instances.Discover
77
, RuntimeInstancesCache
88
, getDebugViewInstance
99
, emptyRuntimeInstancesCache
10+
11+
-- * Finding runtime instances utils
12+
, compileAndLoadMthd
1013
) where
1114

1215
import Data.IORef
@@ -105,7 +108,7 @@ findDebugViewInstance needle_ty = do
105108
case mhdv_uid of
106109
Just hdv_uid -> do
107110
let modl = mkModule (RealUnit (Definite hdv_uid)) debuggerViewClassModName
108-
let mthdRdrName mthStr = mkOrig modl (mkVarOcc mthStr)
111+
let mthdRdrName mthStr = mkOrig modl (mkVarOcc mthStr) :: RdrName
109112

110113
(err_msgs, res) <- liftIO $ runTcInteractive hsc_env $ do
111114

@@ -119,16 +122,16 @@ findDebugViewInstance needle_ty = do
119122

120123
-- Try to compile and load an expression for all methods of `DebugView`
121124
-- applied to the dictionary for the given Type (`needle_ty`)
122-
let debugValueMN = mthdRdrName "debugValueIOWrapper"
123-
debugFieldsMN = mthdRdrName "debugFieldsIOWrapper"
125+
let debugValueME = nlHsVar $ mthdRdrName "debugValueIOWrapper"
126+
debugFieldsME = nlHsVar $ mthdRdrName "debugFieldsIOWrapper"
124127
debugValueWrapperMT =
125128
mkVisFunTyMany needle_ty $
126129
mkTyConApp ioTyCon [mkListTy varValueIOTy]
127130
debugFieldsWrapperMT =
128131
mkVisFunTyMany needle_ty $
129132
mkTyConApp ioTyCon [mkListTy varFieldsIOTy]
130-
!debugValue_fval <- compileAndLoadMthd debugValueMN debugValueWrapperMT
131-
!debugFields_fval <- compileAndLoadMthd debugFieldsMN debugFieldsWrapperMT
133+
!debugValue_fval <- compileAndLoadMthd debugValueME debugValueWrapperMT
134+
!debugFields_fval <- compileAndLoadMthd debugFieldsME debugFieldsWrapperMT
132135

133136
let eval_opts = initEvalOpts (hsc_dflags hsc_env) EvalStepNone
134137
interp = hscInterp hsc_env
@@ -170,18 +173,16 @@ findDebugViewInstance needle_ty = do
170173

171174
-- | Try to compile and load a class method for the given type.
172175
--
173-
-- E.g. @compileAndLoadMthd "debugValue" (<ty> -> VarValue)@ returns the
174-
-- foreign value for an expression @debugValue@ applied to the dictionary for
175-
-- the requested type.
176-
compileAndLoadMthd :: RdrName -- ^ Name of method/name of function that takes dictionary
177-
-> Type -- ^ The final type of expr when funct is alredy applied to dict
176+
-- E.g. @compileAndLoadMthd (nlHsVar "foo") <ty>@ returns the
177+
-- foreign value for an expression @foo@ applied to the dictionary required to
178+
-- produce the final requested type
179+
compileAndLoadMthd :: LHsExpr GhcPs -- ^ Expr of method/expr that takes dictionary
180+
-> Type -- ^ The final type of expr when funct is alredy applied to dict
178181
-> TcM ForeignHValue
179-
compileAndLoadMthd mthName mthTy = do
182+
compileAndLoadMthd expr mthTy = do
180183
hsc_env <- getTopEnv
181184

182-
let expr = nlHsVar mthName
183-
184-
-- Rn, Tc, desugar applied to DebugView dictionary
185+
-- Rn, Tc, desugar applied to dictionary
185186
(expr', _) <- rnExpr (unLoc expr)
186187
(expr'', wcs) <- captureConstraints $ tcExpr expr' (Check mthTy)
187188
ev <- simplifyTop wcs

0 commit comments

Comments
 (0)