crucible-debug-0.1.2.0: src/Lang/Crucible/Debug.hs
{-|
Copyright : (c) Galois, Inc. 2025
Maintainer : Langston Barrett <langston@galois.com>
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Lang.Crucible.Debug
( debugger
, bareDebuggerExt
, bareDebugger
, Inps.Inputs(..)
, Inps.defaultDebuggerInputs
, Inps.prepend
, Outps.prettyOut
, Outps.Outputs(..)
, Outps.defaultDebuggerOutputs
, Arg.Arg(..)
, AType.ArgTypeRepr(..)
, type AType.Breakpoint
, type AType.Exactly
, type AType.Function
, type AType.Int
, type AType.Path
, type AType.Text
, Cmd.Command(Base, Ext)
, Cmd.CommandExt(..)
, Cmd.voidExts
, Cmds.execStateSimState
, Ctxt.EvalResult(..)
, Ctxt.Context(..)
, Ctxt.CommandImpl(..)
, Ctxt.ExtImpl(..)
, Ctxt.voidImpl
, Ctxt.DebuggerState(..)
, Resp.Response(Ok, UserError, XResponse)
, Resp.UserError(NotApplicable)
, Resp.NotApplicable(DoneSimulating, NotYetSimulating)
, Resp.ResponseExt
, type Rgx.Regex
, type Rgx.Empty
, type Rgx.Lit
, type (Rgx.:|)
, type Rgx.Then
, type Rgx.Star
, Rgx.Match(..)
, Rgx.RegexRepr(..)
, Stmt.ParseError
, Stmt.parse
, Stmt.renderParseError
, Trace.Trace
, Trace.TraceEntry(..)
, Trace.latest
, IntrinsicPrinters(..)
, Ctxt.initCtx
, Pers.HasContext(..)
, Ov.debuggerPrepend
, Ov.debuggerQuit
, Ov.debuggerStop
, Ov.debugOverride
, Ov.debugRunOverride
) where
import Control.Applicative qualified as Applicative
import Control.Lens qualified as Lens
import Control.Monad qualified as Monad
import Data.Function ((&))
import Data.Maybe qualified as Maybe
import Data.Parameterized.Map qualified as MapF
import Data.Parameterized.Nonce qualified as Nonce
import Data.Parameterized.Some (Some(Some))
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Void (Void)
import Lang.Crucible.Backend qualified as C
import Lang.Crucible.Backend.Simple qualified as C
import Lang.Crucible.CFG.Extension qualified as C
import Lang.Crucible.Debug.Arg qualified as Arg
import Lang.Crucible.Debug.Arg.Type qualified as AType
import Lang.Crucible.Debug.Breakpoint qualified as Break
import Lang.Crucible.Debug.Command qualified as Cmd
import Lang.Crucible.Debug.Commands qualified as Cmds
import Lang.Crucible.Debug.Complete qualified as Complete
import Lang.Crucible.Debug.Complete (CompletionT)
import Lang.Crucible.Debug.Context (Context, DebuggerState)
import Lang.Crucible.Debug.Context qualified as Ctxt
import Lang.Crucible.Debug.Eval (EvalResult)
import Lang.Crucible.Debug.Eval qualified as Eval
import Lang.Crucible.Debug.Inputs (Inputs)
import Lang.Crucible.Debug.Inputs qualified as Inps
import Lang.Crucible.Debug.Outputs (Outputs)
import Lang.Crucible.Debug.Outputs qualified as Outps
import Lang.Crucible.Debug.Override qualified as Ov
import Lang.Crucible.Debug.Personality qualified as Pers
import Lang.Crucible.Debug.Regex qualified as Rgx
import Lang.Crucible.Debug.Response qualified as Resp
import Lang.Crucible.Debug.Response (Response)
import Lang.Crucible.Debug.Statement (Statement)
import Lang.Crucible.Debug.Statement qualified as Stmt
import Lang.Crucible.Debug.Style qualified as Style
import Lang.Crucible.Debug.Style (StyleT)
import Lang.Crucible.Debug.Trace qualified as Trace
import Lang.Crucible.FunctionHandle qualified as C
import Lang.Crucible.Pretty (IntrinsicPrinters(..))
import Lang.Crucible.Simulator.CallFrame qualified as C
import Lang.Crucible.Simulator.EvalStmt qualified as C
import Lang.Crucible.Simulator.ExecutionTree qualified as C
import Lang.Crucible.Simulator qualified as C
import Lang.Crucible.Syntax.Concrete qualified as C
import Lang.Crucible.Types qualified as C
import Prettyprinter qualified as PP
import Prettyprinter.Render.Text qualified as PP
import System.Exit qualified as Exit
import System.IO (stdout)
import What4.Expr qualified as W4
import What4.Interface qualified as W4
-- Useful for debugging
_printState :: C.ExecState p sym ext rtp -> Text
_printState =
\case
C.AbortState {} -> "AbortState"
C.CallState {} -> "CallState"
C.TailCallState {} -> "TailCallState"
C.ReturnState {} -> "ReturnState"
C.ResultState {} -> "ResultState"
C.InitialState {} -> "InitialState"
C.OverrideState {} -> "OverrideState"
C.RunningState {} -> "RunningState"
C.SymbolicBranchState {} -> "SymbolicBranchState"
C.BranchMergeState {} -> "BranchMergeState"
C.ControlTransferState {} -> "ControlTransferState"
C.UnwindCallState {} -> "UnwindCallState"
stepState ::
Context cExt sym ext t ->
C.ExecState p sym ext rtp ->
IO DebuggerState
stepState ctx =
\case
C.AbortState {} ->
if Ctxt.dbgStopOnAbort ctx
then pure Ctxt.Stopped
else pure initState
C.CallState _retHandler frm _st -> do
C.SomeHandle hdl <- pure (C.resolvedCallHandle frm)
stepCall hdl
C.TailCallState _vfv frm _st -> do
C.SomeHandle hdl <- pure (C.resolvedCallHandle frm)
stepCall hdl
C.ReturnState _fnm _vfv _ret _st ->
case initState of
Ctxt.Running Ctxt.Finish -> pure Ctxt.Stopped
_ -> pure initState
C.RunningState (C.RunBlockEnd _) simState -> do
let fr = simState Lens.^. C.stateCrucibleFrame
C.CallFrame { C._frameCFG = cfg, C._frameBlockID = blkId } <- pure fr
let ent = Trace.TraceEntry cfg blkId
Trace.append (Ctxt.dbgTrace ctx) ent
pure initState
-- Nothing to do for these...
C.InitialState {} -> pure initState
C.OverrideState {} -> pure initState
C.ResultState {} -> pure initState
C.RunningState {} -> pure initState
C.SymbolicBranchState {} -> pure initState
-- These are mostly implementation details...
C.BranchMergeState {} -> pure initState
C.ControlTransferState {} -> pure initState
C.UnwindCallState {} -> pure initState
where
initState = Ctxt.dbgState ctx
stepCall :: C.FnHandle args ret -> IO DebuggerState
stepCall hdl = do
let b = Break.BreakFunction (C.handleName hdl)
if Set.member b (Ctxt.dbgBreakpoints ctx)
then pure Ctxt.Stopped
else pure initState
mergeResults ::
C.ExecutionFeatureResult p sym ext ret ->
C.ExecutionFeatureResult p sym ext ret ->
C.ExecutionFeatureResult p sym ext ret
mergeResults old new =
case old of
C.ExecutionFeatureNoChange -> new
_ ->
case new of
C.ExecutionFeatureNoChange -> old
_ -> new
resultState ::
C.ExecutionFeatureResult p sym ext ret ->
Maybe (C.ExecState p sym ext ret)
resultState =
\case
C.ExecutionFeatureNoChange -> Nothing
C.ExecutionFeatureModifiedState s -> Just s
C.ExecutionFeatureNewState s -> Just s
stopped ::
(sym ~ W4.ExprBuilder s st fs) =>
C.IsSymInterface sym =>
C.IsSyntaxExtension ext =>
(?parserHooks :: C.ParserHooks ext) =>
PP.Pretty cExt =>
W4.IsExpr (W4.SymExpr sym) =>
Context cExt sym ext t ->
Ctxt.ExtImpl cExt p sym ext t ->
C.ExecState p sym ext (C.RegEntry sym t) ->
IO (EvalResult cExt p sym ext t)
stopped ctx0 ext execState0 = go ctx0 execState0 C.ExecutionFeatureNoChange
where
go c0 s0 r = do
let cEnv = Ctxt.toCompletionEnv c0 s0
let sEnv = Ctxt.toStyleEnv c0 s0
stmt <- Style.runStyleM sEnv (Complete.runCompletionM cEnv (Inps.recv (Ctxt.dbgInputs c0)))
result0 <- Eval.eval c0 ext s0 stmt
let featResult = mergeResults r (Eval.evalFeatureResult result0)
let result = result0 { Eval.evalFeatureResult = featResult }
let s = Maybe.fromMaybe s0 (resultState featResult)
let c = Eval.evalCtx result
Outps.send (Ctxt.dbgOutputs c) (Eval.evalResp result)
case Ctxt.dbgState c of
Ctxt.Quit -> pure result
Ctxt.Running {} -> pure result
Ctxt.Stopped -> go c s featResult
dispatch ::
(sym ~ W4.ExprBuilder s st fs) =>
C.IsSymInterface sym =>
C.IsSyntaxExtension ext =>
W4.IsExpr (W4.SymExpr sym) =>
(?parserHooks :: C.ParserHooks ext) =>
PP.Pretty cExt =>
Context cExt sym ext t ->
Ctxt.ExtImpl cExt p sym ext t ->
C.ExecState p sym ext (C.RegEntry sym t) ->
IO (Context cExt sym ext t, C.ExecutionFeatureResult p sym ext (C.RegEntry sym t))
dispatch ctx0 ext execState =
case Ctxt.dbgState ctx0 of
Ctxt.Quit ->
pure (ctx0, C.ExecutionFeatureNoChange)
Ctxt.Running {} | C.ResultState {} <- execState -> do
let ctx = ctx0 { Ctxt.dbgState = Ctxt.Stopped }
dispatch ctx ext execState
Ctxt.Running (Ctxt.Step i) | i <= 1 -> do
let ctx = ctx0 { Ctxt.dbgState = Ctxt.Stopped }
dispatch ctx ext execState
Ctxt.Running (Ctxt.Step i) -> do
let ctx = ctx0 { Ctxt.dbgState = Ctxt.Running (Ctxt.Step (i - 1)) }
state <- stepState ctx execState
let ctx' = ctx0 { Ctxt.dbgState = state }
pure (ctx', C.ExecutionFeatureNoChange)
Ctxt.Running {} -> do
state <- stepState ctx0 execState
let ctx = ctx0 { Ctxt.dbgState = state }
pure (ctx, C.ExecutionFeatureNoChange)
Ctxt.Stopped -> do
result <- stopped ctx0 ext execState
pure (Eval.evalCtx result, Eval.evalFeatureResult result)
debugger ::
Pers.HasContext p cExt sym ext t =>
(sym ~ W4.ExprBuilder s st fs) =>
C.IsSymInterface sym =>
C.IsSyntaxExtension ext =>
W4.IsExpr (W4.SymExpr sym) =>
(?parserHooks :: C.ParserHooks ext) =>
PP.Pretty cExt =>
Ctxt.ExtImpl cExt p sym ext t ->
C.ExecutionFeature p sym ext (C.RegEntry sym t)
debugger ext =
C.ExecutionFeature $ \execState -> do
let simCtx = execState Lens.^. Lens.to C.execStateContext
let ctx0 = simCtx Lens.^. C.cruciblePersonality . Pers.context
(ctx, featResult) <- dispatch ctx0 ext execState
let simCtx' =
case featResult of
C.ExecutionFeatureNoChange -> simCtx
C.ExecutionFeatureModifiedState execState' ->
C.execStateContext execState'
C.ExecutionFeatureNewState execState' ->
C.execStateContext execState'
let simCtx'' = simCtx' & C.cruciblePersonality . Pers.context Lens..~ ctx
pure $
case featResult of
C.ExecutionFeatureNoChange ->
C.ExecutionFeatureModifiedState
(C.setExecStateContext simCtx'' execState)
C.ExecutionFeatureModifiedState execState' ->
C.ExecutionFeatureModifiedState
(C.setExecStateContext simCtx'' execState')
C.ExecutionFeatureNewState execState' ->
C.ExecutionFeatureNewState
(C.setExecStateContext simCtx'' execState')
-- | Like 'bareDebugger', but with a syntax extension
bareDebuggerExt ::
C.IsSyntaxExtension ext =>
(?parserHooks :: C.ParserHooks ext) =>
PP.Pretty cExt =>
Cmd.CommandExt cExt ->
(forall p sym t. C.IsSymInterface sym => Ctxt.ExtImpl cExt p sym ext t) ->
(forall p sym. C.IsSymInterface sym => C.ExtensionImpl p sym ext) ->
Inputs (CompletionT cExt (StyleT cExt IO)) (Statement cExt) ->
Outputs IO (Response cExt) ->
(Text -> IO ()) ->
IO ()
bareDebuggerExt cExts cEval extImpl inps outps logger = do
Some nonceGen <- Nonce.newIONonceGenerator
ha <- C.newHandleAllocator
sym <- W4.newExprBuilder W4.FloatIEEERepr W4.EmptyExprBuilderState nonceGen
bak <- C.newSimpleBackend sym
let retType = C.UnitRepr
let fns = C.FnBindings C.emptyHandleMap
initCtx <- do
let iFns = IntrinsicPrinters MapF.empty
Ctxt.initCtx cExts iFns inps outps retType
let simCtx = C.initSimContext bak C.emptyIntrinsicTypes ha stdout fns extImpl initCtx
let ov = C.runOverrideSim retType $ return ()
let simSt = C.InitialState simCtx C.emptyGlobals C.defaultAbortHandler retType ov
Monad.void $ C.executeCrucible [debugger cEval] simSt
C.getProofObligations bak >>= \case
Nothing -> pure ()
Just obls -> do
logger "There were unhandled proof obligations! Try `prove` and `clear`."
let goals = C.goalsToList obls
prettyGoals <- mapM (C.ppProofObligation sym) goals
let render = PP.renderStrict . PP.layoutSmart PP.defaultLayoutOptions
logger (render (PP.vcat prettyGoals))
Exit.exitFailure
-- | Start a debugger instance in an empty Crucible program.
--
-- The Crucible program simply returns the unit value. The idea is to use the
-- @Load@ and @Call@ commands to actually run some Crucible CFG(s).
--
-- Proves obligations using Z3.
bareDebugger ::
Inputs (CompletionT Void (StyleT Void IO)) (Statement Void) ->
Outputs IO (Response Void) ->
(Text -> IO ()) ->
IO ()
bareDebugger inps outps logger =
let ?parserHooks = C.ParserHooks Applicative.empty Applicative.empty in
bareDebuggerExt
Cmd.voidExts
Ctxt.voidImpl
C.emptyExtensionImpl
inps
outps
logger