{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}

-- | Functionality for computing the names and addresses of PLT stub functions
-- in a dynamically linked ELF binary.
--
-- Note that the API in this library is somewhat experimental, and as we further
-- develop the underlying approach (see @Note [Dynamic lookup scope]@), we may
-- need to change the API accordingly.
module Data.Macaw.Memory.ElfLoader.DynamicDependencies
  ( loadDynamicDependencies
  , parseDynNeeded
  , DynamicLoadingError(..)
  ) where

import qualified Control.Exception as X
import qualified Control.Monad.Catch as CMC
import qualified Data.ByteString as BS
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.ElfEdit as EE
import qualified Data.Set as Set
import qualified Data.Sequence as Seq
import qualified Data.Type.Equality as DTE
import qualified Prettyprinter as PP
import qualified System.Directory as SD
import qualified System.FilePath as SF

-- | Given a binary, load all of the shared libraries that it transitively
-- depends on, returning the order in which they were encountered in a
-- breadth-first search. Returning them in this order is important since we
-- rely on the assumption that the binaries are in the same order as the global
-- lookup scope. See @Note [Dynamic lookup scope]@.
--
-- This function makes a couple of simplifying assumptions about how shared
-- libraries work that are not true in general:
--
-- * All of the shared libraries that the main binary depends on are located in
--   a single directory.
--
-- * None of the binaries use @dlopen@, which can load additional shared
--   libraries at runtime.
loadDynamicDependencies ::
     forall w
   . EE.ElfMachine
  -- ^ The architecture of the main binary (x86-64, AArch32, etc.)
  -> EE.ElfClass w
  -- ^ Whether the main binary is 32-bit or 64-bit
  -> FilePath
  -- ^ The directory in which the shared libraries live
  -> EE.ElfHeaderInfo w
  -- ^ The main binary's ELF header info
  -> FilePath
  -- ^ The main binary's path
  -> IO [(EE.ElfHeaderInfo w, FilePath)]
  -- ^ The shared libraries in order of global lookup scope, paired with their
  -- file paths
loadDynamicDependencies :: forall (w :: Nat).
ElfMachine
-> ElfClass w
-> FilePath
-> ElfHeaderInfo w
-> FilePath
-> IO [(ElfHeaderInfo w, FilePath)]
loadDynamicDependencies ElfMachine
expectedHeaderMachine ElfClass w
expectedHeaderClass
                        FilePath
sharedObjectDir ElfHeaderInfo w
mainBinaryEHI FilePath
mainBinaryPath =
   -- First, parse the DT_NEEDED entries of the main binary.
   case ElfHeaderInfo w
-> FilePath -> Maybe (Either DynamicLoadingError [ByteString])
forall (w :: Nat).
ElfHeaderInfo w
-> FilePath -> Maybe (Either DynamicLoadingError [ByteString])
parseDynNeeded ElfHeaderInfo w
mainBinaryEHI FilePath
mainBinaryPath of
     -- If this returns Nothing, the binary isn't dynamically linked.
     -- We return an empty list of shared libraries in this case.
     Maybe (Either DynamicLoadingError [ByteString])
Nothing -> [(ElfHeaderInfo w, FilePath)] -> IO [(ElfHeaderInfo w, FilePath)]
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure []
     -- Otherwise, proceed with a breadth-first search of the DT_NEEDED entries
     -- in each of the shared libraries.
     Just Either DynamicLoadingError [ByteString]
dynNeededs -> do
       [ByteString]
