elf 0.28 → 0.29
raw patch · 14 files changed
+301/−104 lines, 14 filesdep +containersdep +elfdep +hspecdep ~basedep ~binarysetup-changedbinary-addedPVP ok
version bump matches the API change (PVP)
Dependencies added: containers, elf, hspec
Dependency ranges changed: base, binary
API changes (from Hackage documentation)
Files
- LICENSE +26/−26
- Setup.hs +2/−6
- elf.cabal +40/−18
- src/Data/Elf.hs +69/−33
- testdata/HOWTO +17/−0
- testdata/bloated binary
- testdata/bloated.cpp +7/−0
- testdata/empty +0/−0
- testdata/tiny binary
- testdata/tiny.asm +8/−0
- tests/Data/ElfSpec.hs +131/−0
- tests/Spec.hs +1/−0
- tests/Test.hs +0/−21
- tests/empty.elf +0/−0
LICENSE view
@@ -1,26 +1,26 @@-Copyright (c) Erik Charlebois. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The names of the author may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. +Copyright (c) Erik Charlebois.+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. The names of the author may not be used to endorse or promote+ products derived from this software without specific prior written+ permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
Setup.hs view
@@ -1,6 +1,2 @@-import Distribution.Simple -import System.Cmd(system) - -main = defaultMainWithHooks $ simpleUserHooks { runTests = runElfTests } - -runElfTests a b pd lb = system "runhaskell -i./src ./tests/Test.hs" >> return () +import Distribution.Simple+main = defaultMain
elf.cabal view
@@ -1,24 +1,46 @@-Name: elf-Version: 0.28-License: BSD3-License-file: LICENSE-Category: Data-Author: Erik Charlebois-Copyright: Erik Charlebois-Maintainer: Baojun Wang <wangbj@gmail.com>-Stability: stable-Cabal-Version: >= 1.6-Homepage: https://github.com/wangbj/elf-Build-Type: Custom-Synopsis: Parser for ELF object format.-Description: Parser for ELF object format.-Data-Files: tests/empty.elf tests/Test.hs+Name: elf+Version: 0.29+License: BSD3+License-file: LICENSE+Category: Data+Author: Erik Charlebois+Copyright: Erik Charlebois+Maintainer: Baojun Wang <wangbj@gmail.com>+Stability: stable+Cabal-Version: >= 1.10+Homepage: https://github.com/wangbj/elf+Build-Type: Simple+Synopsis: An Elf parser+Description: Parser for ELF object format.+Extra-Source-Files: testdata/tiny testdata/bloated testdata/HOWTO+ testdata/bloated.cpp testdata/tiny.asm+ testdata/empty +tested-with:+ GHC == 8.4.3+ , GHC == 8.2.2+ , GHC == 8.0.2+ source-repository head type: git location: https://github.com/wangbj/elf.git library- build-depends: base >= 2 && < 5, bytestring, binary- hs-source-dirs: src- exposed-modules: Data.Elf+ build-depends: base >= 2 && < 5+ , bytestring+ , binary+ hs-source-dirs: src+ exposed-modules: Data.Elf+ default-language: Haskell2010++test-suite tests+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Spec.hs+ other-modules: Data.ElfSpec+ build-depends: base+ , bytestring+ , hspec >= 2.4 && < 3+ , containers >= 0.5.9.2+ , elf+ default-language: Haskell2010
src/Data/Elf.hs view
@@ -1,4 +1,4 @@--- | Data.Elf is a module for parsing a ByteString of an ELF file into an Elf record.+-- | Data.Elf is a module for parsing a ByteString of an ELF file into an Elf record. module Data.Elf ( parseElf , parseSymbolTables , findSymbolDefinition@@ -23,12 +23,11 @@ import Data.Binary.Get as G import Data.Bits import Data.Maybe-import Data.Word-import Numeric import Control.Monad-import qualified Data.ByteString as B-import qualified Data.ByteString.Internal as B-import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Internal as L data Elf = Elf { elfClass :: ElfClass -- ^ Identifies the class of the object file.@@ -56,19 +55,22 @@ , elfSectionData :: B.ByteString -- ^ The raw data for the section. } deriving (Eq, Show) +elfMagic :: [Word8]+elfMagic = [0x7f, 0x45, 0x4c, 0x46] -- "\DELELF"++getElfMagic :: Get [Word8] getElfMagic = do- ei_magic <- liftM (map B.w2c) $ sequence [getWord8, getWord8, getWord8, getWord8]- if ei_magic /= "\DELELF" then- fail "Invalid magic number for ELF"- else- return ei_magic+ ei_magic <- replicateM 4 getWord8+ if ei_magic /= elfMagic+ then fail "Invalid magic number for ELF"+ else return ei_magic +getElfVersion :: Get Word8 getElfVersion = do ei_version <- getWord8- if ei_version /= 1 then- fail "Invalid version number for ELF"- else- return ei_version+ if ei_version /= 1+ then fail "Invalid version number for ELF"+ else return ei_version data ElfSectionType = SHT_NULL -- ^ Identifies an empty section header.@@ -85,6 +87,8 @@ | SHT_DYNSYM -- ^ Contains a dynamic loader symbol table | SHT_EXT Word32 -- ^ Processor- or environment-specific type deriving (Eq, Show)++getElfSectionType :: ElfReader -> Get ElfSectionType getElfSectionType er = liftM getElfSectionType_ $ getWord32 er where getElfSectionType_ 0 = SHT_NULL getElfSectionType_ 1 = SHT_PROGBITS@@ -106,19 +110,26 @@ | SHF_EXECINSTR -- ^ Section contains executable instructions | SHF_EXT Int -- ^ Processor- or environment-specific flag deriving (Eq, Show)++getElfSectionFlags :: Bits a => Int -> a -> [ElfSectionFlags] getElfSectionFlags 0 word = [] getElfSectionFlags 1 word | testBit word 0 = SHF_WRITE : getElfSectionFlags 0 word getElfSectionFlags 2 word | testBit word 1 = SHF_ALLOC : getElfSectionFlags 1 word getElfSectionFlags 3 word | testBit word 2 = SHF_EXECINSTR : getElfSectionFlags 2 word getElfSectionFlags n word | testBit word (n-1) = SHF_EXT (n-1) : getElfSectionFlags (n-1) word getElfSectionFlags n word = getElfSectionFlags (n-1) word-getElfSectionFlags32 = liftM (getElfSectionFlags 32) . getWord32 ++getElfSectionFlags32 :: ElfReader -> Get [ElfSectionFlags]+getElfSectionFlags64 :: ElfReader -> Get [ElfSectionFlags]+getElfSectionFlags32 = liftM (getElfSectionFlags 32) . getWord32 getElfSectionFlags64 = liftM (getElfSectionFlags 64) . getWord64- + data ElfClass = ELFCLASS32 -- ^ 32-bit ELF format | ELFCLASS64 -- ^ 64-bit ELF format deriving (Eq, Show)++getElfClass :: Get ElfClass getElfClass = getWord8 >>= getElfClass_ where getElfClass_ 1 = return ELFCLASS32 getElfClass_ 2 = return ELFCLASS64@@ -128,6 +139,8 @@ = ELFDATA2LSB -- ^ Little-endian ELF format | ELFDATA2MSB -- ^ Big-endian ELF format deriving (Eq, Show)++getElfData :: Get ElfData getElfData = getWord8 >>= getElfData_ where getElfData_ 1 = return ELFDATA2LSB getElfData_ 2 = return ELFDATA2MSB@@ -152,6 +165,8 @@ | ELFOSABI_STANDALONE -- ^ Standalone (embedded) application | ELFOSABI_EXT Word8 -- ^ Other deriving (Eq, Show)++getElfOsabi :: Get ElfOSABI getElfOsabi = liftM getElfOsabi_ getWord8 where getElfOsabi_ 0 = ELFOSABI_SYSV getElfOsabi_ 1 = ELFOSABI_HPUX@@ -179,6 +194,8 @@ | ET_CORE -- ^ Core dump object file | ET_EXT Word16 -- ^ Other deriving (Eq, Show)++getElfType :: ElfReader -> Get ElfType getElfType = liftM getElfType_ . getWord16 where getElfType_ 0 = ET_NONE getElfType_ 1 = ET_REL@@ -283,6 +300,8 @@ | EM_UNICORE -- ^ Microprocessor series from PKU-Unity Ltd. and MPRC of Peking University | EM_EXT Word16 -- ^ Other deriving (Eq, Show)++getElfMachine :: ElfReader -> Get ElfMachine getElfMachine = liftM getElfMachine_ . getWord16 where getElfMachine_ 0 = EM_NONE getElfMachine_ 1 = EM_M32@@ -516,11 +535,13 @@ , getWord32 :: Get Word32 , getWord64 :: Get Word64 }++elfReader :: ElfData -> ElfReader elfReader ELFDATA2LSB = ElfReader { getWord16 = getWord16le, getWord32 = getWord32le, getWord64 = getWord64le } elfReader ELFDATA2MSB = ElfReader { getWord16 = getWord16be, getWord32 = getWord32be, getWord64 = getWord64be } divide :: B.ByteString -> Int -> Int -> [B.ByteString]-divide bs s 0 = []+divide _ _ 0 = [] divide bs s n = let (x,y) = B.splitAt s bs in x : divide y s (n-1) -- | Parses a ByteString into an Elf record. Parse failures call error. 32-bit ELF objects have their@@ -623,7 +644,7 @@ deriving (Eq,Show) parseElfSegmentFlags :: Word32 -> [ElfSegmentFlag]-parseElfSegmentFlags word = [ cvt bit | bit <- [ 0 .. 31 ], testBit word bit ]+parseElfSegmentFlags word = [ cvt bit_ | bit_ <- [ 0 .. 31 ], testBit word bit_ ] where cvt 0 = PF_X cvt 1 = PF_W cvt 2 = PF_R@@ -656,11 +677,33 @@ -- | Assumes the given section is a symbol table, type SHT_SYMTAB -- (guaranteed by parseSymbolTables). getSymbolTableEntries :: Elf -> ElfSection -> [ElfSymbolTableEntry]-getSymbolTableEntries e s =- let link = elfSectionLink s- strtab = lookup (fromIntegral link) (zip [0..] (elfSections e))- in runGetMany (getSymbolTableEntry e strtab) (L.fromChunks [elfSectionData s])+getSymbolTableEntries e s = go decoder (L.fromChunks [elfSectionData s])+ where+ link = elfSectionLink s+ strtab = lookup (fromIntegral link) (zip [0..] (elfSections e))+ decoder = runGetIncremental (getSymbolTableEntry e strtab)+ go :: Decoder ElfSymbolTableEntry -> L.ByteString -> [ElfSymbolTableEntry]+ go (Done leftover _ entry) input =+ entry : go decoder (L.Chunk leftover input)+ go (Partial k) input =+ go (k . takeHeadChunk $ input) (dropHeadChunk input)+ go (Fail _ _ msg) input = if L.null input+ then []+ else error msg +takeHeadChunk :: L.ByteString -> Maybe B.ByteString+takeHeadChunk lbs =+ case lbs of+ (L.Chunk bs _) -> Just bs+ _ -> Nothing++dropHeadChunk :: L.ByteString -> L.ByteString+dropHeadChunk lbs =+ case lbs of+ (L.Chunk _ lbs') -> lbs'+ _ -> L.Empty++ -- | Use the symbol offset and size to extract its definition -- (in the form of a ByteString). -- If the size is zero, or the offset larger than the 'elfSectionData',@@ -668,16 +711,11 @@ findSymbolDefinition :: ElfSymbolTableEntry -> Maybe B.ByteString findSymbolDefinition e = steEnclosingSection e >>= \enclosingSection -> let enclosingData = elfSectionData enclosingSection- start = ( (fromIntegral (steValue e)) - (fromIntegral (elfSectionAddr enclosingSection) ) )+ start = (fromIntegral (steValue e)) - (fromIntegral (elfSectionAddr enclosingSection)) len = fromIntegral (steSize e) def = (B.take len . B.drop start) enclosingData in if B.null def then Nothing else Just def -runGetMany :: Get a -> L.ByteString -> [a]-runGetMany g bs- | L.length bs == 0 = []- | otherwise = let (v,bs',_) = runGetState g bs 0 in v: runGetMany g bs'- symbolTableSections :: Elf -> [ElfSection] symbolTableSections e = filter ((== SHT_SYMTAB) . elfSectionType) (elfSections e) @@ -686,7 +724,7 @@ getSymbolTableEntry e strtlb = if elfClass e == ELFCLASS32 then getSymbolTableEntry32 else getSymbolTableEntry64 where- strs = fromMaybe B.empty (fmap elfSectionData strtlb)+ strs = maybe B.empty elfSectionData strtlb er = elfReader (elfData e) getSymbolTableEntry32 = do nameIdx <- liftM fromIntegral (getWord32 er)@@ -712,7 +750,7 @@ return $ EST (nameIdx,name) sec typ bind other sTlbIdx symVal size sectionByIndex :: Elf -> ElfSectionIndex -> Maybe ElfSection-sectionByIndex e (SHNIndex i) = lookup i . zip [0..] $ (elfSections e)+sectionByIndex e (SHNIndex i) = lookup i . zip [0..] $ elfSections e sectionByIndex _ _ = Nothing infoToTypeAndBind :: Word8 -> (ElfSymbolType,ElfSymbolBinding)@@ -831,5 +869,3 @@ stringByIndex n strtab = let str = (B.takeWhile (/=0) . B.drop (fromIntegral n)) strtab in if B.length str == 0 then Nothing else Just str--
+ testdata/HOWTO view
@@ -0,0 +1,17 @@+'tiny' was generated this way on a 64bit linux system:++ $ nasm -f elf64 tiny.asm+ $ gcc -o tiny tiny.o -nostartfiles -nostdlib -nodefaultlibs+ $ strip -s tiny+ $ strip --remove-section=.note.gnu.build-id tiny++Thank you whoever wrote this tutorial: <http://timelessname.com/elfbin/>.++'bloated' was generated this way on a 64bit linux system:++ $ clang++ -m32 -o bloated bloated.cpp++This HOWTO is provided for documentation purposes, elf should not be+regenerated/changed because tests assert their content. Their content was read+by the readelf utility command.+
+ testdata/bloated view
binary file changed (absent → 7880 bytes)
+ testdata/bloated.cpp view
@@ -0,0 +1,7 @@+#include <iostream>++int main(int argc, char *argv[])+{+ std::cout << "Hello world!" << std::endl;+ return 0;+}
+ testdata/empty view
+ testdata/tiny view
binary file changed (absent → 448 bytes)
+ testdata/tiny.asm view
@@ -0,0 +1,8 @@+SECTION .text++global _start++_start:+ mov ebx,0+ mov eax,1+ int 0x80
+ tests/Data/ElfSpec.hs view
@@ -0,0 +1,131 @@+module Data.ElfSpec (spec) where++import Test.Hspec++import Control.Exception (evaluate)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as C+import Data.Either+import Data.Foldable (find)+import qualified Data.Map as Map+import Data.Maybe+import System.IO++import Data.Elf+++getBinaryFileContents :: FilePath -> IO BS.ByteString+getBinaryFileContents fname = withBinaryFile fname ReadMode BS.hGetContents+++spec :: Spec+spec = do+ emptyContents <- runIO $ getBinaryFileContents "./testdata/empty"+ tinyContents <- runIO $ getBinaryFileContents "./testdata/tiny"+ bloatedContents <- runIO $ getBinaryFileContents "./testdata/bloated"++ let tinyElf = parseElf tinyContents+ bloatedElf = parseElf bloatedContents++ describe "parseElf" $ do+ -- TODO: That was the original only test in this package. This test+ -- should be removed, and future versions of this library should return+ -- an 'Either ParseError Elf'.+ it "does not accept an empty elf" $+ evaluate (parseElf emptyContents) `shouldThrow` anyException++ context "Headers parsing" $ do++ it "parses the version" $+ elfVersion tinyElf `shouldBe` 1++ it "parses the architecture" $ do+ elfClass tinyElf `shouldBe` ELFCLASS64+ elfClass bloatedElf `shouldBe` ELFCLASS32++ it "parses the endianness" $ do+ elfData tinyElf `shouldBe` ELFDATA2LSB++ it "parses the OS ABI" $+ elfOSABI tinyElf `shouldBe` ELFOSABI_SYSV++ it "parses the type" $+ elfType bloatedElf `shouldBe` ET_EXEC++ it "parses the machine type" $ do+ elfMachine tinyElf `shouldBe` EM_X86_64+ elfMachine bloatedElf `shouldBe` EM_386++ it "parses the entry point" $ do+ elfEntry tinyElf `shouldBe` 0x4000e0+ elfEntry bloatedElf `shouldBe` 0x8048610++ context "Segment parsing" $ do+ let tinySegments = elfSegments tinyElf+ bloatedSegments = elfSegments bloatedElf++ it "parses the right amount of segments" $ do+ length tinySegments `shouldBe` 2+ length bloatedSegments `shouldBe` 9++ it "parses segment types" $+ let segmentTypes = map elfSegmentType tinySegments in+ segmentTypes `shouldBe` [PT_LOAD, PT_NOTE]++ it "parses segment flags" $ do+ let segmentFlags = map elfSegmentFlags tinySegments+ segmentFlags !! 0 `shouldMatchList` [PF_R, PF_X]+ segmentFlags !! 1 `shouldMatchList` [PF_R]++ context "Section parsing" $ do+ let tinySections = elfSections tinyElf+ bloatedSections = elfSections bloatedElf++ it "parses the right amount of sections" $ do+ length tinySections `shouldBe` 3+ length bloatedSections `shouldBe` 31++ it "parses the section in the right order" $ do+ map elfSectionName tinySections `shouldBe` [ "", ".text", ".shstrtab" ]++ it "parses the section types" $+ let sectionTypes = map elfSectionType bloatedSections in+ take 5 sectionTypes `shouldBe` [ SHT_NULL, SHT_PROGBITS, SHT_NOTE+ , SHT_NOTE, SHT_EXT 1879048182]++ it "parses the data" $+ let comment = find (\sec -> elfSectionName sec == ".comment") bloatedSections+ expected = C.pack . concat $ [ "GCC: (GNU) 6.3.1 20161221 (Red Hat 6.3.1-1)\NUL"+ , "clang version 3.8.1 (tags/RELEASE_381/final)\NUL"+ ]+ in+ fmap elfSectionData comment `shouldBe` Just expected++ describe "findSymbolDefinition" $ do+ let tinySymbols = parseSymbolTables tinyElf+ bloatedSymbols = parseSymbolTables bloatedElf++ it "parses stripped symbol" $+ -- This binary was stripped+ concat tinySymbols `shouldSatisfy` all (isNothing . snd . steName)++ let namedBloatedSymbols =+ let go sym = fmap (\ name -> (name, sym)) $ snd (steName sym)+ in Map.fromList $ catMaybes $ map go $ concat bloatedSymbols++ member k = Map.member (C.pack k)+ (!?) m k = m Map.!? (C.pack k)++ it "parses symbol symbol names" $ do+ namedBloatedSymbols `shouldSatisfy` member "_init"+ namedBloatedSymbols `shouldSatisfy` member "main"++ let initSymbol = namedBloatedSymbols !? "_init"+ fnameSymbol = namedBloatedSymbols !? "bloated.cpp"++ it "parses symbol address" $+ fmap steValue initSymbol `shouldBe` Just 0x0804850c++ it "parses symbol type" $ do+ fmap steType initSymbol `shouldBe` Just STTFunc+ fmap steType fnameSymbol `shouldBe` Just STTFile
+ tests/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
− tests/Test.hs
@@ -1,21 +0,0 @@-module Main where - -import Data.Elf -import System.IO -import Test.HUnit -import Control.Monad -import qualified Control.Exception as E -import qualified Data.ByteString as B - -testEmptyElf = withBinaryFile "./tests/empty.elf" ReadMode $ \h -> do - fil <- B.hGetContents h - res <- E.try (E.evaluate (parseElf fil)) :: IO (Either E.SomeException Elf) - case res of - Left e -> return () - Right a -> assertFailure "Empty ELF did not cause an exception." - -tests = TestList - [ TestLabel "Empty ELF" $ TestCase testEmptyElf - ] - -main = runTestTT tests
− tests/empty.elf