Skip to content

Commit

Permalink
Implement stepping out of functions from a paused symbolic execution …
Browse files Browse the repository at this point in the history
…frame

This change completes Vikraman's sketch of the functionality.  It:

- Removes the error calls (replacing them with logging)
- Simplifies the single stepping implementation
- Redesigns the symbolic execution state management and rendering

The issue with the state management was that symbolic execution state was
duplicated between the core and the brick UI in a way that allowed them to get
out of sync.  This mean that changes to the core state (e.g., as users advance
the suspended state via commands) were not reflected in the UI (as the UI state
was stale).  To fix this, all updates to the symbolic execution state now go
through a new distinguished message (the handler for which applies the update).
This allows the frontends to apply additional processing to observe all of these
updates and regenerate the UI state whenever there is a relevant update.  This
also simplifies the symbolic execution widget, which now only returns its own
updated state (and handles any state updates via message passing).

Some other changes included with this change:
- There is an option for adding calling context information for generated log entries
- Extracts the core symbolic execution state handlers and puts them back into
the core where they belong
- Make symbolic execution session state updates safer (remove mergeSessionState)

Closes #74 (integrated with these changes)
  • Loading branch information
travitch committed Dec 29, 2020
1 parent f8363af commit bc9a7c6
Show file tree
Hide file tree
Showing 23 changed files with 577 additions and 337 deletions.
25 changes: 15 additions & 10 deletions surveyor-brick/src/Surveyor/Brick.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Control.Lens ( (&), (^.), (^?), (.~) )
import qualified Control.Lens as L
import Control.Monad ( join )
import qualified Data.Foldable as F
import qualified Data.Map.Strict as Map
import Data.Maybe ( fromMaybe, mapMaybe, isJust )
import qualified Data.Parameterized.Classes as PC
import qualified Data.Parameterized.Map as MapF
Expand Down Expand Up @@ -99,8 +100,8 @@ drawDiagnostics diags = B.viewport DiagnosticView B.Vertical body
case comp of
C.Unspecified -> ""
C.Loader -> "Loader" @? "log-component"
C.EventHandler nm -> PPT.renderStrict (PP.layoutCompact ("Event[" PP.<+> PP.pretty nm PP.<+> "]")) @? "log-component"
C.CommandCallback nm -> PPT.renderStrict (PP.layoutCompact ("Command[" PP.<+> PP.pretty nm PP.<+> "]")) @? "log-component"
C.EventHandler nm -> PPT.renderStrict (PP.layoutCompact ("Event[" <> PP.pretty nm <> "]")) @? "log-component"
C.CommandCallback nm -> PPT.renderStrict (PP.layoutCompact ("Command[" <> PP.pretty nm <> "]")) @? "log-component"
C.EchoAreaUpdate -> ""

renderTime tm = PPT.renderStrict (PP.layoutCompact (PP.pretty tm)) @? "log-time"
Expand Down Expand Up @@ -204,10 +205,14 @@ drawUIMode binFileName archState s uim =
| otherwise -> drawAppShell s (B.txt (T.pack ("Missing function view for IR: " ++ show repr)))
C.SemanticsViewer ->
drawAppShell s (ISV.renderInstructionSemanticsViewer binfo (archState ^. C.contextG) ISV.instructionSemanticsViewer)
C.SymbolicExecutionManager -> do
let valNames = s ^. C.lValueNames
let sem = archState ^. BH.symbolicExecutionManagerG
drawAppShell s (SEM.renderSymbolicExecutionManager sem valNames)
C.SymbolicExecutionManager
| Just sessionID <- archState ^? C.contextL . C.currentContext . C.symExecSessionIDL
, Just sessions <- archState ^? C.symExStateL
, Just (Some symExState) <- C.lookupSessionState sessions sessionID
, Just manager <- archState ^. BH.symbolicExecutionStateG . L.at sessionID -> do
let valNames = s ^. C.lValueNames
drawAppShell s (SEM.renderSymbolicExecutionManager manager symExState valNames)
| otherwise -> drawAppShell s (B.txt (T.pack "No symbolic execution state"))
where
binfo = C.sAnalysisResult archState