dynNeededs' <- (DynamicLoadingError -> IO [ByteString])
-> ([ByteString] -> IO [ByteString])
-> Either DynamicLoadingError [ByteString]
-> IO [ByteString]
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either DynamicLoadingError -> IO [ByteString]
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM [ByteString] -> IO [ByteString]
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Either DynamicLoadingError [ByteString]
dynNeededs
       Set ByteString
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
go Set ByteString
forall a. Set a
Set.empty (Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)])
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
forall a b. (a -> b) -> a -> b
$ [ByteString] -> Seq ByteString
forall a. [a] -> Seq a
Seq.fromList [ByteString]
dynNeededs'
  where
    -- The main loop in the breadth-first search.
    go ::
      Set.Set BS.ByteString ->
      -- ^ The set of shared libraries that we have already loaded.
      Seq.Seq BS.ByteString ->
      -- ^ The queue of @DT_NEEDED@ entries that need to be processed.
      IO [(EE.ElfHeaderInfo w, FilePath)]
    go :: Set ByteString
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
go Set ByteString
sosSeenSoFar Seq ByteString
dynQueue =
      -- Dequeue the next DT_NEEDED entry on the queue.
      case Seq ByteString -> ViewL ByteString
forall a. Seq a -> ViewL a
Seq.viewl Seq ByteString
dynQueue of
        ViewL ByteString
Seq.EmptyL -> [(ElfHeaderInfo w, FilePath)] -> IO [(ElfHeaderInfo w, FilePath)]
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure []
        ByteString
dynNext Seq.:< Seq ByteString
dynRest
          |  -- If we have already loaded this shared library, skip it and
             -- process the rest of the queue.
             ByteString
