Introduce separate stack-analysis algorithm.

This commit is contained in:
Joe Hendrix 2019-12-16 09:39:48 -08:00 committed by Joe Hendrix
parent a52698beae
commit 0236aa5d9a
7 changed files with 1134 additions and 1015 deletions

View File

@ -52,6 +52,7 @@ library
Data.Macaw.AbsDomain.CallParams
Data.Macaw.AbsDomain.JumpBounds
Data.Macaw.AbsDomain.Refine
Data.Macaw.AbsDomain.StackAnalysis
Data.Macaw.AbsDomain.StridedInterval
Data.Macaw.Analysis.FunctionArgs
Data.Macaw.Analysis.RegisterUse
@ -76,6 +77,7 @@ library
Data.Macaw.Memory.Symbols
Data.Macaw.SCFG
Data.Macaw.Types
Data.Macaw.Utils.Changed
Data.Macaw.Utils.Pretty
ghc-options: -Wall -Wcompat

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,885 @@
{- |
This module defines a relational abstract domain for tracking
registers and stack addresses. The model records when one of these
values is known to be equal to the current pointer stack frame. This
domain also tracks equalities between nodes so that analysis
algorithms built on top of this are modulo known equivalences
between registers and stack locations.
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Macaw.AbsDomain.StackAnalysis
( -- * Block level datatypes
-- ** BlockStartStackConstraints
BlockStartStackConstraints
, StackEqConstraint(..)
, ppBlockStartStackConstraints
, fnEntryBlockStartStackConstraints
, ppStackEqConstraint
, blockStartLocExpr
, blockStartLocStackOffset
, StackOffConstraint
, blockStartLocRepAndCns
, BoundLoc(..)
, joinBlockStartStackConstraints
, JoinClassMap
, JoinClassPair(..)
-- ** BlockIntraStackConstraints
, BlockIntraStackConstraints
, mkBlockIntraStackConstraints
, biscInitConstraints
, blockIntraValueExpr
, blockIntraValueStackOffset
, StackExpr(..)
, blockIntraRhsExpr
, bindAssignment
, discardStackInfo
, writeStackOff
-- ** Block terminator updates
, postJumpStackConstraints
, postCallStackConstraints
-- * LocMap
, LocMap(..)
, locMapEmpty
, locLookup
, nonOverlapLocInsert
, locOverwriteWith
, ppLocMap
-- * StackMap
, StackMap
, emptyStackMap
, stackMapLookup
, StackMapLookup(..)
, stackMapOverwrite
, stackMapMapWithKey
, stackMapTraverseMaybeWithKey
, stackMapDropAbove
, stackMapDropBelow
-- * NextStateMonad
, NextStateMonad
, runNextStateMonad
, getNextStateRepresentatives
-- * Miscelaneous
) where
import Control.Monad.Reader
import Control.Monad.State
import Data.Functor
import Data.Kind
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Parameterized.Classes
import Data.Parameterized.Map (MapF)
import qualified Data.Parameterized.Map as MapF
import Data.Parameterized.Pair
import Data.Parameterized.TraversableF
import Data.Parameterized.TraversableFC
import Data.STRef
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
import Data.Macaw.AbsDomain.CallParams
import Data.Macaw.CFG.App
import Data.Macaw.CFG.Core
import Data.Macaw.Memory
import qualified Data.Macaw.Types as M
import Data.Macaw.Types hiding (Type)
import Data.Macaw.Utils.Changed
addrTypeRepr :: MemWidth w => TypeRepr (BVType w)
addrTypeRepr = BVTypeRepr memWidthNatRepr
ppAddend :: MemInt w -> Doc
ppAddend o | memIntValue o < 0 =
text "-" <+> pretty (negate (toInteger (memIntValue o)))
| otherwise = text "+" <+> pretty o
ppStackOff :: MemInt w -> MemRepr tp -> Doc
ppStackOff o repr =
text "*(stack_frame" <+> ppAddend o <> text "," <+> pretty repr <> text ")"
------------------------------------------------------------------------
-- JoinClassPair
data JoinClassPair (key :: k -> Type) (tp :: k) = JoinClassPair !(key tp) !(key tp)
instance TestEquality k => TestEquality (JoinClassPair k) where
testEquality (JoinClassPair x1 x2) (JoinClassPair y1 y2) = do
Refl <- testEquality x1 y1
testEquality x2 y2
instance OrdF k => OrdF (JoinClassPair k) where
compareF (JoinClassPair x1 x2) (JoinClassPair y1 y2) =
joinOrderingF (compareF x1 y1) (compareF x2 y2)
------------------------------------------------------------------------
-- MemVal
-- | A value with a particular type along with a MemRepr indicating
-- how the type is encoded in memory.
data MemVal (p :: M.Type -> Type) =
forall (tp :: M.Type) . MemVal !(MemRepr tp) !(p tp)
instance FunctorF MemVal where
fmapF f (MemVal r x) = MemVal r (f x)
------------------------------------------------------------------------
-- BoundLoc
-- | Either a register or stack offset.
--
-- These locations are tracked by our bounds analysis algorithm.
data BoundLoc (r :: M.Type -> Type) (tp :: M.Type) where
-- | @RegLoc r@ denotes the register @r@.
RegLoc :: !(r tp) -> BoundLoc r tp
-- | @StackkOffLoc o repr@ denotes the bytes from address range
-- @[initSP + o .. initSP + o + memReprBytes repr)@.
--
-- We should note that this makes no claim that those addresses
-- are valid.
StackOffLoc :: !(MemInt (RegAddrWidth r))
-> !(MemRepr tp)
-> BoundLoc r tp
instance TestEquality r => TestEquality (BoundLoc r) where
testEquality (RegLoc x) (RegLoc y) = testEquality x y
testEquality (StackOffLoc xi xr) (StackOffLoc yi yr) | xi == yi =
testEquality xr yr
testEquality _ _ = Nothing
instance HasRepr r TypeRepr => HasRepr (BoundLoc r) TypeRepr where
typeRepr (RegLoc r) = typeRepr r
typeRepr (StackOffLoc _ r) = typeRepr r
instance OrdF r => OrdF (BoundLoc r) where
compareF (RegLoc x) (RegLoc y) = compareF x y
compareF (RegLoc _) _ = LTF
compareF _ (RegLoc _) = GTF
compareF (StackOffLoc xi xr) (StackOffLoc yi yr) =
case compare xi yi of
LT -> LTF
GT -> GTF
EQ -> compareF xr yr
instance ShowF r => Pretty (BoundLoc r tp) where
pretty (RegLoc r) = text (showF r)
pretty (StackOffLoc i tp) =
text "*(stack_frame " <+> ppAddend i <> text ") :" <> pretty tp
instance ShowF r => PrettyF (BoundLoc r) where
prettyF = pretty
------------------------------------------------------------------------
-- StackMap
-- | This is a data structure for representing values written to
-- concrete stack offsets.
newtype StackMap w (v :: M.Type -> Type) = SM (Map (MemInt w) (MemVal v))
instance FunctorF (StackMap w) where
fmapF f (SM m) = SM (fmap (fmapF f) m)
-- | Empty stack map
emptyStackMap :: StackMap w p
emptyStackMap = SM Map.empty
-- | Pretty print a stack map given a term
ppStackMap :: (forall tp . Doc -> v tp -> Doc) -> StackMap w v -> Doc
ppStackMap f (SM m)
| Map.null m = text "empty-stack-map"
| otherwise =
vcat $
[ f (ppStackOff o repr) v
| (o,MemVal repr v) <- Map.toList m
]
instance PrettyF v => Pretty (StackMap w v) where
pretty = ppStackMap (\nm d -> nm <+> text ":=" <+> prettyF d)
-- | Result returned by @stackMapLookup@.
data StackMapLookup w p tp where
-- 1| We found a value at the exact offset and repr
SMLResult :: !(p tp) -> StackMapLookup w p tp
-- | We found a value that had an overlapping offset and repr.
SMLOverlap :: !(MemInt w)
-> !(MemRepr utp)
-> !(p utp)
-> StackMapLookup w p tp
-- | We found neither an exact match nor an overlapping write.
SMLNone :: StackMapLookup w p tp
-- | Lookup value (if any) at given offset and representation.
stackMapLookup :: MemWidth w
=> MemInt w
-> MemRepr tp
-> StackMap w p
-> StackMapLookup w p tp
stackMapLookup off repr (SM m) = do
let end = off + fromIntegral (memReprBytes repr)
if end < off then
error $ "stackMapLookup given bad offset."
else
case Map.lookupLT end m of
Just (oldOff, MemVal oldRepr val)
-- If match exact
| oldOff == off
, Just Refl <- testEquality oldRepr repr ->
SMLResult val
-- If previous write ends after this write starts
| oldOff + fromIntegral (memReprBytes oldRepr) > off ->
SMLOverlap oldOff oldRepr val
| otherwise ->
SMLNone
Nothing -> SMLNone
clearPreWrites :: Ord i => i -> i -> Map i v -> Map i v
clearPreWrites l h m =
case Map.lookupLT h m of
Just (o,_v) | o >= l -> clearPreWrites l h (Map.delete o m)
_ -> m
clearPostWrites :: Ord i => i -> i -> Map i v -> Map i v
clearPostWrites l h m =
case Map.lookupGE l m of
Just (o,_v) | o < h -> clearPostWrites l h (Map.delete o m)
_ -> m
-- | This assigns a region of bytes to a particular value in the stack.
--
-- It overwrites any values that overlap with the location.
stackMapOverwrite :: forall w p tp
. MemWidth w
=> MemInt w -- ^ Offset in the stack to write to
-> MemRepr tp -- ^ Type of value to write
-> p tp -- ^ Value to write
-> StackMap w p
-> StackMap w p
stackMapOverwrite off repr v (SM m) =
let e = off + fromIntegral (memReprBytes repr)
in SM $ Map.insert off (MemVal repr v)
$ clearPreWrites off e
$ clearPostWrites off e m
-- | This sets the value at an offset without checking to clear any
-- previous writes to values.
unsafeStackMapInsert :: MemInt w -> MemRepr tp -> p tp -> StackMap w p -> StackMap w p
unsafeStackMapInsert o repr v (SM m) = SM (Map.insert o (MemVal repr v) m)
stackMapFoldrWithKey :: (forall tp . MemInt w -> MemRepr tp -> v tp -> r -> r)
-> r
-> StackMap w v
-> r
stackMapFoldrWithKey f z (SM m) =
Map.foldrWithKey (\k (MemVal repr v) r -> f k repr v r) z m
stackMapTraverseWithKey_ :: Applicative m
=> (forall tp . MemInt w -> MemRepr tp -> v tp -> m ())
-> StackMap w v
-> m ()
stackMapTraverseWithKey_ f (SM m) =
Map.foldrWithKey (\k (MemVal repr v) r -> f k repr v *> r) (pure ()) m
stackMapMapWithKey :: (forall tp . MemInt w -> MemRepr tp -> a tp -> b tp)
-> StackMap w a
-> StackMap w b
stackMapMapWithKey f (SM m) =
SM (Map.mapWithKey (\o (MemVal repr v) -> MemVal repr (f o repr v)) m)
stackMapTraverseMaybeWithKey :: Applicative m
=> (forall tp . MemInt w -> MemRepr tp -> a tp -> m (Maybe (b tp)))
-- ^ Traversal function
-> StackMap w a
-> m (StackMap w b)
stackMapTraverseMaybeWithKey f (SM m) =
SM <$> Map.traverseMaybeWithKey (\o (MemVal repr v) -> fmap (MemVal repr) <$> f o repr v) m
-- @stackMapDropAbove bnd m@ includes only entries in @m@ whose bytes do not go above @bnd@.
stackMapDropAbove :: MemWidth w => Integer -> StackMap w p -> StackMap w p
stackMapDropAbove bnd (SM m) = SM (Map.filterWithKey p m)
where p o (MemVal r _) = toInteger o + toInteger (memReprBytes r) <= bnd
-- @stackMapDropBelow bnd m@ includes only entries in @m@ whose bytes do not go below @bnd@.
stackMapDropBelow :: MemWidth w => Integer -> StackMap w p -> StackMap w p
stackMapDropBelow bnd (SM m) = SM (Map.filterWithKey p m)
where p o _ = toInteger o >= bnd
------------------------------------------------------------------------
-- LocMap
-- | A map from register/concrete stack offsets to values
data LocMap (r :: M.Type -> Type) (v :: M.Type -> Type)
= LocMap { locMapRegs :: !(MapF r v)
, locMapStack :: !(StackMap (RegAddrWidth r) v)
}
-- | An empty location map.
locMapEmpty :: LocMap r v
locMapEmpty = LocMap { locMapRegs = MapF.empty, locMapStack = emptyStackMap }
-- | Pretty print a location map.
ppLocMap :: ShowF r => (forall tp . Doc -> p tp -> Doc) -> LocMap r p -> [Doc]
ppLocMap f m
= flip (MapF.foldrWithKey (\k v -> (f (text (showF k)) v:)))
(locMapRegs m)
$ stackMapFoldrWithKey (\i repr v -> (f (ppStackOff i repr) v:))
[]
(locMapStack m)
-- | Return value associated with location or nothing if it is not
-- defined.
locLookup :: (MemWidth (RegAddrWidth r), OrdF r)
=> BoundLoc r tp
-> LocMap r v
-> Maybe (v tp)
locLookup (RegLoc r) m = MapF.lookup r (locMapRegs m)
locLookup (StackOffLoc o repr) m =
case stackMapLookup o repr (locMapStack m) of
SMLResult r -> Just r
SMLOverlap _ _ _ -> Nothing
SMLNone -> Nothing
-- | This associates the location with a value in the map, replacing any existing binding.
--
-- It is prefixed with "nonOverlap" because it doesn't guarantee that stack
-- values are non-overlapping -- the user should ensure this before calling this.
nonOverlapLocInsert :: OrdF r => BoundLoc r tp -> v tp -> LocMap r v -> LocMap r v
nonOverlapLocInsert (RegLoc r) v m =
m { locMapRegs = MapF.insert r v (locMapRegs m) }
nonOverlapLocInsert (StackOffLoc off repr) v m =
m { locMapStack = unsafeStackMapInsert off repr v (locMapStack m) }
locOverwriteWith :: (OrdF r, MemWidth (RegAddrWidth r))
=> (v tp -> v tp -> v tp) -- ^ Update takes new and old.
-> BoundLoc r tp
-> v tp
-> LocMap r v
-> LocMap r v
locOverwriteWith upd (RegLoc r) v m =
m { locMapRegs = MapF.insertWith upd r v (locMapRegs m) }
locOverwriteWith upd (StackOffLoc o repr) v m =
let nv = case stackMapLookup o repr (locMapStack m) of
SMLNone -> v
SMLOverlap _ _ _ -> v
SMLResult oldv -> upd v oldv
in m { locMapStack = stackMapOverwrite o repr nv (locMapStack m) }
------------------------------------------------------------------------
-- StackEqConstraint
-- | A constraint on a location for the stack analysis.
data StackEqConstraint r tp where
-- | An equivalence class representative with the given number of
-- elements.
--
-- In our map the number of equivalence class members should always
-- be positive.
IsStackOff :: !(MemInt (RegAddrWidth r)) -> StackEqConstraint r (BVType (RegAddrWidth r))
EqualLoc :: !(BoundLoc r tp) -> StackEqConstraint r tp
ppStackEqConstraint :: ShowF r => Doc -> StackEqConstraint r tp -> Doc
ppStackEqConstraint d (IsStackOff i) =
d <+> text "= stack_frame" <+> ppAddend i
ppStackEqConstraint d (EqualLoc l) = d <+> text "=" <+> pretty l
------------------------------------------------------------------------
-- BlockStartStackConstraints
-- | Constraints on start of block
newtype BlockStartStackConstraints arch =
BSSC { unBSSC :: LocMap (ArchReg arch) (StackEqConstraint (ArchReg arch)) }
-- | Pretty print the lines in stack constraints.
ppBlockStartStackConstraints :: ShowF (ArchReg arch) => BlockStartStackConstraints arch -> [Doc]
ppBlockStartStackConstraints = ppLocMap ppStackEqConstraint . unBSSC
fnEntryBlockStartStackConstraints :: RegisterInfo (ArchReg arch) => BlockStartStackConstraints arch
fnEntryBlockStartStackConstraints =
BSSC LocMap { locMapRegs = MapF.singleton sp_reg (IsStackOff 0)
, locMapStack = emptyStackMap
}
-- | @blockStartLocStackOffset c l@ returns an offset of the stack
-- pointer that the value stored in @l@ is inferred to be equivalent
-- to when the block starts execution.
blockStartLocStackOffset :: (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BoundLoc (ArchReg arch) tp
-> Maybe (MemInt (ArchAddrWidth arch))
blockStartLocStackOffset cns loc =
case locLookup loc (unBSSC cns) of
Nothing -> Nothing
Just (IsStackOff o) -> Just o
Just (EqualLoc loc') -> blockStartLocStackOffset cns loc'
data StackOffConstraint w tp where
StackOffCns :: !(MemInt w) -> StackOffConstraint w (BVType w)
-- | @boundsLocRep bnds loc@ returns the representative location for
-- @loc@.
--
-- This representative location has the property that a location must
-- have the same value as its representative location, and if two
-- locations have provably equal values in the bounds, then they must
-- have the same representative.
blockStartLocRepAndCns :: (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BoundLoc (ArchReg arch) tp
-> (BoundLoc (ArchReg arch) tp, Maybe (StackOffConstraint (ArchAddrWidth arch) tp))
blockStartLocRepAndCns cns l =
case locLookup l (unBSSC cns) of
Just (EqualLoc loc) -> blockStartLocRepAndCns cns loc
Just (IsStackOff o) -> (l,Just (StackOffCns o))
Nothing -> (l, Nothing)
------------------------------------------------------------------------
-- joinStackExpr
type JoinClassMap r = MapF (JoinClassPair (BoundLoc r)) (BoundLoc r)
joinStackEqConstraint :: Maybe (StackOffConstraint w tp)
-> Maybe (StackOffConstraint w tp)
-> Changed s (Maybe (StackOffConstraint w tp))
joinStackEqConstraint Nothing _ = pure Nothing
joinStackEqConstraint p@(Just (StackOffCns i)) (Just (StackOffCns j)) | i == j = pure p
joinStackEqConstraint _ _ = markChanged True $> Nothing
-- | Join locations
joinNewLoc :: forall s arch tp
. (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BlockStartStackConstraints arch
-> STRef s (BlockStartStackConstraints arch)
-- ^ The current index bounds.
-> STRef s (JoinClassMap (ArchReg arch))
-- ^ The map from (oldClassRep, newClassRep) pairs to the
-- result class rep.
-> STRef s Int
-- ^ Stores the number of equivalence classes we have seen in
-- in the old class
-- Used so determining if any classes are changed.
-> BoundLoc (ArchReg arch) tp
-- ^ Location in join that we have not yet visited.
-> StackEqConstraint (ArchReg arch) tp
-- ^ Constraint on location in original list.
-> Changed s ()
joinNewLoc old new bndsRef procRef cntr thisLoc oldCns = do
(oldRep, oldPred) <- changedST $
case oldCns of
EqualLoc oldLoc ->
pure (blockStartLocRepAndCns old oldLoc)
IsStackOff o -> do
-- Increment number of equivalence classes when we see an old
-- representative.
modifySTRef' cntr (+1)
-- Return this loc
pure (thisLoc, Just (StackOffCns o))
let (newRep, newPred) = blockStartLocRepAndCns new thisLoc
m <- changedST $ readSTRef procRef
-- See if we have already added a representative for this class.
let pair = JoinClassPair oldRep newRep
case MapF.lookup pair m of
Nothing -> do
mp <- joinStackEqConstraint oldPred newPred
changedST $ do
writeSTRef procRef $! MapF.insert pair thisLoc m
case mp of
Nothing -> pure ()
Just (StackOffCns o) ->
modifySTRef' bndsRef $ \cns -> BSSC (nonOverlapLocInsert thisLoc (IsStackOff o) (unBSSC cns))
Just resRep ->
-- Assert r is equal to resRep
changedST $
modifySTRef' bndsRef $ \cns -> BSSC (nonOverlapLocInsert thisLoc (EqualLoc resRep) (unBSSC cns))
-- | Return a jump bounds that implies both input bounds, or `Nothing`
-- if every fact inx the old bounds is implied by the new bounds.
joinBlockStartStackConstraints :: forall arch s
. (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BlockStartStackConstraints arch
-> Changed s (BlockStartStackConstraints arch, JoinClassMap (ArchReg arch))
joinBlockStartStackConstraints old new = do
-- Reference to new bounds.
bndsRef <- changedST $ newSTRef (BSSC locMapEmpty)
-- This maps pairs containing the class representative in the old
-- and new maps to the associated class representative in the
-- combined bounds.
procRef <- changedST $ newSTRef MapF.empty
cntr <- changedST $ newSTRef 0
--
MapF.traverseWithKey_
(\r -> joinNewLoc old new bndsRef procRef cntr (RegLoc r))
(locMapRegs (unBSSC old))
stackMapTraverseWithKey_
(\i r c -> joinNewLoc old new bndsRef procRef cntr (StackOffLoc i r) c)
(locMapStack (unBSSC old))
-- Check number of equivalence classes in result and original
-- The count should not have decreased, but may increase if two elements
-- are no longer equal, and we need to check this.
origClassCount <- changedST $ readSTRef cntr
resultClassCount <- changedST $ MapF.size <$> readSTRef procRef
unless (origClassCount <= resultClassCount) $ do
error "Original class count should be bound by resultClassCount"
-- Record changed if any classes are different.
markChanged (origClassCount < resultClassCount)
changedST $ (,) <$> readSTRef bndsRef <*> readSTRef procRef
------------------------------------------------------------------------
-- StackExpr
-- | This is an expression that represents the value of stack
-- locations and assignments during steping through the block.
--
-- The main diference between this and
-- the `Value` type, index expressions do not depend on values read
-- and written to memory during execution of the block, and are purely
-- functions of the input
--
-- This is different from `ClassPred` in that @ClassPred@ is a property
-- assigned to equivalence classes
data StackExpr arch ids tp where
-- | This refers to the value that a location had at the start of
-- block execution.
--
-- The location should be a class representative in the initial bounds.
ClassRepExpr :: !(BoundLoc (ArchReg arch) tp) -> StackExpr arch ids tp
-- | An assignment that is not interpreted, and just treated as a constant.
UninterpAssignExpr :: !(AssignId ids tp)
-> !(TypeRepr tp)
-> StackExpr arch ids tp
-- | Denotes the value of the stack pointer at function start plus some constant.
StackOffsetExpr :: !(MemInt (ArchAddrWidth arch))
-> StackExpr arch ids (BVType (ArchAddrWidth arch))
-- | Denotes a constant
CExpr :: !(CValue arch tp) -> StackExpr arch ids tp
-- | This is a pure function applied to other index expressions that
-- may be worth interpreting (but could be treated as an uninterp
-- assign expr also.
AppExpr :: !(AssignId ids tp)
-> !(App (StackExpr arch ids) tp)
-> StackExpr arch ids tp
instance TestEquality (ArchReg arch) => TestEquality (StackExpr arch ids) where
testEquality (ClassRepExpr x) (ClassRepExpr y) =
testEquality x y
testEquality (UninterpAssignExpr x _) (UninterpAssignExpr y _) =
testEquality x y
testEquality (StackOffsetExpr x) (StackOffsetExpr y) =
if x == y then
Just Refl
else
Nothing
testEquality (CExpr x) (CExpr y) =
testEquality x y
testEquality (AppExpr xn _xa) (AppExpr yn _ya) =
testEquality xn yn
testEquality _ _ = Nothing
instance OrdF (ArchReg arch) => OrdF (StackExpr arch ids) where
compareF (ClassRepExpr x) (ClassRepExpr y) = compareF x y
compareF ClassRepExpr{} _ = LTF
compareF _ ClassRepExpr{} = GTF
compareF (UninterpAssignExpr x _) (UninterpAssignExpr y _) = compareF x y
compareF UninterpAssignExpr{} _ = LTF
compareF _ UninterpAssignExpr{} = GTF
compareF (StackOffsetExpr x) (StackOffsetExpr y) = fromOrdering (compare x y)
compareF StackOffsetExpr{} _ = LTF
compareF _ StackOffsetExpr{} = GTF
compareF (CExpr x) (CExpr y) = compareF x y
compareF CExpr{} _ = LTF
compareF _ CExpr{} = GTF
compareF (AppExpr xn _xa) (AppExpr yn _ya) = compareF xn yn
instance ( HasRepr (ArchReg arch) TypeRepr
, MemWidth (ArchAddrWidth arch)
) => HasRepr (StackExpr arch ids) TypeRepr where
typeRepr e =
case e of
ClassRepExpr x -> typeRepr x
UninterpAssignExpr _ tp -> tp
StackOffsetExpr _ -> addrTypeRepr
CExpr x -> typeRepr x
AppExpr _ a -> typeRepr a
instance ShowF (ArchReg arch) => Pretty (StackExpr arch id tp) where
pretty e =
case e of
ClassRepExpr l -> pretty l
UninterpAssignExpr n _ -> parens (text "uninterp" <+> ppAssignId n)
StackOffsetExpr o -> parens (text "+ stack_off" <+> pretty o)
CExpr v -> pretty v
AppExpr n _ -> parens (text "app" <+> ppAssignId n)
instance ShowF (ArchReg arch) => PrettyF (StackExpr arch id) where
prettyF = pretty
-- | Return an expression equivalent to the location in the constraint
-- map.
--
-- This attempts to normalize the expression to get a representative.
blockStartLocExpr :: (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BoundLoc (ArchReg arch) tp
-> StackExpr arch ids tp
blockStartLocExpr bnds loc =
case locLookup loc (unBSSC bnds) of
Nothing -> ClassRepExpr loc
Just (IsStackOff o) -> StackOffsetExpr o
Just (EqualLoc loc') -> blockStartLocExpr bnds loc'
------------------------------------------------------------------------
-- BlockIntraStackConstraints
-- | Information about bounds for a particular value within a block.
data BlockIntraStackConstraints arch ids
= BISC { biscInitConstraints :: !(BlockStartStackConstraints arch)
-- ^ Bounds at execution start.
, stackExprMap :: !(StackMap (ArchAddrWidth arch) (StackExpr arch ids))
-- ^ Maps stack offsets to the expression associated with them.
, assignExprMap :: !(MapF (AssignId ids) (StackExpr arch ids))
-- ^ Maps processed assignments to index expressions.
, memoTable :: !(MapF (App (StackExpr arch ids)) (StackExpr arch ids))
-- ^ Table for ensuring each bound expression has a single
-- representative.
}
-- | Create index bounds from initial index bounds.
mkBlockIntraStackConstraints :: forall arch ids
. (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockStartStackConstraints arch
-> BlockIntraStackConstraints arch ids
mkBlockIntraStackConstraints bnds =
let stackExpr :: MemInt (ArchAddrWidth arch)
-> MemRepr tp
-> StackEqConstraint (ArchReg arch) tp
-> StackExpr arch ids tp
stackExpr i tp _ = blockStartLocExpr bnds (StackOffLoc i tp)
in BISC { biscInitConstraints = bnds
, stackExprMap = stackMapMapWithKey stackExpr (locMapStack (unBSSC bnds))
, assignExprMap = MapF.empty
, memoTable = MapF.empty
}
-- | Return the value of the index expression given the bounds.
blockIntraValueExpr :: forall arch ids tp
. (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockIntraStackConstraints arch ids
-> Value arch ids tp
-> StackExpr arch ids tp
blockIntraValueExpr bnds val =
case val of
CValue c -> CExpr c
AssignedValue (Assignment aid _) ->
case MapF.lookup aid (assignExprMap bnds) of
Just e -> e
Nothing -> error $ "blockIntraValueExpr internal: Expected value to be assigned."
Initial r -> blockStartLocExpr (biscInitConstraints bnds) (RegLoc r)
-- | Return stack offset if value is a stack offset.
blockIntraValueStackOffset :: (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockIntraStackConstraints arch ids
-> Value arch ids (BVType (ArchAddrWidth arch))
-> Maybe (MemInt (ArchAddrWidth arch))
blockIntraValueStackOffset bnds val =
case blockIntraValueExpr bnds val of
StackOffsetExpr i -> Just i
_ -> Nothing
-- | Return an expression associated with the @AssignRhs@.
blockIntraRhsExpr :: forall arch ids tp
. ( MemWidth (ArchAddrWidth arch)
, OrdF (ArchReg arch)
, ShowF (ArchReg arch)
)
=> BlockIntraStackConstraints arch ids
-> AssignId ids tp
-> AssignRhs arch (Value arch ids) tp
-> StackExpr arch ids tp
blockIntraRhsExpr bnds aid arhs =
case arhs of
EvalApp app -> do
let stackFn v = toInteger <$> blockIntraValueStackOffset bnds v
case appAsStackOffset stackFn app of
Just (StackOffsetView o) -> do
StackOffsetExpr $ fromInteger o
_ ->
let a = fmapFC (blockIntraValueExpr bnds) app
in case MapF.lookup a (memoTable bnds) of
Just r -> r
Nothing -> AppExpr aid a
ReadMem addr repr
| Just o <- blockIntraValueStackOffset bnds addr
, SMLResult e <- stackMapLookup o repr (stackExprMap bnds) ->
e
_ -> UninterpAssignExpr aid (typeRepr arhs)
-- | Associate the given bound expression with the assignment.
bindAssignment :: AssignId ids tp
-> StackExpr arch ids tp
-> BlockIntraStackConstraints arch ids
-> BlockIntraStackConstraints arch ids
bindAssignment aid expr bnds =
bnds { assignExprMap = MapF.insert aid expr (assignExprMap bnds) }
-- | Discard information about the stack in the bounds due to an
-- operation that may affect the stack.
discardStackInfo :: BlockIntraStackConstraints arch ids
-> BlockIntraStackConstraints arch ids
discardStackInfo bnds =
bnds { stackExprMap = emptyStackMap }
-- | Update the stack to point to the given expression.
writeStackOff :: forall arch ids tp
. (MemWidth (ArchAddrWidth arch), OrdF (ArchReg arch))
=> BlockIntraStackConstraints arch ids
-> MemInt (ArchAddrWidth arch)
-> MemRepr tp
-> Value arch ids tp
-> BlockIntraStackConstraints arch ids
writeStackOff bnds off repr v =
bnds { stackExprMap = stackMapOverwrite off repr (blockIntraValueExpr bnds v) (stackExprMap bnds) }
------------------------------------------------------------------------
-- NextStateMonad
-- | Maps bound expression that have been visited to their location.
--
-- We memoize expressions seen so that we can infer when two locations
-- must be equal.
data NextBlockState arch ids =
NBS { nbsExprMap :: !(MapF (StackExpr arch ids) (BoundLoc (ArchReg arch) ))
, nbsRepLocs :: ![Pair (BoundLoc (ArchReg arch)) (StackExpr arch ids)]
-- ^ List of location expression pairs
}
-- Monad used for computing next states.
newtype NextStateMonad arch ids a = NSM (State (NextBlockState arch ids) a)
deriving (Functor, Applicative, Monad)
runNextStateMonad :: NextStateMonad arch ids a -> a
runNextStateMonad (NSM m) = evalState m $! NBS { nbsExprMap = MapF.empty, nbsRepLocs = [] }
-- | Return a list of locations and associated expressions that
-- represent equivalence classes in the next state.
--
-- Note that each equivalence class has a unique stack expression, so
-- all the locations in an equivalence class should have the same
-- expression.
getNextStateRepresentatives :: NextStateMonad arch ids [Pair (BoundLoc (ArchReg arch)) (StackExpr arch ids)]
getNextStateRepresentatives = NSM $ gets nbsRepLocs
------------------------------------------------------------------------
-- BlockIntraStackConstraints next state
-- | Return the constraint associated with the given location and expression
-- or nothing if the constraint is the top one and should be stored.
nextStateStackEqConstraint :: ( MemWidth (ArchAddrWidth arch)
, HasRepr (ArchReg arch) TypeRepr
, OrdF (ArchReg arch)
, ShowF (ArchReg arch)
)
=> BoundLoc (ArchReg arch) tp
-- ^ Location expression is stored at.
-> StackExpr arch ids tp
-- ^ Expression to infer predicate or.
-> NextStateMonad arch ids (Maybe (StackEqConstraint (ArchReg arch) tp))
nextStateStackEqConstraint loc e = do
s <- NSM $ get
case MapF.lookup e (nbsExprMap s) of
Just l ->
pure $! Just $ EqualLoc l
Nothing -> do
NSM $ put $! NBS { nbsExprMap = MapF.insert e loc (nbsExprMap s)
, nbsRepLocs = Pair loc e : nbsRepLocs s
}
case e of
StackOffsetExpr o ->
pure $! (Just $! IsStackOff o)
_ ->
pure $! Nothing
-- | Bounds for block after jump
postJumpStackConstraints :: forall arch ids
. RegisterInfo (ArchReg arch)
=> BlockIntraStackConstraints arch ids
-- ^ Constraints at end of block.
-> RegState (ArchReg arch) (Value arch ids)
-- ^ Register values at start of next state.
-> NextStateMonad arch ids (BlockStartStackConstraints arch)
postJumpStackConstraints bnds regs = do
rm <- MapF.traverseMaybeWithKey
(\r v -> nextStateStackEqConstraint (RegLoc r) (blockIntraValueExpr bnds v))
(regStateMap regs)
sm <- stackMapTraverseMaybeWithKey (\i repr e -> nextStateStackEqConstraint (StackOffLoc i repr) e)
(stackExprMap bnds)
pure $! BSSC (LocMap { locMapRegs = rm, locMapStack = sm })
-- | Get the constraint associated with a register after a call.
postCallConstraint :: RegisterInfo (ArchReg arch)
=> CallParams (ArchReg arch)
-- ^ Information about calling convention.
-> BlockIntraStackConstraints arch ids
-- ^ Bounds at end of this state.
-> ArchReg arch tp
-- ^ Register to get
-> Value arch ids tp
-- ^ Value of register at time call occurs.
-> NextStateMonad arch ids (Maybe (StackEqConstraint (ArchReg arch) tp))
postCallConstraint params bnds r v
| Just Refl <- testEquality r sp_reg
, StackOffsetExpr o <- blockIntraValueExpr bnds v = do
pure $! (Just $! IsStackOff (o+fromInteger (postCallStackDelta params)))
| preserveReg params r =
nextStateStackEqConstraint (RegLoc r) (blockIntraValueExpr bnds v)
| otherwise =
pure Nothing
-- | Return the index bounds after a function call.
postCallStackConstraints :: forall arch ids
. RegisterInfo (ArchReg arch)
=> CallParams (ArchReg arch)
-> BlockIntraStackConstraints arch ids
-> RegState (ArchReg arch) (Value arch ids)
-> BlockStartStackConstraints arch
postCallStackConstraints params cns regs =
runNextStateMonad $ do
rm <- MapF.traverseMaybeWithKey (postCallConstraint params cns) (regStateMap regs)
let finalStack = stackExprMap cns
let filteredStack =
case blockIntraValueExpr cns (getBoundValue sp_reg regs) of
StackOffsetExpr spOff
| stackGrowsDown params ->
let newOff = toInteger spOff + postCallStackDelta params
-- Keep entries at offsets above return address.
in stackMapDropBelow newOff finalStack
| otherwise ->
let newOff = toInteger spOff + postCallStackDelta params
-- Keep entries whose high address is below new stack offset
in stackMapDropAbove newOff finalStack
_ -> emptyStackMap
let nextStackFn :: MemInt (ArchAddrWidth arch)
-> MemRepr tp
-> StackExpr arch ids tp
-> NextStateMonad arch ids (Maybe (StackEqConstraint (ArchReg arch) tp))
nextStackFn i repr e =
nextStateStackEqConstraint (StackOffLoc i repr) e
sm <- stackMapTraverseMaybeWithKey nextStackFn filteredStack
pure $! BSSC (LocMap { locMapRegs = rm, locMapStack = sm })

View File

@ -395,9 +395,9 @@ valueUses (AssignedValue (Assignment a rhs)) = do
rhs' <- foldlMFC (\s v -> seq s $ Set.union s <$> valueUses v) Set.empty rhs
seq rhs' $ modify' $ Map.insert (Some a) rhs'
pure $ rhs'
valueUses (Initial r) = do
valueUses (Initial r) =
pure $! Set.singleton (Some r)
valueUses _ = do
valueUses _ =
pure $! Set.empty
addValueUses :: (OrdF (ArchReg arch), FoldableFC (ArchFn arch))
@ -434,7 +434,7 @@ recordBlockTransfer _addr regs regSet = do
-> Some (ArchReg arch)
-> State (AssignmentCache (ArchReg arch) ids)
(FinalRegisterDemands (ArchReg arch))
doReg m (Some r) = do
doReg m (Some r) =
case testEquality r ip_reg of
Just _ -> pure m
Nothing -> do

View File

@ -38,7 +38,8 @@ import qualified Data.Set as Set
import GHC.Stack
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
import Data.Macaw.AbsDomain.JumpBounds
import Data.Macaw.AbsDomain.JumpBounds (initBndsMap)
import Data.Macaw.AbsDomain.StackAnalysis
import Data.Macaw.CFG.DemandSet
( DemandContext
, demandConstraints
@ -78,7 +79,7 @@ type AssignStackOffsetMap w ids = Map (Some (AssignId ids)) (MemInt w)
valueStackOffset :: ( MemWidth (ArchAddrWidth arch)
, OrdF (ArchReg arch)
)
=> InitJumpBounds arch
=> BlockStartStackConstraints arch
-> AssignStackOffsetMap (ArchAddrWidth arch) ids
-> Value arch ids (BVType (ArchAddrWidth arch))
-> Maybe (MemInt (ArchAddrWidth arch))
@ -87,9 +88,7 @@ valueStackOffset bnds amap v =
CValue{} ->
Nothing
Initial r ->
case boundsLocationInfo bnds (RegLoc r) of
(_rep, IsStackOffset o) -> Just o
_ -> Nothing
blockStartLocStackOffset bnds (RegLoc r)
AssignedValue (Assignment aid _) ->
Map.lookup (Some aid) amap
@ -251,7 +250,7 @@ data RegisterUseContext arch ids
-- particular locations after the block executes.
data BlockUsageSummary (arch :: Type) ids = RUS
{ -- | Information about stack layout/jump bounds at start of block.
blockPrecond :: !(InitJumpBounds arch)
blockPrecond :: !(BlockStartStackConstraints arch)
-- | Maps locations to the dependencies needed to compute values in that location.
, _blockInitDeps :: !(BlockProvideDepMap (ArchReg arch) ids)
-- | Dependencies needed to execute statements with side effects.
@ -266,9 +265,9 @@ data BlockUsageSummary (arch :: Type) ids = RUS
type RegisterUseM arch ids a =
ReaderT (RegisterUseContext arch ids) (StateT (BlockUsageSummary arch ids) (Except String)) a
initBlockUsageSummary :: InitJumpBounds arch -> BlockUsageSummary arch ids
initBlockUsageSummary bnds =
RUS { blockPrecond = bnds
initBlockUsageSummary :: BlockStartStackConstraints arch -> BlockUsageSummary arch ids
initBlockUsageSummary bCns =
RUS { blockPrecond = bCns
, _blockInitDeps = emptyBlockProvideDepMap
, _blockExecDemands = emptyDeps
, _assignmentCache = Map.empty
@ -459,7 +458,7 @@ summarizeBlock :: forall arch ids
-> ParsedBlock arch ids
-> Except String (BlockUsageSummary arch ids)
summarizeBlock ctx blk = do
let s0 = initBlockUsageSummary (blockJumpBounds blk)
let s0 = initBlockUsageSummary (initBndsMap (blockJumpBounds blk))
flip execStateT s0 $ flip runReaderT ctx $ do
let addr = pblockAddr blk
-- Add demanded values for terminal

View File

@ -158,7 +158,7 @@ cameFromUnsoundReason found_map rsn = do
-- | Add any values needed to compute term statement to demand set.
addTermDemands :: TermStmt arch ids -> DemandComp arch ids ()
addTermDemands t = do
addTermDemands t =
case t of
FetchAndExecute regs -> do
traverseF_ addValueDemands regs
@ -1218,10 +1218,11 @@ directJumpClassifier = classifierName "Jump" $ do
let abst = finalAbsBlockState (classifierAbsState bcc) (classifierFinalRegState bcc)
let abst' = abst & setAbsIP tgt_mseg
let tgtBnds = Jmp.postJumpBounds (classifierJumpBounds bcc) (classifierFinalRegState bcc)
pure $ ParsedContents { parsedNonterm = toList (classifierStmts bcc)
, parsedTerm = ParsedJump (classifierFinalRegState bcc) tgt_mseg
, writtenCodeAddrs = classifierWrittenAddrs bcc
, intraJumpTargets = [(tgt_mseg, abst', Jmp.nextBlockBounds (classifierJumpBounds bcc) (classifierFinalRegState bcc))]
, intraJumpTargets = [(tgt_mseg, abst', tgtBnds)]
, newFunctionAddrs = []
}
@ -1240,7 +1241,7 @@ jumpTableClassifier = classifierName "Jump table" $ do
let abst :: AbsBlockState (ArchReg arch)
abst = finalAbsBlockState (classifierAbsState bcc) (classifierFinalRegState bcc)
let nextBnds = Jmp.nextBlockBounds (classifierJumpBounds bcc) (classifierFinalRegState bcc)
let nextBnds = Jmp.postJumpBounds (classifierJumpBounds bcc) (classifierFinalRegState bcc)
let term = ParsedLookupTable (classifierFinalRegState bcc) jumpIndex entries
pure $ seq abst $
ParsedContents { parsedNonterm = toList (classifierStmts bcc)

View File

@ -0,0 +1,47 @@
{- |
This module defines a monad `Changed` that is designed for supporting
functions which take a value of some type as an argument, and may
modify it's value.
It is primarily used for abstract domains so that we know if we need
to re-explore a block when joining new edges into the state.
-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
module Data.Macaw.Utils.Changed
( Changed
, runChanged
, markChanged
, changedST
) where
import Control.Monad.Reader
import Control.Monad.ST
import Data.STRef
------------------------------------------------------------------------
-- Changed
-- | A monad that can be used to record when a value is changed.
newtype Changed s a = Changed { unChanged :: ReaderT (STRef s Bool) (ST s) a }
deriving (Functor, Applicative, Monad)
-- | Run a `ST` computation.
changedST :: ST s a -> Changed s a
changedST m = Changed (lift m)
-- | Record the value has changed if the Boolean is true.
markChanged :: Bool -> Changed s ()
markChanged False = pure ()
markChanged True = do
r <- Changed ask
changedST $ writeSTRef r True
runChanged :: forall a . (forall s . Changed s a) -> Maybe a
runChanged action = runST $ do
r <- newSTRef False
a <- runReaderT (unChanged action) r
b <- readSTRef r
pure $! if b then Just a else Nothing