Expand Down Expand Up @@ -346,8 +351,8 @@ stateFromContext ng mkAnalysisResult chan simState simData = do
, SBE.sBlockViewers = MapF.fromList blockViewers
, SBE.sFunctionViewer = MapF.fromList (funcViewers ares)
, SBE.sFunctionSelector = FS.functionSelector (const (return ())) focusedListAttr []
, SBE.sSymbolicExecutionManager =
SEM.symbolicExecutionManager (Some symbolicExecutionState)
, SBE.sSymbolicExecutionState =
Map.singleton sesID (SEM.symbolicExecutionManager (Some symbolicExecutionState))
}
tc0 <- C.newTranslationCache
ctxStk <- contextStackFromState ng tc0 ares sesID simState pfcfg
Expand Down Expand Up @@ -408,8 +413,8 @@ stateFromContext ng mkAnalysisResult chan simState simData = do
, SBE.sBlockViewers = MapF.fromList blockViewers
, SBE.sFunctionViewer = MapF.fromList (funcViewers ares)
, SBE.sFunctionSelector = FS.functionSelector (const (return ())) focusedListAttr []
, SBE.sSymbolicExecutionManager =
SEM.symbolicExecutionManager (Some symbolicExecutionState)
, SBE.sSymbolicExecutionState =
Map.singleton sesID (SEM.symbolicExecutionManager (Some symbolicExecutionState))
}
let archState = C.ArchState { C.sAnalysisResult = ares
-- FIXME: Pick a context based on the stack in the simulator
Expand Down
13 changes: 7 additions & 6 deletions surveyor-brick/src/Surveyor/Brick/Extension.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ module Surveyor.Brick.Extension (
blockViewerG,
functionViewersL,
functionViewerG,
symbolicExecutionManagerL,
symbolicExecutionManagerG
symbolicExecutionStateL,
symbolicExecutionStateG
) where

import Control.Lens ( (^.), (&), (%~) )
import qualified Control.Lens as L
import qualified Control.NF as NF
import Data.Kind ( Type )
import qualified Data.Map.Strict as Map
import qualified Data.Parameterized.Map as MapF
import qualified Data.Parameterized.Nonce as PN
import qualified Data.Text as T
Expand Down Expand Up @@ -71,7 +72,7 @@ data BrickUIState arch s =
, sBlockSelector :: !(BS.BlockSelector arch s)
, sBlockViewers :: !(MapF.MapF (C.IRRepr arch) (BV.BlockViewer arch s))
, sFunctionViewer :: !(MapF.MapF (C.IRRepr arch) (FV.FunctionViewer arch s))
, sSymbolicExecutionManager :: !(SEM.SymbolicExecutionManager (C.Events s (C.S BrickUIExtension BrickUIState)) arch s)
, sSymbolicExecutionState :: !(Map.Map (C.SessionID s) (SEM.SymbolicExecutionManager (C.Events s (C.S BrickUIExtension BrickUIState)) arch s))
}
deriving (Generic)

Expand All @@ -84,7 +85,7 @@ L.makeLensesFor
, ("sBlockSelector", "blockSelectorL")
, ("sBlockViewers", "blockViewersL")
, ("sFunctionViewer", "functionViewersL")
, ("sSymbolicExecutionManager", "symbolicExecutionManagerL") ]
, ("sSymbolicExecutionState", "symbolicExecutionStateL") ]
''BrickUIState

