melf-1.3.1: src/Data/Internal/Elf.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTSyntax #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Internal.Elf where
import Control.Exception.ChainedException
import Data.Elf.Constants
import Data.Elf.Headers hiding (Header)
import qualified Data.Elf.Headers as H
import Data.Interval as I
import Control.Lens.Combinators hiding (contains)
import Control.Lens.Operators
import Control.Monad
import Control.Monad.Catch
import Control.Monad.State as MS
import Data.Binary
import Data.Bits as Bin
import Data.ByteString.Lazy.Char8 as BSL8
import Data.ByteString.Lazy as BSL
import Data.Foldable
import Data.Int
import qualified Data.List as L
import Data.Maybe
import Data.Monoid
-- | @RBuilder@ is an intermediate internal data type that is used by parser.
-- It contains information about layout of the ELF file that can be used
-- by `Data.Elf.PrettyPrint.printLayout`
data RBuilder c
= RBuilderHeader
{ rbhHeader :: HeaderXX c
}
| RBuilderSectionTable
{ rbstHeader :: HeaderXX c
}
| RBuilderSegmentTable
{ rbptHeader :: HeaderXX c
}
| RBuilderSection
{ rbsHeader :: SectionXX c
, rbsN :: ElfSectionIndex
, rbsName :: String
}
| RBuilderSegment
{ rbpHeader :: SegmentXX c
, rbpN :: Word16
, rbpData :: [RBuilder c]
}
| RBuilderRawData
{ rbrdInterval :: Interval (WordXX c)
}
| RBuilderRawAlign
{ rbraOffset :: WordXX c
, rbraAlign :: WordXX c
}
data LZip a = LZip [a] (Maybe a) [a]
instance Foldable LZip where
foldMap f (LZip l (Just c) r) = foldMap f $ LZip l Nothing (c : r)
foldMap f (LZip l Nothing r) = foldMap f $ L.reverse l ++ r
-- | t`Elf` is a forrest of trees of type `ElfXX`.
-- Trees are composed of `ElfXX` nodes, `ElfSegment` can contain subtrees
data ElfNodeType = Header | SectionTable | SegmentTable | Section | Segment | RawData | RawAlign
-- | List of ELF nodes.
data ElfListXX c where
ElfListCons :: ElfXX t c -> ElfListXX c -> ElfListXX c
ElfListNull :: ElfListXX c
-- | Elf is a sigma type where the first entry defines the type of the second one
data Elf = forall a . Elf (SingElfClass a) (ElfListXX a)
-- | Section data may contain a string table.
-- If a section contains a string table with section names, the data
-- for such a section is generated and `esData` should contain `ElfSectionDataStringTable`
data ElfSectionData c
= ElfSectionData -- ^ Regular section data
{ esdData :: BSL.ByteString -- ^ The content of the section
}
| ElfSectionDataStringTable -- ^ Section data will be generated from section names
| ElfSectionDataNoBits -- ^ SHT_NOBITS uninitialized section data: section has size but no content
{ esdSize :: WordXX c -- ^ Size of the section
}
-- | The type of node that defines Elf structure.
data ElfXX t c where
ElfHeader ::
{ ehData :: ElfData -- ^ Data encoding (big- or little-endian)
, ehOSABI :: ElfOSABI -- ^ OS/ABI identification
, ehABIVersion :: Word8 -- ^ ABI version
, ehType :: ElfType -- ^ Object file type
, ehMachine :: ElfMachine -- ^ Machine type
, ehEntry :: WordXX c -- ^ Entry point address
, ehFlags :: Word32 -- ^ Processor-specific flags
} -> ElfXX 'Header c
ElfSectionTable :: ElfXX 'SectionTable c
ElfSegmentTable :: ElfXX 'SegmentTable c
ElfSection ::
{ esName :: String -- ^ Section name (NB: string, not offset in the string table)
, esType :: ElfSectionType -- ^ Section type
, esFlags :: ElfSectionFlag -- ^ Section attributes
, esAddr :: WordXX c -- ^ Virtual address in memory
, esAddrAlign :: WordXX c -- ^ Address alignment boundary
, esEntSize :: WordXX c -- ^ Size of entries, if section has table
, esN :: ElfSectionIndex -- ^ Section number
, esInfo :: Word32 -- ^ Miscellaneous information
, esLink :: Word32 -- ^ Link to other section
, esData :: ElfSectionData c -- ^ The content of the section
} -> ElfXX 'Section c
ElfSegment ::
{ epType :: ElfSegmentType -- ^ Type of segment
, epFlags :: ElfSegmentFlag -- ^ Segment attributes
, epVirtAddr :: WordXX c -- ^ Virtual address in memory
, epPhysAddr :: WordXX c -- ^ Physical address
, epAddMemSize :: WordXX c -- ^ Add this amount of memory after the section when the section is loaded to memory by execution system.
-- Or, in other words this is how much `pMemSize` is bigger than `pFileSize`
, epAlign :: WordXX c -- ^ Alignment of segment
, epData :: ElfListXX c -- ^ Content of the segment
} -> ElfXX 'Segment c
-- | Some ELF files (some executables) don't bother to define
-- sections for linking and have just raw data in segments.
ElfRawData ::
{ edData :: BSL.ByteString -- ^ Raw data in ELF file
} -> ElfXX 'RawData c
-- | Align the next data in the ELF file.
-- The offset of the next data in the ELF file
-- will be the minimal @x@ such that
-- @x mod eaAlign == eaOffset mod eaAlign @
ElfRawAlign ::
{ eaOffset :: WordXX c -- ^ Align value
, eaAlign :: WordXX c -- ^ Align module
} -> ElfXX 'RawAlign c
data WBuilderData
= WBuilderDataHeader
| WBuilderDataByteStream { wbdData :: BSL.ByteString }
| WBuilderDataSectionTable
| WBuilderDataSegmentTable
data WBuilderState a =
WBuilderState
{ _wbsSections :: [(ElfSectionIndex, SectionXX a)]
, _wbsSegmentsReversed :: [SegmentXX a]
, _wbsDataReversed :: [WBuilderData]
, _wbsOffset :: WordXX a
, _wbsPhOff :: WordXX a
, _wbsShOff :: WordXX a
, _wbsShStrNdx :: ElfSectionIndex
, _wbsNameIndexes :: [Int64]
}
makeLenses ''WBuilderState
infixr 9 ~:
-- | Helper for `ElfListCons`
(~:) :: ElfXX t a -> ElfListXX a -> ElfListXX a
(~:) = ElfListCons
foldMapElfList :: Monoid m => (forall t' . (ElfXX t' a -> m)) -> ElfListXX a -> m
foldMapElfList f (ElfListCons v@(ElfSegment { .. }) l) = f v <> foldMapElfList f epData <> foldMapElfList f l
foldMapElfList f (ElfListCons v l) = f v <> foldMapElfList f l
foldMapElfList _ ElfListNull = mempty
foldMapElfList' :: Monoid m => (forall t' . (ElfXX t' a -> m)) -> ElfListXX a -> m
foldMapElfList' f (ElfListCons v l) = f v <> foldMapElfList' f l
foldMapElfList' _ ElfListNull = mempty
mapMElfList :: Monad m => (forall t' . (ElfXX t' a -> m b)) -> ElfListXX a -> m [b]
mapMElfList f l = sequence $ foldMapElfList' ((: []) . f) l
headerInterval :: forall a . SingElfClassI a => HeaderXX a -> Interval (WordXX a)
headerInterval _ = I 0 $ headerSize $ fromSingElfClass $ singElfClass @a
sectionTableInterval :: SingElfClassI a => HeaderXX a -> Interval (WordXX a)
sectionTableInterval HeaderXX{..} = I hShOff $ fromIntegral $ hShEntSize * hShNum
segmentTableInterval :: SingElfClassI a => HeaderXX a -> Interval (WordXX a)
segmentTableInterval HeaderXX{..} = I hPhOff $ fromIntegral $ hPhEntSize * hPhNum
sectionInterval :: SingElfClassI a => SectionXX a -> Interval (WordXX a)
sectionInterval SectionXX{..} = I sOffset if sType == SHT_NOBITS then 0 else sSize
segmentInterval :: SingElfClassI a => SegmentXX a -> Interval (WordXX a)
segmentInterval SegmentXX{..} = I pOffset pFileSize
rBuilderInterval :: SingElfClassI a => RBuilder a -> Interval (WordXX a)
rBuilderInterval RBuilderHeader{..} = headerInterval rbhHeader
rBuilderInterval RBuilderSectionTable{..} = sectionTableInterval rbstHeader
rBuilderInterval RBuilderSegmentTable{..} = segmentTableInterval rbptHeader
rBuilderInterval RBuilderSection{..} = sectionInterval rbsHeader
rBuilderInterval RBuilderSegment{..} = segmentInterval rbpHeader
rBuilderInterval RBuilderRawData{..} = rbrdInterval
rBuilderInterval RBuilderRawAlign{} = error "Internal error: rBuilderInterval is not defined for RBuilderRawAlign"
findInterval :: (Ord t, Num t) => (a -> Interval t) -> t -> [a] -> LZip a
findInterval f e = findInterval' []
where
findInterval' l [] = LZip l Nothing []
findInterval' l (x : xs) | e `touches` f x = LZip l (Just x) xs
| e < offset (f x) = LZip l Nothing (x : xs)
| otherwise = findInterval' (x : l) xs
touches a i | I.empty i = a == offset i
| otherwise = a `member` i
showRBuilder' :: RBuilder a -> String
showRBuilder' RBuilderHeader{} = "header"
showRBuilder' RBuilderSectionTable{} = "section table"
showRBuilder' RBuilderSegmentTable{} = "segment table"
showRBuilder' RBuilderSection{..} = "section " ++ show rbsN
showRBuilder' RBuilderSegment{..} = "segment " ++ show rbpN
showRBuilder' RBuilderRawData{} = "raw data" -- should not be called
showRBuilder' RBuilderRawAlign{} = "alignment" -- should not be called
showRBuilder :: SingElfClassI a => RBuilder a -> String
showRBuilder v = showRBuilder' v ++ " (" ++ show (rBuilderInterval v) ++ ")"
-- showERBList :: SingElfClassI a => [RBuilder a] -> String
-- showERBList l = "[" ++ (L.concat $ L.intersperse ", " $ fmap showRBuilder l) ++ "]"
intersectMessage :: SingElfClassI a => RBuilder a -> RBuilder a -> String
intersectMessage x y = showRBuilder x ++ " and " ++ showRBuilder y ++ " intersect"
addRBuilders :: forall a m . (SingElfClassI a, MonadCatch m) => [RBuilder a] -> m [RBuilder a]
addRBuilders newts =
let
addRBuilders' f newts' l = foldM (flip f) l newts'
addRBuilderEmpty :: (SingElfClassI a, MonadCatch m) => RBuilder a -> [RBuilder a] -> m [RBuilder a]
addRBuilderEmpty t ts =
-- (unsafePerformIO $ Prelude.putStrLn $ "Add Empty " ++ showRBuilder t ++ " to " ++ showERBList ts) `seq`
let
to' = offset $ rBuilderInterval t
(LZip l c' r) = findInterval rBuilderInterval to' ts
-- Let `(le, lo)` is the result of `allEmptyStarting a l`.
-- Then `le` is the initial sublist of `l` each element of which is empty and starts at `a`,
-- `lo` is the rest of `l`.
allEmptyStartingAt :: WordXX a -> [RBuilder a] -> ([RBuilder a], [RBuilder a])
allEmptyStartingAt a ls = f ([], ls)
where
f (le, []) = (L.reverse le, [])
f (le, h : lo) =
let
hi = rBuilderInterval h
in if not (I.empty hi) || (offset hi /= a)
then (L.reverse le, h : lo)
else f (h : le, lo)
in case c' of
Just RBuilderSegment{..} -> do
d <- $addContext' $ addRBuilderEmpty t rbpData
return $ toList $ LZip l (Just RBuilderSegment{ rbpData = d, .. }) r
Just c ->
if offset (rBuilderInterval c) /= to' then
$chainedError $ intersectMessage t c
else
let
(ce, re') = allEmptyStartingAt to' (c : r)
in case t of
RBuilderSegment{..} ->
return $ toList $ LZip l (Just RBuilderSegment{ rbpData = ce, .. }) re'
_ ->
return $ toList $ LZip l Nothing (ce ++ (t : re'))
Nothing -> return $ toList $ LZip l (Just t) r
addRBuilderNonEmpty :: (SingElfClassI a, MonadCatch m) => RBuilder a -> [RBuilder a] -> m [RBuilder a]
addRBuilderNonEmpty t ts =
-- (unsafePerformIO $ Prelude.putStrLn $ "Add NonEmpty " ++ showRBuilder t ++ " to " ++ showERBList ts) `seq`
let
ti = rBuilderInterval t
(LZip l c' r) = findInterval rBuilderInterval (offset ti) ts
addRBuildersNonEmpty :: (SingElfClassI a, MonadCatch m) => [RBuilder a] -> RBuilder a -> m (RBuilder a)
addRBuildersNonEmpty [] x = return x
addRBuildersNonEmpty ts' RBuilderSegment{..} = do
d <- $addContext' $ addRBuilders' addRBuilderNonEmpty ts' rbpData
return RBuilderSegment{ rbpData = d, .. }
addRBuildersNonEmpty (x:_) y = $chainedError $ intersectMessage x y
in case c' of
Just c ->
if ti == rBuilderInterval c then
case t of
-- NB: If a segment A has number greater than segment B and they have same size, then
-- segment A contains segment B
-- This should be taken into account in the serialization code.
RBuilderSegment{..} ->
return $ toList $ LZip l (Just RBuilderSegment{ rbpData = [c], .. }) r
_ -> do
c'' <- $addContext' $ addRBuildersNonEmpty [t] c
return $ toList $ LZip l (Just c'') r
else if rBuilderInterval c `contains` ti then do
c'' <- $addContext' $ addRBuildersNonEmpty [t] c
return $ toList $ LZip l (Just c'') r
else if ti `contains` rBuilderInterval c then
let
tir = offset ti + size ti - 1
(LZip l2 c2' r2) = findInterval rBuilderInterval tir r
in case c2' of
Nothing -> do
-- add this: ......[t__________________________]...................
-- to this list: ......[c__]......[l2__]...[l2__].....[________].......
-- no need to keep the order of l2 as each member of the list will be placed independently from scratch
c'' <- $addContext' $ addRBuildersNonEmpty (c : l2) t
return $ toList $ LZip l (Just c'') r2
Just c2 ->
if ti `contains` rBuilderInterval c2 then do
-- add this: ......[t______________________]........................
-- to this list: ......[c_________]......[c2___]......[________]........
c'' <- $addContext' $ addRBuildersNonEmpty (c : c2 : l2) t
return $ toList $ LZip l (Just c'') r2
else
-- add this: ......[t_________________].............................
-- to this list: ......[c_________]......[c2___]......[________]........
$chainedError $ intersectMessage t c2
else
-- add this: ..........[t________].............................
-- to this list: ......[c_________]......[_____]......[________]...
$chainedError $ intersectMessage t c
Nothing ->
let
tir = offset ti + size ti - 1
(LZip l2 c2' r2) = findInterval rBuilderInterval tir r
in case c2' of
Nothing -> do
-- add this: ....[t___].........................................
-- or this: ....[t_________________________]...................
-- to this list: .............[l2__]...[l2__].....[________]........
c'' <- $addContext' $ addRBuildersNonEmpty l2 t
return $ toList $ LZip l (Just c'') r2
Just c2 ->
if ti `contains` rBuilderInterval c2 then do
-- add this: ....[t_________________________________]........
-- to this list: ..........[l2__]..[l2__].....[c2_______]........
c'' <- $addContext' $ addRBuildersNonEmpty (c2 : l2) t
return $ toList $ LZip l (Just c'') r2
else
-- add this: ....[t_______________________________]..........
-- to this list: ..........[l2__]..[l2__].....[c2_______]........
$chainedError $ intersectMessage t c2
(emptyRBs, nonEmptyRBs) = L.partition (I.empty . rBuilderInterval) newts
in
addRBuilders' addRBuilderNonEmpty nonEmptyRBs [] >>= addRBuilders' addRBuilderEmpty emptyRBs
-- | Find section with a given number
elfFindSection :: forall a m b . (SingElfClassI a, MonadThrow m, Integral b, Show b)
=> ElfListXX a -- ^ Structured ELF data
-> b -- ^ Number of the section
-> m (ElfXX 'Section a) -- ^ The section in question
elfFindSection elfs n = if n == 0
then $chainedError "no section 0"
else $maybeAddContext ("no section " ++ show n) maybeSection
where
maybeSection = getFirst $ foldMapElfList f elfs
f :: ElfXX t a -> First (ElfXX 'Section a)
f s@ElfSection{..} | esN == fromIntegral n = First $ Just s
f _ = First Nothing
-- | Find section with a given name
elfFindSectionByName :: forall a m . (SingElfClassI a, MonadThrow m)
=> ElfListXX a -- ^ Structured ELF data
-> String -- ^ Section name
-> m (ElfXX 'Section a) -- ^ The section in question
elfFindSectionByName elfs n = $maybeAddContext ("no section \"" ++ show n ++ "\"") maybeSection
where
maybeSection = getFirst $ foldMapElfList f elfs
f :: ElfXX t a -> First (ElfXX 'Section a)
f s@ElfSection{..} | esName == n = First $ Just s
f _ = First Nothing
-- | Find ELF header
elfFindHeader :: forall a m . (SingElfClassI a, MonadThrow m)
=> ElfListXX a -- ^ Structured ELF data
-> m (ElfXX 'Header a) -- ^ ELF header
elfFindHeader elfs = $maybeAddContext "no header" maybeHeader
where
maybeHeader = getFirst $ foldMapElfList f elfs
f :: ElfXX t a -> First (ElfXX 'Header a)
f h@ElfHeader{} = First $ Just h
f _ = First Nothing
cut :: BSL.ByteString -> Int64 -> Int64 -> BSL.ByteString
cut content offset size = BSL.take size $ BSL.drop offset content
tail' :: [a] -> [a]
tail' [] = []
tail' (_ : xs) = xs
nextOffset :: SingElfClassI a => WordXX a -> WordXX a -> WordXX a -> WordXX a
nextOffset _ 0 a = a
nextOffset t m a | m .&. (m - 1) /= 0 = error $ "align module is not power of two " ++ show m
| otherwise = if a' + t' < a then a' + m + t' else a' + t'
where
a' = a .&. complement (m - 1)
t' = t .&. (m - 1)
addRawData :: forall a . SingElfClassI a => BSL.ByteString -> [RBuilder a] -> [RBuilder a]
addRawData _ [] = []
addRawData bs rBuilders = snd $ addRawData' 0 (lrbie, rBuilders)
where
-- e, e', ee and lrbie stand for the first occupied byte after the place being fixed
-- lrbi: last rBuilder interval (begin, size)
lrbi@(I lrbib lrbis) = rBuilderInterval $ L.last rBuilders
lrbie = if I.empty lrbi then lrbib else lrbib + lrbis
allEmpty :: WordXX a -> WordXX a -> Bool
allEmpty b s = BSL.all (== 0) bs'
where
bs' = cut bs (fromIntegral b) (fromIntegral s)
addRawData' :: WordXX a -> (WordXX a, [RBuilder a]) -> (WordXX a, [RBuilder a])
addRawData' alignHint (e, rbs) = L.foldr f (e, []) $ fmap fixRBuilder rbs
where
f rb (e', rbs') =
let
i@(I b s) = rBuilderInterval rb
b' = if I.empty i then b else b + s
rbs'' = addRaw b' e' rbs'
in
(b, rb : rbs'')
fixRBuilder :: RBuilder a -> RBuilder a
fixRBuilder p | I.empty $ rBuilderInterval p = p
fixRBuilder p@RBuilderSegment{..} =
RBuilderSegment{ rbpData = addRaw b ee' rbs', ..}
where
(I b s) = rBuilderInterval p
ee = b + s
alignHint' = max (pAlign rbpHeader) alignHint
(ee', rbs') = addRawData' alignHint' (ee, rbpData)
fixRBuilder x = x
-- b is the first free byte
addRaw :: WordXX a -> WordXX a -> [RBuilder a] -> [RBuilder a]
addRaw b ee rbs' =
if b < ee
then
if not $ allEmpty b s
then
RBuilderRawData (I b s) : rbs'
else
-- check e' < ee means
-- check if next section/segment was actually placed (ee) with greater offset
-- than is required by alignment rules (e')
if e' < ee && e'' == ee
then
RBuilderRawAlign ee alignHint : rbs'
else
rbs'
else
rbs'
where
s = ee - b
eAddr = case rbs' of
(RBuilderSegment{rbpHeader = SegmentXX{..}} : _) -> pVirtAddr
_ -> 0
eAddrAlign = case rbs' of
(RBuilderSegment{rbpHeader = SegmentXX{..}} : _) -> pAlign
(RBuilderSection{rbsHeader = SectionXX{..}} : _) -> sAddrAlign
_ -> wordSize $ fromSingElfClass $ singElfClass @a
-- e' here is the address of the next section/segment
-- according to the regular alignment rules
e' = nextOffset eAddr eAddrAlign b
e'' = nextOffset ee alignHint b
infix 9 !!?
(!!?) :: (Integral b) => [a] -> b -> Maybe a
(!!?) xs i
| i < 0 = Nothing
| otherwise = go i xs
where
go :: (Integral b) => b -> [a] -> Maybe a
go 0 (x:_) = Just x
go j (_:ys) = go (j - 1) ys
go _ [] = Nothing
-- | Parse ELF file and produce [`RBuilder`]
parseRBuilder :: (SingElfClassI a, MonadCatch m)
=> HeaderXX a -- ^ ELF header
-> [SectionXX a] -- ^ Section table
-> [SegmentXX a] -- ^ Segment table
-> BSL.ByteString -- ^ ELF file
-> m [RBuilder a]
parseRBuilder hdr@HeaderXX{..} ss ps bs = do
let
maybeStringSectionData = getSectionData bs <$> (ss !!? hShStrNdx)
mkRBuilderSection :: (SingElfClassI a, MonadCatch m) => (ElfSectionIndex, SectionXX a) -> m (RBuilder a)
mkRBuilderSection (n, s@SectionXX{..}) = do
stringSectionData <- $maybeAddContext "No string table" maybeStringSectionData
return $ RBuilderSection s n $ getString stringSectionData $ fromIntegral sName
mkRBuilderSegment :: (SingElfClassI a, MonadCatch m) => (Word16, SegmentXX a) -> m (RBuilder a)
mkRBuilderSegment (n, p) = return $ RBuilderSegment p n []
sections <- mapM mkRBuilderSection $ tail' $ Prelude.zip [0 .. ] ss
segments <- mapM mkRBuilderSegment $ Prelude.zip [0 .. ] ps
let
header = RBuilderHeader hdr
maybeSectionTable = if hShNum == 0 then Nothing else Just $ RBuilderSectionTable hdr
maybeSegmentTable = if hPhNum == 0 then Nothing else Just $ RBuilderSegmentTable hdr
rbs <- addRBuilders $ [header] ++ maybeToList maybeSectionTable
++ maybeToList maybeSegmentTable
++ segments
++ sections
return $ addRawData bs rbs
parseElf' :: forall a m . (SingElfClassI a, MonadCatch m) =>
HeaderXX a ->
[SectionXX a] ->
[SegmentXX a] ->
BSL.ByteString -> m Elf
parseElf' hdr@HeaderXX{..} ss ps bs = do
rbs <- parseRBuilder hdr ss ps bs
let
rBuilderToElf :: RBuilder a -> ElfListXX a -> m (ElfListXX a)
rBuilderToElf RBuilderHeader{} l =
return $ ElfListCons ElfHeader
{ ehData = hData
, ehOSABI = hOSABI
, ehABIVersion = hABIVersion
, ehType = hType
, ehMachine = hMachine
, ehEntry = hEntry
, ehFlags = hFlags
} l
rBuilderToElf RBuilderSectionTable{} l =
return $ ElfListCons ElfSectionTable l
rBuilderToElf RBuilderSegmentTable{} l =
return $ ElfListCons ElfSegmentTable l
rBuilderToElf RBuilderSection{ rbsHeader = s@SectionXX{..}, ..} l =
return $ ElfListCons ElfSection
{ esName = rbsName
, esType = sType
, esFlags = fromIntegral sFlags
, esAddr = sAddr
, esAddrAlign = sAddrAlign
, esEntSize = sEntSize
, esN = rbsN
, esInfo = sInfo
, esLink = sLink
, esData =
if rbsN == hShStrNdx
then ElfSectionDataStringTable
else if sType == SHT_NOBITS
then ElfSectionDataNoBits sSize
else ElfSectionData $ getSectionData bs s
} l
rBuilderToElf RBuilderSegment{ rbpHeader = SegmentXX{..}, ..} l = do
d <- foldrM rBuilderToElf ElfListNull rbpData
addMemSize <- if pMemSize /= 0 && pFileSize /= 0 && pMemSize < pFileSize
then $chainedError "memSize < fileSize"
else return (pMemSize - pFileSize)
return $ ElfListCons ElfSegment
{ epType = pType
, epFlags = pFlags
, epVirtAddr = pVirtAddr
, epPhysAddr = pPhysAddr
, epAddMemSize = addMemSize
, epAlign = pAlign
, epData = d
} l
rBuilderToElf RBuilderRawData{ rbrdInterval = I o s } l =
return $ ElfListCons (ElfRawData $ cut bs (fromIntegral o) (fromIntegral s)) l
rBuilderToElf RBuilderRawAlign{..} l =
return $ ElfListCons (ElfRawAlign rbraOffset rbraAlign) l
el <- foldrM rBuilderToElf ElfListNull rbs -- mapM rBuilderToElf rbs
return $ Elf singElfClass el
-- | Parse ELF file
parseElf :: MonadCatch m => BSL.ByteString -> m Elf
parseElf bs = do
Headers classS hdr ss ps <- parseHeaders bs
withSingElfClassI classS parseElf' hdr ss ps bs
-------------------------------------------------------------------------------
--
-------------------------------------------------------------------------------
wbStateInit :: forall a . SingElfClassI a => WBuilderState a
wbStateInit = WBuilderState
{ _wbsSections = []
, _wbsSegmentsReversed = []
, _wbsDataReversed = []
, _wbsOffset = 0
, _wbsPhOff = 0
, _wbsShOff = 0
, _wbsShStrNdx = 0
, _wbsNameIndexes = []
}
zeroSection :: forall a . SingElfClassI a => SectionXX a
zeroSection = SectionXX 0 0 0 0 0 0 0 0 0 0
neighbours :: [a] -> (a -> a -> b) -> [b]
neighbours [] _ = []
neighbours x@(_:xtail) f = fmap (uncurry f) $ L.zip x xtail
-- make string table and indexes for it from a list of strings
mkStringTable :: [String] -> (BSL.ByteString, [Int64])
mkStringTable sectionNames = (stringTable, os)
where
-- names:
-- i for indexes of the section entry in section table
-- n for section name string
-- o for offset of the string in the string table
-- in, io -- for pairs
-- ins, ios -- for lists of pairs
-- etc
(ins0, ins) = L.break ((/= "") . snd) $ L.sortOn (L.length . snd) $ L.zip [(1 :: Word32) .. ] sectionNames
ios0 = fmap f' ins0
where
f' (i, _) = (i, 0)
(stringTable, ios, _) = f (BSL.singleton 0, [], L.reverse ins)
os = fmap snd $ L.sortOn fst $ ios0 ++ ios
-- create string table. If one name is a suffix of another,
-- allocate only the longest name in string table
f x@(_, _, []) = x
f (st, iosf, (i, n) : insf) = f (st', iosf'', insf')
where
st' = st <> BSL8.pack n <> BSL.singleton 0
o = BSL.length st
iosf'' = (i, o) : iosf' ++ iosf
(iosf', insf') = ff insf
-- look if there exists a name that is a suffix for the currently allocated name
-- in the list of unallocated indexed section names
ff = L.foldr fff ([], [])
where
fff (i', n') (iosff, insff) = if n' `L.isSuffixOf` n
then
let
o' = o + fromIntegral (L.length n - L.length n')
in
((i', o') : iosff, insff)
else (iosff, (i', n') : insff)
serializeElf' :: forall a m . (SingElfClassI a, MonadCatch m) => ElfListXX a -> m BSL.ByteString
serializeElf' elfs = do
-- FIXME: it's better to match constructor here, but there is a bug that prevents to conclude that
-- the match is irrefutable:
-- https://stackoverflow.com/questions/72803815/phantom-type-makes-pattern-matching-irrefutable-but-that-seemingly-does-not-wor
-- https://gitlab.haskell.org/ghc/ghc/-/issues/15681#note_165436
-- But if I use lazy pattern match, then some other bug comes up that prevents type inference
-- on GHC 9.0.2
header' <- $addContext' $ elfFindHeader elfs
let
elfClass = fromSingElfClass $ singElfClass @a
sectionN :: Num b => b
sectionN = getSum $ foldMapElfList f elfs
where
f ElfSection{} = Sum 1
f _ = Sum 0
sectionNames :: [String]
sectionNames = foldMapElfList f elfs
where
f :: ElfXX t a -> [String]
f ElfSection{..} = [ esName ]
f _ = []
(stringTable, nameIndexes) = mkStringTable sectionNames
segmentN :: Num b => b
segmentN = getSum $ foldMapElfList f elfs
where
f ElfSegment{} = Sum 1
f _ = Sum 0
sectionTable :: Bool
sectionTable = getAny $ foldMapElfList f elfs
where
f ElfSectionTable = Any True
f _ = Any False
align :: (MonadThrow n, MonadState (WBuilderState a) n) => WordXX a -> WordXX a -> n ()
align _ 0 = return ()
align _ 1 = return ()
align t m | m .&. (m - 1) /= 0 = $chainedError $ "align module is not power of two " ++ show m
| otherwise = do
offset <- use wbsOffset
wbsOffset .= nextOffset t m offset
offset' <- use wbsOffset
wbsDataReversed %= (WBuilderDataByteStream (BSL.replicate (fromIntegral $ offset' - offset) 0) :)
alignWord :: (MonadThrow n, MonadState (WBuilderState a) n) => n ()
alignWord = align 0 $ wordSize $ fromSingElfClass $ singElfClass @a
dataIsEmpty :: ElfSectionData c -> Bool
dataIsEmpty (ElfSectionData bs) = BSL.null bs
dataIsEmpty ElfSectionDataStringTable = BSL.null stringTable
dataIsEmpty (ElfSectionDataNoBits _) = True
lastSection :: ElfListXX a -> (forall t' . (ElfXX t' a -> b)) -> b -> b
lastSection ElfListNull _ b = b
lastSection (ElfListCons v ElfListNull) f _ = f v
lastSection (ElfListCons _ l) f b = lastSection l f b
lastSectionIsEmpty :: ElfListXX a -> Bool
lastSectionIsEmpty l = lastSection l f False
where
f ElfSection { .. } = dataIsEmpty esData
f _ = False
elf2WBuilder :: (MonadThrow n, MonadState (WBuilderState a) n) => ElfXX t a -> n ()
elf2WBuilder ElfHeader{} = do
-- FIXME: add push monad
wbsDataReversed %= (WBuilderDataHeader :)
wbsOffset += headerSize elfClass
elf2WBuilder ElfSectionTable = do
alignWord
use wbsOffset >>= assign wbsShOff
wbsDataReversed %= (WBuilderDataSectionTable :)
wbsOffset += (sectionN + 1) * sectionTableEntrySize elfClass
elf2WBuilder ElfSegmentTable = do
alignWord
use wbsOffset >>= assign wbsPhOff
wbsDataReversed %= (WBuilderDataSegmentTable :)
wbsOffset += segmentN * segmentTableEntrySize elfClass
elf2WBuilder ElfSection{esFlags = ElfSectionFlag f, ..} = do
when (f .&. fromIntegral (complement (maxBound @(WordXX a))) /= 0) do
$chainedError $ "section flags at section " ++ show esN ++ "don't fit"
-- I don't see any sense in aligning NOBITS section data
-- still gcc does it for .o files
when (esType /= SHT_NOBITS || (ehType header') == ET_REL) do
align 0 esAddrAlign
(n, ns) <- uses wbsNameIndexes \case
n' : ns' -> (n', ns')
_ -> error "internal error: different number of sections in two iterations"
shStrNdx' <- use wbsShStrNdx
let
(d, shStrNdx, sz) = case esData of
ElfSectionData { .. } -> (esdData, shStrNdx', fromIntegral $ BSL.length esdData)
ElfSectionDataStringTable -> (stringTable, esN, fromIntegral $ BSL.length stringTable)
ElfSectionDataNoBits { .. } -> (BSL.empty, shStrNdx', esdSize)
sName = fromIntegral n -- Word32
sType = esType -- ElfSectionType
sFlags = fromIntegral f
sAddr = esAddr -- WXX c
sSize = sz -- WXX c
sLink = esLink -- Word32
sInfo = esInfo -- Word32
sAddrAlign = esAddrAlign -- WXX c
sEntSize = esEntSize -- WXX c
sOffset <- use wbsOffset -- WXX c
wbsSections %= ((esN, SectionXX { .. }) :)
wbsDataReversed %= (WBuilderDataByteStream d :)
wbsOffset += fromIntegral (BSL.length d)
wbsShStrNdx .= shStrNdx
wbsNameIndexes .= ns
elf2WBuilder ElfSegment { .. } = do
align epVirtAddr epAlign
offset <- use wbsOffset
void $ mapMElfList elf2WBuilder epData
offset' <- use wbsOffset
let
-- allocate one more byte in the end of segment if there exists an empty section
-- at the end so that that empty section will go to the current segment
add1 = lastSectionIsEmpty epData && offset /= offset'
pType = epType
pFlags = epFlags
pOffset = offset
pVirtAddr = epVirtAddr
pPhysAddr = epPhysAddr
pFileSize = offset' - offset + if add1 then 1 else 0
pMemSize = pFileSize + epAddMemSize
pAlign = epAlign
wbsSegmentsReversed %= (SegmentXX { .. } :)
when add1 do
wbsDataReversed %= (WBuilderDataByteStream (BSL.singleton 0) :)
wbsOffset += 1
elf2WBuilder ElfRawData { .. } = do
wbsDataReversed %= (WBuilderDataByteStream edData :)
wbsOffset += fromIntegral (BSL.length edData)
elf2WBuilder ElfRawAlign { .. } = align eaOffset eaAlign
fixSections :: [(ElfSectionIndex, SectionXX a)] -> m [SectionXX a]
fixSections ss = do
when (L.length ss /= sectionN) (error "internal error: L.length ss /= sectionN")
let
f (ln, _) (rn, _) = ln `compare` rn
sorted = L.sortBy f ss
next (ln, _) (rn, _) = ln + 1 == rn
checkNeibours = and $ neighbours sorted next
unless checkNeibours ($chainedError "sections are not consistent")
return $ fmap snd sorted
wbState2ByteString :: WBuilderState a -> m BSL.ByteString
wbState2ByteString WBuilderState{..} = do
sections <- fixSections _wbsSections
let
f WBuilderDataHeader =
case header' of
ElfHeader{..} ->
let
hData = ehData
hOSABI = ehOSABI
hABIVersion = ehABIVersion
hType = ehType
hMachine = ehMachine
hEntry = ehEntry
hPhOff = _wbsPhOff
hShOff = _wbsShOff
hFlags = ehFlags
hPhEntSize = segmentTableEntrySize elfClass
hPhNum = segmentN :: Word16
hShEntSize = sectionTableEntrySize elfClass
hShNum = (if sectionTable then sectionN + 1 else 0) :: Word16
hShStrNdx = _wbsShStrNdx
h :: H.Header
h = H.Header (singElfClass @a) HeaderXX{..}
in
encode h
f WBuilderDataByteStream {..} = wbdData
f WBuilderDataSectionTable =
serializeBList (ehData header') $ zeroSection : sections
f WBuilderDataSegmentTable =
serializeBList (ehData header') $ L.reverse _wbsSegmentsReversed
return $ foldMap f $ L.reverse _wbsDataReversed
execStateT (mapMElfList elf2WBuilder elfs) wbStateInit{ _wbsNameIndexes = nameIndexes } >>= wbState2ByteString
-- | Serialze ELF file
serializeElf :: MonadCatch m => Elf -> m BSL.ByteString
serializeElf (Elf classS ls) = withSingElfClassI classS serializeElf' ls
-------------------------------------------------------------------------------
--
-------------------------------------------------------------------------------
-- FIXME: move this to a separate file
-- | Parsed ELF symbol table entry. NB: This is work in progress
data ElfSymbolXX c =
ElfSymbolXX
{ steName :: String -- ^ Symbol name (NB: String, not string index)
, steBind :: ElfSymbolBinding -- ^ Symbol binding attributes
, steType :: ElfSymbolType -- ^ Symbol Type
, steShNdx :: ElfSectionIndex -- ^ Section table index
, steValue :: WordXX c -- ^ Symbol value
, steSize :: WordXX c -- ^ Size of object
}
getStringFromData :: BSL.ByteString -> Word32 -> String
getStringFromData stringTable offset = BSL8.unpack $ BSL.takeWhile (/= 0) $ BSL.drop (fromIntegral offset) stringTable
mkElfSymbolTableEntry :: SingElfClassI a => BSL.ByteString -> SymbolXX a -> ElfSymbolXX a
mkElfSymbolTableEntry stringTable SymbolXX{..} =
let
steName = getStringFromData stringTable stName
steBind = ElfSymbolBinding $ stInfo `shiftR` 4
steType = ElfSymbolType $ stInfo .&. 0x0f
steShNdx = stShNdx
steValue = stValue
steSize = stSize
in
ElfSymbolXX{..}
-- | Parse symbol table
parseSymbolTable :: (MonadThrow m, SingElfClassI a)
=> ElfData -- ^ Endianness of the ELF file
-> ElfXX 'Section a -- ^ Parsed section such that @`sectionIsSymbolTable` . `sType`@ is true.
-> ElfListXX a -- ^ Structured ELF data
-> m [ElfSymbolXX a] -- ^ Symbol table
parseSymbolTable d symbolTableSection@(ElfSection { .. }) elfs = do
symbolTable <- case symbolTableSection of
ElfSection{ esData = ElfSectionData st } -> return st
_ -> $chainedError "wrong symbol table section data"
section <- elfFindSection elfs esLink
stringTable <- case section of
ElfSection{ esData = ElfSectionData st } -> return st
_ -> $chainedError "wrong string table section data"
st <- parseBList d symbolTable
return (mkElfSymbolTableEntry stringTable <$> st)
mkSymbolTableEntry :: Word32 -> ElfSymbolXX a -> SymbolXX a
mkSymbolTableEntry nameIndex ElfSymbolXX{..} =
let
ElfSymbolBinding b = steBind
ElfSymbolType t = steType
stName = nameIndex
stInfo = b `shift` 4 .|. t
stOther = 0 :: Word8
stShNdx = steShNdx
stValue = steValue
stSize = steSize
in
SymbolXX{..}
-- | Serialize symbol table
serializeSymbolTable :: (MonadThrow m, SingElfClassI a)
=> ElfData -- ^ Endianness of the ELF file
-> [ElfSymbolXX a] -- ^ Symbol table
-> m (BSL.ByteString, BSL.ByteString) -- ^ Pair of symbol table section data and string table section data
serializeSymbolTable d ss = do
let
(stringTable, stringIndexes) = mkStringTable $ fmap steName ss
ssWithNameIndexes = L.zip ss stringIndexes
f :: (ElfSymbolXX a, Int64) -> SymbolXX a
f (s, n) = mkSymbolTableEntry (fromIntegral n) s
symbolTable = serializeBList d $ fmap f ssWithNameIndexes
return (symbolTable, stringTable)