content: {-|
  Module      : Unicorn.Hook
  Description : Unicorn hooks.
  Copyright   : (c) Adrian Herrera, 2016
  License     : GPL-2
  
  Insert hook points into the Unicorn emulator engine.
  -}
  module Unicorn.Hook
      ( -- * Hook types
        Hook
      , MemoryHookType(..)
      , MemoryEventHookType(..)
      , MemoryAccess(..)
  
        -- * Hook callbacks
      , CodeHook
      , InterruptHook
      , BlockHook
      , InHook
      , OutHook
      , SyscallHook
      , MemoryHook
      , MemoryReadHook
      , MemoryWriteHook
      , MemoryEventHook
  
        -- * Hook callback management
      , codeHookAdd
      , interruptHookAdd
      , blockHookAdd
      , inHookAdd
      , outHookAdd
      , syscallHookAdd
      , memoryHookAdd
      , memoryEventHookAdd
      , hookDel
      ) where
  
  import Control.Monad
  import Control.Monad.Trans.Class
  import Control.Monad.Trans.Except (ExceptT (..), throwE)
  import Foreign
  
  import Unicorn.Internal.Core
  import Unicorn.Internal.Hook
  import qualified Unicorn.CPU.X86 as X86
  
  -------------------------------------------------------------------------------
  -- Hook callback management (registration and deletion)
  -------------------------------------------------------------------------------
  
  -- | Register a callback for a code hook event.
  codeHookAdd :: Storable a
              => Engine           -- ^ 'Unicorn' engine handle
              -> CodeHook a       -- ^ Code hook callback
              -> a                -- ^ User-defined data. This will be passed to
                                  -- the callback function
              -> Word64           -- ^ Start address
              -> Word64           -- ^ End address
              -> Emulator Hook    -- ^ The hook handle on success, or an 'Error'
                                  -- on failure
  codeHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalCodeHook callback
          getResult $ ucHookAdd uc HookCode funPtr userDataPtr begin end
  
  -- | Register a callback for an interrupt hook event.
  interruptHookAdd :: Storable a
                   => Engine          -- ^ 'Unicorn' engine handle
                   -> InterruptHook a -- ^ Interrupt callback
                   -> a               -- ^ User-defined data. This will be passed
                                      -- to the callback function
                   -> Word64          -- ^ Start address
                   -> Word64          -- ^ End address
                   -> Emulator Hook   -- ^ The hook handle on success, or 'Error'
                                      -- on failure
  interruptHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalInterruptHook callback
          getResult $ ucHookAdd uc HookIntr funPtr userDataPtr begin end
  
  -- | Register a callback for a block hook event.
  blockHookAdd :: Storable a
               => Engine          -- ^ 'Unicorn' engine handle
               -> BlockHook a     -- ^ Block callback
               -> a               -- ^ User-defined data. This will be passed to
                                  -- the callback function
               -> Word64          -- ^ Start address
               -> Word64          -- ^ End address
               -> Emulator Hook   -- ^ The hook handle on success, or an 'Error'
                                  -- on failure
  blockHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalBlockHook callback
          getResult $ ucHookAdd uc HookBlock funPtr userDataPtr begin end
  
  -- | Register a callback for an IN instruction hook event (X86).
  inHookAdd :: Storable a
            => Engine         -- ^ 'Unicorn' engine handle
            -> InHook a       -- ^ IN instruction callback
            -> a              -- ^ User-defined data. This will be passed to the
                              -- callback function
            -> Word64         -- ^ Start address
            -> Word64         -- ^ End address
            -> Emulator Hook  -- ^ The hook handle on success, or an 'Error' on
                              -- failure
  inHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalInHook callback
          getResult $ ucInsnHookAdd uc HookInsn funPtr userDataPtr begin end
                                    X86.In
  
  -- | Register a callback for an OUT instruction hook event (X86).
  outHookAdd :: Storable a
             => Engine        -- ^ 'Unicorn' engine handle
             -> OutHook a     -- ^ OUT instruction callback
             -> a             -- ^ User-defined data. This will be passed to the
                              -- callback function
             -> Word64        -- ^ Start address
             -> Word64        -- ^ End address
             -> Emulator Hook -- ^ The hook handle on success, or an 'Error' on
                              -- failure
  outHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalOutHook callback
          getResult $ ucInsnHookAdd uc HookInsn funPtr userDataPtr begin end
                                    X86.Out
  
  -- | Register a callback for a SYSCALL instruction hook event (X86).
  syscallHookAdd :: Storable a
                 => Engine        -- ^ 'Unicorn' engine handle
                 -> SyscallHook a -- ^ SYSCALL instruction callback
                 -> a             -- ^ User-defined data. This will be passed to
                                  -- the callback function
                 -> Word64        -- ^ Start address
                 -> Word64        -- ^ End address
                 -> Emulator Hook -- ^ The hook handle on success, or an 'Error'
                                  -- on failure
  syscallHookAdd uc callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalSyscallHook callback
          getResult $ ucInsnHookAdd uc HookInsn funPtr userDataPtr begin end
                                    X86.Syscall
  
  -- | Register a callback for a valid memory access event.
  memoryHookAdd :: Storable a
                => Engine         -- ^ 'Unicorn' engine handle
                -> MemoryHookType -- ^ A valid memory access (e.g. read, write,
                                  -- etc.) to trigger the callback on
                -> MemoryHook a   -- ^ Memory access callback
                -> a              -- ^ User-defined data. This will be passed to
                                  -- the callback function
                -> Word64         -- ^ Start address
                -> Word64         -- ^ End address
                -> Emulator Hook  -- ^ The hook handle on success, or an 'Error'
                                  -- on failure
  memoryHookAdd uc memHookType callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalMemoryHook callback
          getResult $ ucHookAdd uc memHookType funPtr userDataPtr begin end
  
  -- | Register a callback for an invalid memory access event.
  memoryEventHookAdd :: Storable a
                     => Engine                -- ^ 'Unicorn' engine handle
                     -> MemoryEventHookType   -- ^ An invalid memory access (e.g.
                                              -- read, write, etc.) to trigger
                                              -- the callback on
                     -> MemoryEventHook a     -- ^ Invalid memory access callback
                     -> a                     -- ^ User-defined data. This will
                                              -- be passed to the callback
                                              -- function
                     -> Word64                -- ^ Start address
                     -> Word64                -- ^ End address
                     -> Emulator Hook         -- ^ The hook handle on success, or
                                              -- an 'Error' on failure
  memoryEventHookAdd uc memEventHookType callback userData begin end =
      ExceptT . alloca $ \userDataPtr -> do
          poke userDataPtr userData
          funPtr <- marshalMemoryEventHook callback
          getResult $ ucHookAdd uc memEventHookType funPtr userDataPtr begin end
  
  -- | Unregister (remove) a hook callback.
  hookDel :: Engine       -- ^ 'Unicorn' engine handle
          -> Hook         -- ^ 'Hook' handle
          -> Emulator ()  -- ^ 'ErrOk' on success, or other value on failure
  hookDel uc hook = do
      err <- lift $ ucHookDel uc hook
      if err == ErrOk then
          pure ()
      else
          throwE err
  
  -------------------------------------------------------------------------------
  -- Helper functions
  -------------------------------------------------------------------------------
  
  -- Takes the tuple returned by `ucHookAdd`, an IO (Error, Hook), and
  -- returns either a `Right Hook` if no error occurred or a `Left Error` if an
  -- error occurred
  getResult :: IO (Error, Hook)
            -> IO (Either Error Hook)
  getResult =
      liftM (uncurry checkResult)
      where checkResult err hook =
              if err == ErrOk then
                  Right hook
              else
                  Left err
  