type instance C.EventExtension (C.S BrickUIExtension BrickUIState) = BrickUIEvent
Expand Down Expand Up @@ -135,5 +136,5 @@ blockViewerG rep = L.to (\as -> MapF.lookup rep (as ^. C.lUIState . blockViewers
functionViewerG :: C.IRRepr arch ir -> L.Getter (C.ArchState BrickUIState arch s) (Maybe (FV.FunctionViewer arch s ir))
functionViewerG rep = L.to (\as -> MapF.lookup rep (as ^. C.lUIState . functionViewersL))

symbolicExecutionManagerG :: L.Getter (C.ArchState BrickUIState arch s) (SEM.SymbolicExecutionManager (C.Events s (C.S BrickUIExtension BrickUIState)) arch s)
symbolicExecutionManagerG = L.to (^. C.lUIState . symbolicExecutionManagerL)
symbolicExecutionStateG :: L.Getter (C.ArchState BrickUIState arch s) (Map.Map (C.SessionID s) (SEM.SymbolicExecutionManager (C.Events s (C.S BrickUIExtension BrickUIState)) arch s))
symbolicExecutionStateG = L.to (^. C.lUIState . symbolicExecutionStateL)
26 changes: 17 additions & 9 deletions surveyor-brick/src/Surveyor/Brick/Handlers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,19 @@ module Surveyor.Brick.Handlers (
SBE.functionSelectorG,
SBE.functionViewerG,
SBE.minibufferG,
SBE.symbolicExecutionManagerG
SBE.symbolicExecutionStateG
) where

import qualified Brick as B
import Control.Lens ( (&), (^.), (.~), (%~), (^?), _Just )
import qualified Control.Lens as L
import Control.Monad.IO.Class ( liftIO )
import qualified Control.NF as NF
import qualified Data.Foldable as F
import qualified Data.Map.Strict as Map
import Data.Parameterized.Classes
import qualified Data.Parameterized.List as PL
import Data.Parameterized.Some ( viewSome )
import Data.Parameterized.Some ( Some(..) )
import qualified Graphics.Vty as V

import Prelude
Expand Down Expand Up @@ -111,12 +113,13 @@ handleVtyEvent s0@(C.State s) evt
let s' = s & C.lArchState . _Just . C.contextL .~ cstk'
B.continue $! C.State s'
C.SomeUIMode C.SymbolicExecutionManager
| Just archState <- s ^. C.lArchState -> do
let manager0 = archState ^. C.lUIState . SBE.symbolicExecutionManagerL
manager1 <- SEM.handleSymbolicExecutionManagerEvent (B.VtyEvent evt) manager0
let st1 = SEM.symbolicExecutionManagerState manager1
let s' = s & C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionManagerL .~ manager1
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState (viewSome C.singleSessionState st1)
| Just archState <- s ^. C.lArchState
, Just sessionID <- archState ^? C.contextL . C.currentContext . C.symExecSessionIDL
, Just sessions <- archState ^? C.symExStateL
, Just (Some symExState) <- C.lookupSessionState sessions sessionID
, Just manager0 <- archState ^. C.lUIState . SBE.symbolicExecutionStateL . L.at sessionID -> do
manager1 <- SEM.handleSymbolicExecutionManagerEvent s (B.VtyEvent evt) manager0 symExState
let s' = s & C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionStateL %~ Map.insert sessionID manager1
B.continue $! C.State s'
C.SomeUIMode _m -> B.continue s0

Expand All @@ -128,7 +131,12 @@ handleCustomEvent :: (C.Architecture arch s)
handleCustomEvent s0 evt =
case evt of
C.LoadEvent le -> handleLoadEvent s0 le
C.SymbolicExecutionEvent se -> handleSymbolicExecutionEvent s0 se
C.SymbolicExecutionEvent se -> do
-- Delegate to the core handler, then add a hook to do any special
-- handling required to keep the UI in sync (using the brick UI specific
-- handler)
C.State s1 <- C.handleSymbolicExecutionEvent s0 se
handleSymbolicExecutionEvent s1 se
C.LoggingEvent le -> do
s1 <- C.handleLoggingEvent s0 le
B.continue s1
Expand Down
7 changes: 4 additions & 3 deletions surveyor-brick/src/Surveyor/Brick/Handlers/Load.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import qualified Brick as B
import Control.Lens ( (&), (^.), (.~), (%~), _Just )
import Control.Monad.IO.Class ( liftIO )
import qualified Data.Foldable as F
import qualified Data.Map.Strict as Map
import qualified Data.Parameterized.Classes as PC
import qualified Data.Parameterized.Map as MapF
import Data.Parameterized.Some ( Some(..) )
Expand Down Expand Up @@ -123,7 +124,7 @@ stateFromAnalysisResult s0 ares newDiags state uiMode = do
defFunc : _ -> do
let pushContext (newContext, sessState) oldState =
oldState & C.lArchState . _Just . C.contextL %~ C.pushContext newContext
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState sessState
& C.lArchState . _Just . C.symExStateL %~ C.updateSessionState sessState
C.asynchronously (C.archNonce ares) (C.sEmitEvent s0) pushContext $ do
case C.functionBlocks ares defFunc of
b0 : _ -> do
Expand Down Expand Up @@ -178,8 +179,8 @@ stateFromAnalysisResult s0 ares newDiags state uiMode = do
, SBE.sBlockViewers = MapF.fromList blockViewers
, SBE.sFunctionViewer = MapF.fromList funcViewers
, SBE.sFunctionSelector = FS.functionSelector (const (return ())) focusedListAttr []
, SBE.sSymbolicExecutionManager =
SEM.symbolicExecutionManager (Some (C.Configuring ses))
, SBE.sSymbolicExecutionState =
Map.singleton (ses ^. C.sessionID) (SEM.symbolicExecutionManager (Some (C.Configuring ses)))
}
return C.ArchState { C.sAnalysisResult = ares
, C.sUIState = uiState
Expand Down
117 changes: 31 additions & 86 deletions surveyor-brick/src/Surveyor/Brick/Handlers/SymbolicExecution.hs
Original file line number Diff line number Diff line change
@@ -1,108 +1,53 @@
{-# LANGUAGE GADTs #-}
-- | Brick-level handlers for symbolic execution events
module Surveyor.Brick.Handlers.SymbolicExecution ( handleSymbolicExecutionEvent ) where

import qualified Brick as B
import qualified Control.Concurrent.Async as A
import Control.Lens ( (&), (^.), (.~), (%~), _Just, (^?) )
import Control.Lens ( (&), (^.), (%~), _Just )
import Control.Monad.IO.Class ( liftIO )
import qualified Control.NF as NF
import qualified Data.Map.Strict as Map
import qualified Data.Parameterized.Classes as PC
import Data.Parameterized.Some ( Some(..) )
import qualified Data.Text as T
import qualified Lang.Crucible.Simulator.RegMap as CSR
import qualified Lang.Crucible.Types as LCT
import GHC.Stack ( HasCallStack )
import Surveyor.Brick.Names ( Names(..) )
import qualified Surveyor.Core as C
import qualified What4.Expr.Builder as WEB

import qualified Surveyor.Brick.Extension as SBE
import qualified Surveyor.Brick.Widget.SymbolicExecution as SEM

handleSymbolicExecutionEvent :: (C.Architecture arch s)
-- | This handler provides brick UI-specific handling for symbolic execution events
--
-- This handler is intended to run after the core-provided handlers, providing
-- hooks to update UI state when needed.
--
-- We really only need to do one thing: capture any updates to the symbolic
-- execution state so that we can rebuild the relevant UI elements
handleSymbolicExecutionEvent :: (C.Architecture arch s, HasCallStack)
=> C.S SBE.BrickUIExtension SBE.BrickUIState arch s
-> C.SymbolicExecutionEvent s (C.S SBE.BrickUIExtension SBE.BrickUIState)
-> B.EventM Names (B.Next (C.State SBE.BrickUIExtension SBE.BrickUIState s))
handleSymbolicExecutionEvent s0 evt =
case evt of
C.InitializeSymbolicExecution archNonce mConfig mFuncHandle
| Just PC.Refl <- PC.testEquality archNonce (s0 ^. C.lNonce)
, Just sessionID <- s0 ^? C.lArchState . _Just . C.contextL . C.currentContext . C.symExecSessionIDL
, Just symExSt <- s0 ^? C.lArchState . _Just . C.symExStateL -> do
-- FIXME: Instead of the default, we could scan the context stack for
-- the most recent configuration
let ng = C.sNonceGenerator s0
conf <- liftIO $ maybe (C.defaultSymbolicExecutionConfig ng) return mConfig
case C.lookupSessionState symExSt sessionID of
Just (Some oldState) -> liftIO $ C.cleanupSymbolicExecutionState oldState
Nothing -> return ()
let newState = C.configuringSymbolicExecution conf
let manager = SEM.symbolicExecutionManager (Some newState)
let s1 = s0 & C.lUIMode .~ C.SomeUIMode C.SymbolicExecutionManager
& C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionManagerL .~ manager
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState (C.singleSessionState newState)
& C.lArchState . _Just . C.contextL . C.currentContext . C.symExecSessionIDL .~ C.symbolicSessionID newState
B.continue (C.State s1)
| otherwise -> B.continue (C.State s0)

C.BeginSymbolicExecutionSetup archNonce symExConfig cfg
C.InitializeSymbolicExecution {} -> B.continue (C.State s0)
C.BeginSymbolicExecutionSetup {} -> B.continue (C.State s0)
C.StartSymbolicExecution {} -> B.continue (C.State s0)
C.ReportSymbolicExecutionMetrics {} -> B.continue (C.State s0)
C.NameValue {} -> B.continue (C.State s0)
C.InitializeValueNamePrompt {} -> B.continue (C.State s0)
C.SetCurrentSymbolicExecutionValue {} -> B.continue (C.State s0)
C.UpdateSymbolicExecutionState archNonce newState
| Just PC.Refl <- PC.testEquality archNonce (s0 ^. C.lNonce) -> do
let ng = C.sNonceGenerator s0
symExSt <- liftIO $ C.initializingSymbolicExecution ng symExConfig cfg
let manager = SEM.symbolicExecutionManager (Some symExSt)
let s1 = s0 & C.lUIMode .~ C.SomeUIMode C.SymbolicExecutionManager
& C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionManagerL .~ manager
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState (C.singleSessionState symExSt)
B.continue (C.State s1)
| otherwise -> B.continue (C.State s0)
-- Whenever the symbolic execution state changes, we need to rebuild
-- the UI for the corresponding session ID
let sessionID = C.symbolicSessionID newState

C.StartSymbolicExecution archNonce ares symState
| Just PC.Refl <- PC.testEquality archNonce (s0 ^. C.lNonce) -> do
let eventChan = s0 ^. C.lEventChannel
(newState, executionLoop) <- liftIO $ C.startSymbolicExecution eventChan ares symState
task <- liftIO $ A.async $ do
inspectState <- executionLoop
let updateSymExecState _ st =
let manager = SEM.symbolicExecutionManager (Some inspectState)
in st & C.lUIMode .~ C.SomeUIMode C.SymbolicExecutionManager
& C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionManagerL .~ manager
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState (C.singleSessionState newState)
-- We pass () as the value of the update state and capture the real
-- value (the new state) because there isn't an easy way to get an
-- NFData instance for states. That is okay, though, because they are
-- evaluated enough.
C.writeChan eventChan (C.AsyncStateUpdate archNonce (NF.nf ()) updateSymExecState)
let manager = SEM.symbolicExecutionManager (Some newState)
let s1 = s0 & C.lUIMode .~ C.SomeUIMode C.SymbolicExecutionManager
& C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionManagerL .~ manager
& C.lArchState . _Just . C.symExStateL %~ C.mergeSessionState (C.singleSessionState newState)
B.continue (C.State s1)
| otherwise -> B.continue (C.State s0)

C.ReportSymbolicExecutionMetrics sid metrics -> do
let s1 = s0 & C.lArchState . _Just . C.symExStateL %~ C.updateSessionMetrics sid metrics
B.continue (C.State s1)

C.NameValue valueNonce name -> do
let s1 = s0 & C.lValueNames %~ C.addValueName valueNonce name
B.continue (C.State s1)
let msg = C.msgWithContext { C.logLevel = C.Debug
, C.logText = [ T.pack ("Updating widget for session " ++ show sessionID) ]
}
liftIO $ C.logMessage s0 msg

C.InitializeValueNamePrompt archNonce name
| Just PC.Refl <- PC.testEquality archNonce (s0 ^. C.lNonce)
, Just sessionID <- s0 ^? C.lArchState . _Just . C.contextL . C.currentContext . C.symExecSessionIDL
, Just symExSt <- s0 ^? C.lArchState . _Just . C.symExStateL
, Just (Some (C.Suspended _symNonce suspSt)) <- C.lookupSessionState symExSt sessionID
, Just (Some curVal) <- C.suspendedCurrentValue suspSt -> do
case LCT.asBaseType (CSR.regType curVal) of
LCT.AsBaseType _btr ->
case CSR.regValue curVal of
WEB.AppExpr ae -> liftIO $ C.sEmitEvent s0 (C.NameValue (WEB.appExprId ae) name)
WEB.NonceAppExpr nae -> liftIO $ C.sEmitEvent s0 (C.NameValue (WEB.nonceExprId nae) name)
_ -> return ()
LCT.NotBaseType -> return ()
B.continue (C.State s0)
| otherwise -> do
liftIO $ C.logMessage s0 (C.msgWith { C.logLevel = C.Debug
, C.logSource = C.EventHandler (T.pack "InitializeValueNamePrompt")
, C.logText = [T.pack "Pattern matches failed"]
})
B.continue (C.State s0)
let manager = SEM.symbolicExecutionManager (Some newState)
let s1 = s0 & C.lArchState . _Just . C.lUIState . SBE.symbolicExecutionStateL %~ Map.insert sessionID manager
B.continue (C.State s1)
| otherwise -> B.continue (C.State s0)
Loading

0 comments on commit bc9a7c6

Please sign in to comment.