dynNext ByteString -> Set ByteString -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.member` Set ByteString
sosSeenSoFar
          -> Set ByteString
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
go Set ByteString
sosSeenSoFar Seq ByteString
dynRest
          |  Bool
otherwise
          -> do let dynNextFP :: FilePath
dynNextFP = ByteString -> FilePath
UTF8.toString ByteString
dynNext
                let fullPath :: FilePath
fullPath = FilePath
sharedObjectDir FilePath -> FilePath -> FilePath
SF.</> FilePath
dynNextFP
                Bool
exists <- FilePath -> IO Bool
SD.doesFileExist FilePath
fullPath
                -- We only attempt to load a shared library if it exists on the
                -- user-specified path. Otherwise, we skip it and process the
                -- rest of the queue.
                --
                -- This is a design choice, as we could just as well throw an
                -- error here if the shared library does not exist. We opt to
                -- be somewhat lenient here because it's common for binaries to
                -- link against shared libraries that aren't reached from most
                -- code paths, such as libc. We want to be able to analyze these
                -- code paths without requiring the user to hunt down every last
                -- shared library that the binary could potentially use.
                if Bool
exists
                  then do
                    -- Read the shared library from disk and load it.
                    ByteString
soBytes <- FilePath -> IO ByteString
BS.readFile FilePath
fullPath
                    ElfHeaderInfo w
so <- FilePath -> ByteString -> IO (ElfHeaderInfo w)
loadSharedObject FilePath
dynNextFP ByteString
soBytes
                    case ElfHeaderInfo w
-> FilePath -> Maybe (Either DynamicLoadingError [ByteString])
forall (w :: Nat).
ElfHeaderInfo w
-> FilePath -> Maybe (Either DynamicLoadingError [ByteString])
parseDynNeeded ElfHeaderInfo w
so FilePath
dynNextFP of
                      -- By definition, each of the shared libraries
                      -- should be dynamically linked. If they're not,
                      -- something very fishy is going on, so throw an
                      -- error.
                      Maybe (Either DynamicLoadingError [ByteString])
Nothing -> DynamicLoadingError -> IO [(ElfHeaderInfo w, FilePath)]
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM (DynamicLoadingError -> IO [(ElfHeaderInfo w, FilePath)])
-> DynamicLoadingError -> IO [(ElfHeaderInfo w, FilePath)]
forall a b. (a -> b) -> a -> b
$ FilePath -> DynamicLoadingError
ElfNonDynamicSharedLib FilePath
dynNextFP
                      -- Otherwise, return the loaded shared library and
                      -- proceed. In the process of doing so, we add the
                      -- shared library to the set of already loaded
                      -- libraries and add the DT_NEEDED entries from the
                      -- shared library to the back of the queue.
                      Just Either DynamicLoadingError [ByteString]
dynNeededs -> do
                        [ByteString]
dynNeededs' <- (DynamicLoadingError -> IO [ByteString])
-> ([ByteString] -> IO [ByteString])
-> Either DynamicLoadingError [ByteString]
-> IO [ByteString]
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either DynamicLoadingError -> IO [ByteString]
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM [ByteString] -> IO [ByteString]
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Either DynamicLoadingError [ByteString]
dynNeededs
                        [(ElfHeaderInfo w, FilePath)]
sos <- Set ByteString
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
go (ByteString -> Set ByteString -> Set ByteString
forall a. Ord a => a -> Set a -> Set a
Set.insert ByteString
dynNext Set ByteString
sosSeenSoFar)
                                  (Seq ByteString
dynRest Seq ByteString -> Seq ByteString -> Seq ByteString
forall a. Semigroup a => a -> a -> a
<> [ByteString] -> Seq ByteString
forall a. [a] -> Seq a
Seq.fromList [ByteString]
dynNeededs')
                        [(ElfHeaderInfo w, FilePath)] -> IO [(ElfHeaderInfo w, FilePath)]
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure ((ElfHeaderInfo w
so, FilePath
dynNextFP)(ElfHeaderInfo w, FilePath)
-> [(ElfHeaderInfo w, FilePath)] -> [(ElfHeaderInfo w, FilePath)]
forall a. a -> [a] -> [a]
:[(ElfHeaderInfo w, FilePath)]
sos)
                   else Set ByteString
-> Seq ByteString -> IO [(ElfHeaderInfo w, FilePath)]
go Set ByteString
sosSeenSoFar Seq ByteString
dynRest

    -- Load a shared library from bytes.
    loadSharedObject ::
      FilePath ->
      -- ^ The path to the shared library
      BS.ByteString ->
      -- ^ The contents of the shared library
      IO (EE.ElfHeaderInfo w)
    loadSharedObject :: FilePath -> ByteString -> IO (ElfHeaderInfo w)
loadSharedObject FilePath
soName ByteString
soBytes =
      case ByteString -> Either (ByteOffset, FilePath) (SomeElf ElfHeaderInfo)
EE.decodeElfHeaderInfo ByteString
soBytes of
        Right (EE.SomeElf ElfHeaderInfo w
ehi) -> do
          let hdr :: ElfHeader w
hdr = ElfHeaderInfo w -> ElfHeader w
forall (w :: Nat). ElfHeaderInfo w -> ElfHeader w
EE.header ElfHeaderInfo w
ehi
          let actualHeaderClass :: ElfClass w
actualHeaderClass = ElfHeader w -> ElfClass w
forall (w :: Nat). ElfHeader w -> ElfClass w
EE.headerClass ElfHeader w
hdr
          let actualHeaderMachine :: ElfMachine
actualHeaderMachine = ElfHeader w -> ElfMachine
forall (w :: Nat). ElfHeader w -> ElfMachine
EE.headerMachine ElfHeader w
hdr

          if ElfMachine
actualHeaderMachine ElfMachine -> ElfMachine -> Bool
forall a. Eq a => a -> a -> Bool
== ElfMachine
expectedHeaderMachine
          then
            case ElfClass w -> ElfClass w -> Maybe (w :~: w)
forall (a :: Nat) (b :: Nat).
ElfClass a -> ElfClass b -> Maybe (a :~: b)
forall {k} (f :: k -> Type) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
DTE.testEquality ElfClass w
actualHeaderClass ElfClass w
expectedHeaderClass of
              Just w :~: w
DTE.Refl -> ElfHeaderInfo w -> IO (ElfHeaderInfo w)
forall a. a -> IO a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure ElfHeaderInfo w
ElfHeaderInfo w
ehi
              Maybe (w :~: w)
_ -> DynamicLoadingError -> IO (ElfHeaderInfo w)
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM (DynamicLoadingError -> IO (ElfHeaderInfo w))
-> DynamicLoadingError -> IO (ElfHeaderInfo w)
forall a b. (a -> b) -> a -> b
$ ElfClass w -> ElfClass w -> DynamicLoadingError
forall (w :: Nat) (w' :: Nat).
ElfClass w -> ElfClass w' -> DynamicLoadingError
SoMismatchedElfClass ElfClass w
actualHeaderClass
                                                     ElfClass w
expectedHeaderClass
          else DynamicLoadingError -> IO (ElfHeaderInfo w)
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM (DynamicLoadingError -> IO (ElfHeaderInfo w))
-> DynamicLoadingError -> IO (ElfHeaderInfo w)
forall a b. (a -> b) -> a -> b
$ ElfMachine -> ElfMachine -> DynamicLoadingError
SoMismatchedElfMachine ElfMachine
actualHeaderMachine
                                                   ElfMachine
expectedHeaderMachine
        Left (ByteOffset, FilePath)
_ -> DynamicLoadingError -> IO (ElfHeaderInfo w)
forall e a. (HasCallStack, Exception e) => e -> IO a
forall (m :: Type -> Type) e a.
(MonadThrow m, HasCallStack, Exception e) =>
e -> m a
CMC.throwM (DynamicLoadingError -> IO (ElfHeaderInfo w))
-> DynamicLoadingError -> IO (ElfHeaderInfo w)
forall a b. (a -> b) -> a -> b
$ FilePath -> DynamicLoadingError
NonElfBinaryFormat FilePath
soName

-- | Get values of @DT_NEEDED@ entries in an ELF file. If this is not a dynamic
-- executable, then @Nothing@ is returned.
parseDynNeeded ::
  EE.ElfHeaderInfo w ->
  FilePath ->
  -- ^ The path to the ELF file
  Maybe (Either DynamicLoadingError [BS.ByteString])
parseDynNeeded :: forall (w :: Nat).
ElfHeaderInfo w
-> FilePath -> Maybe (Either DynamicLoadingError [ByteString])
parseDynNeeded ElfHeaderInfo w
elfHeaderInfo FilePath
elfFP = ElfClass w
-> (ElfWidthConstraints w =>
    Maybe (Either DynamicLoadingError [ByteString]))
-> Maybe (Either DynamicLoadingError [ByteString])
forall (w :: Nat) a.
ElfClass w -> (ElfWidthConstraints w => a) -> a
EE.elfClassInstances ElfClass w
elfClass ((ElfWidthConstraints w =>
  Maybe (Either DynamicLoadingError [ByteString]))
 -> Maybe (Either DynamicLoadingError [ByteString]))
-> (ElfWidthConstraints w =>
    Maybe (Either DynamicLoadingError [ByteString]))
-> Maybe (Either DynamicLoadingError [ByteString])
forall a b. (a -> b) -> a -> b
$
  case (Phdr w -> Bool) -> [Phdr w] -> [Phdr w]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Phdr w
p -> Phdr w -> PhdrType
forall (w :: Nat). Phdr w -> PhdrType
EE.phdrSegmentType Phdr w
p PhdrType -> PhdrType -> Bool
forall a. Eq a => a -> a -> Bool
== PhdrType
EE.PT_DYNAMIC) [Phdr w]
elfPhdrs of
    [Phdr w
dynPhdr] -> Either DynamicLoadingError [ByteString]
-> Maybe (Either DynamicLoadingError [ByteString])
forall a. a -> Maybe a
Just (Either DynamicLoadingError [ByteString]
 -> Maybe (Either DynamicLoadingError [ByteString]))
-> Either DynamicLoadingError [ByteString]
-> Maybe (Either DynamicLoadingError [ByteString])
forall a b. (a -> b) -> a -> b
$
      let dynContents :: ByteString
dynContents = FileRange (ElfWordType w) -> ByteString -> ByteString
forall w. Integral w => FileRange w -> ByteString -> ByteString
EE.slice (Phdr w -> FileRange (ElfWordType w)
forall (w :: Nat). Phdr w -> FileRange (ElfWordType w)
EE.phdrFileRange Phdr w
dynPhdr) ByteString
elfBytes
      in case ElfData
-> ElfClass w
-> ByteString
-> Either DynamicError (DynamicSection w)
forall (w :: Nat).
ElfData
-> ElfClass w
-> ByteString
-> Either DynamicError (DynamicSection w)
EE.dynamicEntries (ElfHeader w -> ElfData
forall (w :: Nat). ElfHeader w -> ElfData
EE.headerData ElfHeader w
elfHeader) ElfClass w
elfClass ByteString
dynContents of
           Left DynamicError
dynError ->
             DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. a -> Either a b
Left (DynamicLoadingError -> Either DynamicLoadingError [ByteString])
-> DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. (a -> b) -> a -> b
$ FilePath -> DynamicError -> DynamicLoadingError
ElfDynamicParseError FilePath
elfFP DynamicError
dynError
           Right DynamicSection w
dynSection -> do
             case ByteString -> [Phdr w] -> Maybe (VirtAddrMap w)
forall (t :: Type -> Type) (w :: Nat).
(Foldable t, Integral (ElfWordType w)) =>
ByteString -> t (Phdr w) -> Maybe (VirtAddrMap w)
EE.virtAddrMap ByteString
elfBytes [Phdr w]
elfPhdrs of
               Maybe (VirtAddrMap w)
Nothing -> do
                 DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. a -> Either a b
Left (DynamicLoadingError -> Either DynamicLoadingError [ByteString])
-> DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. (a -> b) -> a -> b
$ FilePath -> DynamicLoadingError
ElfVirtualAddressMapError FilePath
elfFP
               Just VirtAddrMap w
phdrs ->
                 case DynamicSection w -> VirtAddrMap w -> Either FilePath [ByteString]
forall (w :: Nat).
DynamicSection w -> VirtAddrMap w -> Either FilePath [ByteString]
EE.dynNeeded DynamicSection w
dynSection VirtAddrMap w
phdrs of
                   Left FilePath
errMsg -> DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. a -> Either a b
Left (DynamicLoadingError -> Either DynamicLoadingError [ByteString])
-> DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> DynamicLoadingError
ElfDynamicNeededError FilePath
elfFP FilePath
errMsg
                   Right [ByteString]
deps -> [ByteString] -> Either DynamicLoadingError [ByteString]
forall a b. b -> Either a b
Right [ByteString]
deps
    [] -> Maybe (Either DynamicLoadingError [ByteString])
forall a. Maybe a
Nothing
    [Phdr w]
_  -> Either DynamicLoadingError [ByteString]
-> Maybe (Either DynamicLoadingError [ByteString])
forall a. a -> Maybe a
Just (Either DynamicLoadingError [ByteString]
 -> Maybe (Either DynamicLoadingError [ByteString]))
-> Either DynamicLoadingError [ByteString]
-> Maybe (Either DynamicLoadingError [ByteString])
forall a b. (a -> b) -> a -> b
$ DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. a -> Either a b
Left (DynamicLoadingError -> Either DynamicLoadingError [ByteString])
-> DynamicLoadingError -> Either DynamicLoadingError [ByteString]
forall a b. (a -> b) -> a -> b
$ FilePath -> DynamicLoadingError
ElfMultipleDynamicHeaders FilePath
elfFP
  where
    elfHeader :: ElfHeader w
elfHeader = ElfHeaderInfo w -> ElfHeader w
forall (w :: Nat). ElfHeaderInfo w -> ElfHeader w
EE.header ElfHeaderInfo w
elfHeaderInfo
    elfPhdrs :: [Phdr w]
elfPhdrs = ElfHeaderInfo w -> [Phdr w]
forall (w :: Nat). ElfHeaderInfo w -> [Phdr w]
EE.headerPhdrs ElfHeaderInfo w
elfHeaderInfo
    elfBytes :: ByteString
elfBytes = ElfHeaderInfo w -> ByteString
forall (w :: Nat). ElfHeaderInfo w -> ByteString
EE.headerFileContents ElfHeaderInfo w
elfHeaderInfo
    elfClass :: ElfClass w
elfClass = ElfHeader w -> ElfClass w
forall (w :: Nat). ElfHeader w -> ElfClass w
EE.headerClass ElfHeader w
elfHeader

data DynamicLoadingError where
  -- | Encountered a non-ELF binary format, which is not supported.
  NonElfBinaryFormat ::
       FilePath
    -> DynamicLoadingError

  -- | Encountered an error when parsing a dynamic section in an ELF binary.
  ElfDynamicParseError ::
       FilePath
    -> EE.DynamicError
    -> DynamicLoadingError

  -- | Encountered an error when parsing the @DT_NEEDED@ entries in a dynamic
  -- ELF binary.
  ElfDynamicNeededError ::
       FilePath
    -> String
    -> DynamicLoadingError

  -- | Could not constuct a virtual address map for an ELF binary.
  ElfVirtualAddressMapError ::
       FilePath
    -> DynamicLoadingError

  -- | Encountered multiple @PT_DYNAMIC@ program headers when parsing an ELF
  -- binary.
  ElfMultipleDynamicHeaders ::
       FilePath
    -> DynamicLoadingError

  -- | Encountered a shared library that is not dynamically linked.
  ElfNonDynamicSharedLib ::
       FilePath
    -> DynamicLoadingError

  -- | A provided shared object had a different ELF machine value than the main
  -- binary. The first argument is the 'EE.ElfMachine' for the shared object
  -- and the second argument is the 'EE.ElfMachine' for the main binary.
  SoMismatchedElfMachine ::
       EE.ElfMachine
    -> EE.ElfMachine
    -> DynamicLoadingError

  -- | A provided shared object had a different ELF class value than the main
  -- binary. The first argument is the 'EE.ElfClass' for the shared object
  -- and the second argument is the 'EE.ElfClass' for the main binary.
  SoMismatchedElfClass ::
       EE.ElfClass w
    -> EE.ElfClass w'
    -> DynamicLoadingError

deriving instance Show DynamicLoadingError

instance X.Exception DynamicLoadingError where
  displayException :: DynamicLoadingError -> FilePath
displayException = Doc Any -> FilePath
forall a. Show a => a -> FilePath
show (Doc Any -> FilePath)
-> (DynamicLoadingError -> Doc Any)
-> DynamicLoadingError
-> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DynamicLoadingError -> Doc Any
forall a ann. Pretty a => a -> Doc ann
forall ann. DynamicLoadingError -> Doc ann
PP.pretty

instance PP.Pretty DynamicLoadingError where
  pretty :: forall ann. DynamicLoadingError -> Doc ann
pretty DynamicLoadingError
e =
    case DynamicLoadingError
e of
      NonElfBinaryFormat FilePath
p ->
        Doc ann
"Unsupported, non-ELF binary format for file" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+>
        Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann
PP.dquotes (FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
p)

      ElfDynamicParseError FilePath
fp DynamicError
dynErr ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ DynamicError -> Doc ann
forall a ann. Show a => a -> Doc ann
PP.viaShow DynamicError
dynErr
          , Doc ann
"In the file:" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
fp
          ]
      ElfVirtualAddressMapError FilePath
fp ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ Doc ann
"Could not construct virtual address map"
          , Doc ann
"In the file:" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
fp
          ]
      ElfDynamicNeededError FilePath
fp FilePath
errMsg ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
errMsg
          , Doc ann
"In the file:" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
fp
          ]
      ElfMultipleDynamicHeaders FilePath
fp ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ Doc ann
"Encountered multiple PT_DYNAMIC program headers"
          , Doc ann
"In the file:" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
fp
          ]
      ElfNonDynamicSharedLib FilePath
fp ->
        Doc ann
"The shared library" Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> FilePath -> Doc ann
forall ann. FilePath -> Doc ann
forall a ann. Pretty a => a -> Doc ann
PP.pretty FilePath
fp Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+>
        Doc ann
"is not dynamically linked"
      SoMismatchedElfMachine ElfMachine
soMachine ElfMachine
mainMachine ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ Doc ann
"A shared object has a different ELF machine value than the main binary."
          , Doc ann
"Shared object has machine " Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> ElfMachine -> Doc ann
forall a ann. Show a => a -> Doc ann
PP.viaShow ElfMachine
soMachine Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+>
            Doc ann
"and main binary has machine " Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> ElfMachine -> Doc ann
forall a ann. Show a => a -> Doc ann
PP.viaShow ElfMachine
mainMachine
          ]
      SoMismatchedElfClass ElfClass w
soClass ElfClass w'
mainClass ->
        [Doc ann] -> Doc ann
forall ann. [Doc ann] -> Doc ann
PP.vcat
          [ Doc ann
"A shared object has a different ELF class value than the main binary."
          , Doc ann
"Shared object has class " Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> ElfClass w -> Doc ann
forall a ann. Show a => a -> Doc ann
PP.viaShow ElfClass w
soClass Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+>
            Doc ann
"and main binary has class " Doc ann -> Doc ann -> Doc ann
forall ann. Doc ann -> Doc ann -> Doc ann
PP.<+> ElfClass w' -> Doc ann
forall a ann. Show a => a -> Doc ann
PP.viaShow ElfClass w'
mainClass
          ]

{-
Note [Dynamic lookup scope]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
When looking up a dynamically linked function, which shared libraries should be
searched, and in which order? Answering this question precisely is tricky.

Section 1.5.4 of "How to Write Shared Libraries" on lookup scopes
(https://www.cs.dartmouth.edu/~sergey/cs258/ABI/UlrichDrepper-How-To-Write-Shared-Libraries.pdf)
offers a very detailed answer, although it is quite involved since it tries to
specify the behavior of `dlopen`. macaw needs to care about lookup scopes to
some degree, since it affects the semantics of dynamically linked programs, but
for now we prefer to pretend that `dlopen` isn't a thing. To borrow section
1.5.4's jargon, that means that we need to simulate the /global/ lookup scope,
but not local lookup scopes.

When macaw loads shared libraries, it returns them in the order dictated by the
global scope. An ELF binary's direct shared library dependencies are contained
in the DT_NEEDED entries, so to determine the global scope, we perform a
breadth-first search over the DT_NEEDED entries of the main binary and the
entries of the libraries that it depends on. See `loadDynamicDependencies` for
the full details.

Having the loaded binaries be in the same order as the global scope becomes
important when resolving dynamic function symbols. It's possible for two
binaries to define different functions of the same name. For example, imagine
this scenario:

  // a.c
  char f() { return 'a'; }

  // b.c
  char f() { return 'b'; }

  // main.c
  char f();
  int main() { printf("%c\n", f()); }

Suppose a.c and b.c are compiled to shared libraries liba.so and libb.so,
respectively, and that main.c is compiled to a main.exe binary that links
against both liba and libb. What will main.exe print when invoked? The answer
depends on the order of DT_NEEDED entries in main.exe, which in turn depends on
the order in which main.exe was linked against liba and libb when compiled. If
it was compiled like so:

  gcc -L. -la -lb main.c -o main.exe

Then the global scope will be:

  [main.exe, liba.so, libb.so]

And looking up f() will find liba.so first, causing 'a' to be printed. By a
similar token, if main.exe is compiled with `-lb -la` instead, then main.exe
would print 'b'.

This search order is implemented on the macaw-symbolic side in
Data.Macaw.Symbolic.Testing.runDiscovery, which puts all of the dynamic symbols
in a binary into a single Map. The symbols will be inserted into the map in the
order the binaries appear in the global scope, and in the event that we
encounter a symbol that has already been inserted into the map, we will always
prefer the already-inserted symbol, as that appears first in the global scope.

It's worth emphasizing that this implementation fundamentally relies on the
absence of dlopen. If we need to model dlopen in the future, we will have to
pick a more sophisticated implementation approach.
-}