diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -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
diff --git a/elf.cabal b/elf.cabal
--- a/elf.cabal
+++ b/elf.cabal
@@ -1,25 +1,47 @@
-Name:          elf
-Version:       0.27
-License:       BSD3
-License-file:  LICENSE
-Category:      Data
-Author:        Erik Charlebois
-Copyright:     Erik Charlebois
-Maintainer:    Erik Charlebois <erikcharlebois@gmail.com>
-Stability:     stable
-Cabal-Version: >= 1.6
-Homepage:      http://github.com/erikcharlebois/elf
-Build-Depends: base
-Build-Type:    Custom
-Synopsis:      Parser for ELF object format.
-Description:   Parser for ELF object format.
-Data-Files:    tests/empty.elf tests/Test.hs
-
-source-repository head
-  type:     git
-  location: http://github.com/erikcharlebois/elf
-
-library
-    build-depends:   base >= 2 && < 5, bytestring, binary
-    hs-source-dirs:  src
-    exposed-modules: Data.Elf
+Name:               elf
+Version:            0.31
+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 testdata/vdso
+
+tested-with:
+    GHC == 8.6.2
+  , GHC == 8.4.4
+  , 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 >= 0.6
+  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
diff --git a/src/Data/Elf.hs b/src/Data/Elf.hs
--- a/src/Data/Elf.hs
+++ b/src/Data/Elf.hs
@@ -1,34 +1,45 @@
--- | Data.Elf  is a module for parsing a ByteString of an ELF file into an Elf record.
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Data.Elf is a module for parsing a ByteString of an ELF file into an Elf record.
 module Data.Elf ( parseElf
                 , parseSymbolTables
+                , parseRelocations
                 , findSymbolDefinition
+                  -- * Top-level header
                 , Elf(..)
+                , ElfClass(..)
+                , ElfData(..)
+                , ElfOSABI(..)
+                , ElfType(..)
+                , ElfMachine(..)
+                  -- * Sections
                 , ElfSection(..)
                 , ElfSectionType(..)
                 , ElfSectionFlags(..)
+                  -- * Segments
                 , ElfSegment(..)
                 , ElfSegmentType(..)
                 , ElfSegmentFlag(..)
-                , ElfClass(..)
-                , ElfData(..)
-                , ElfOSABI(..)
-                , ElfType(..)
-                , ElfMachine(..)
+                  -- * Symbols
                 , ElfSymbolTableEntry(..)
                 , ElfSymbolType(..)
                 , ElfSymbolBinding(..)
-                , ElfSectionIndex(..)) where
+                , ElfSectionIndex(..)
+                  -- * Relocations
+                , ElfRel(..)
+                , ElfRelocationSection(..)
+                ) where
 
 import Data.Binary
 import Data.Binary.Get as G
 import Data.Bits
 import Data.Maybe
-import Data.Word
-import Numeric
+import Data.Int
 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 +67,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 +99,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 +122,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 +151,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 +177,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 +206,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 +312,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 +547,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 +656,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
@@ -653,40 +686,63 @@
     let secs = symbolTableSections e
     in map (getSymbolTableEntries e) secs
 
--- | Assumes the given section is a symbol table, type SHT_SYMTAB
+-- | Assumes the given section is a symbol table, type SHT_SYMTAB, or SHT_DYNSYM
 -- (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])
+    decodeMany (getSymbolTableEntry e strtab) (L.fromStrict (elfSectionData s))
+  where
+    link   = elfSectionLink s
+    strtab = lookup (fromIntegral link) (zip [0..] (elfSections e))
 
+decodeMany :: forall a. Get a -> L.ByteString -> [a]
+decodeMany get = go decoder
+  where
+    decoder = runGetIncremental get
+
+    go :: Decoder a -> L.ByteString -> [a]
+    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',
 -- then 'Nothing' is returned.
 findSymbolDefinition :: ElfSymbolTableEntry -> Maybe B.ByteString
-findSymbolDefinition e =
-    let enclosingData = fmap elfSectionData (steEnclosingSection e)
-        start = fromIntegral (steValue e)
+findSymbolDefinition e = steEnclosingSection e >>= \enclosingSection ->
+    let enclosingData = elfSectionData enclosingSection
+        start = (fromIntegral (steValue e)) - (fromIntegral (elfSectionAddr enclosingSection))
         len = fromIntegral (steSize e)
-        def = fmap (B.take len . B.drop start) enclosingData
-    in if def == Just B.empty then Nothing else 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'
+        def = (B.take len . B.drop start) enclosingData
+    in if B.null def then Nothing else Just def
 
 symbolTableSections :: Elf -> [ElfSection]
-symbolTableSections e = filter ((== SHT_SYMTAB) . elfSectionType) (elfSections e)
+symbolTableSections e = filter ((`elem` [SHT_SYMTAB, SHT_DYNSYM]) . elfSectionType) (elfSections e)
 
 -- | Gets a single entry from the symbol table, use with runGetMany.
 getSymbolTableEntry :: Elf -> Maybe ElfSection -> Get ElfSymbolTableEntry
 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 +768,7 @@
     return $ EST (nameIdx,name) sec typ bind other sTlbIdx symVal size
 
 sectionByIndex :: Elf -> ElfSectionIndex -> Maybe ElfSection
-sectionByIndex e (SHNIndex i) = lookup i . zip [1..] $ (elfSections e)
+sectionByIndex e (SHNIndex i) = lookup i . zip [0..] $ elfSections e
 sectionByIndex _ _ = Nothing
 
 infoToTypeAndBind :: Word8 -> (ElfSymbolType,ElfSymbolBinding)
@@ -832,4 +888,60 @@
     let str = (B.takeWhile (/=0) . B.drop (fromIntegral n)) strtab
     in if B.length str == 0 then Nothing else Just str
 
+data ElfRel = ElfRel
+    { elfRelOffset    :: Word64
+    , elfRelSymbol    :: Word64
+    , elfRelType      :: Word8
+    , elfRelSymAddend :: Maybe Int64
+    } deriving (Eq, Show)
 
+getElfRel :: ElfClass
+          -> ElfReader
+          -> Bool   -- ^ explicit addend?
+          -> Get ElfRel
+getElfRel ELFCLASS64 er explicit = do
+    offset <- getWord64 er
+    info <- getWord64 er
+    addend <- if explicit then Just <$> getWord64 er else pure Nothing
+    return ElfRel { elfRelOffset    = offset
+                  , elfRelSymbol    = fromIntegral (info `shiftR` 32)
+                  , elfRelType      = fromIntegral info
+                  , elfRelSymAddend = fromIntegral <$> addend
+                  }
+getElfRel ELFCLASS32 er explicit = do
+    offset <- getWord32 er
+    info <- getWord32 er
+    addend <- if explicit then Just <$> getWord32 er else pure Nothing
+    return ElfRel { elfRelOffset    = fromIntegral offset
+                  , elfRelSymbol    = fromIntegral (info `shiftR` 8)
+                  , elfRelType      = fromIntegral info
+                  , elfRelSymAddend = fromIntegral <$> addend
+                  }
+
+getRelocations :: ElfClass -> ElfReader -> ElfSection -> [ElfRel]
+getRelocations elf_class er s =
+    decodeMany (getElfRel elf_class er explicit) (L.fromStrict (elfSectionData s))
+  where
+    explicit = elfSectionType s == SHT_RELA
+
+data ElfRelocationSection = ElfRelocationSection
+    { elfRelSectSymbolTable :: [ElfSymbolTableEntry]
+    , elfRelSectRelocated   :: ElfSection
+    , elfRelSectRelocations :: [ElfRel]
+    } deriving (Eq, Show)
+
+parseRelocations :: Elf -> [ElfRelocationSection]
+parseRelocations elf =
+    [ ElfRelocationSection { elfRelSectSymbolTable = symtab
+                           , elfRelSectRelocated   = relocated
+                           , elfRelSectRelocations = rels
+                           }
+    | s <- elfSections elf
+    , elfSectionType s `elem` [SHT_REL, SHT_RELA]
+    , let symtab = getSymbolTableEntries elf (getSection (elfSectionLink s))
+          relocated = getSection (elfSectionInfo s)
+          rels = getRelocations (elfClass elf) er s
+    ]
+  where
+    getSection i = elfSections elf !! fromIntegral i
+    er = elfReader (elfData elf)
diff --git a/testdata/HOWTO b/testdata/HOWTO
new file mode 100644
--- /dev/null
+++ b/testdata/HOWTO
@@ -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.
+
diff --git a/testdata/bloated b/testdata/bloated
new file mode 100644
Binary files /dev/null and b/testdata/bloated differ
diff --git a/testdata/bloated.cpp b/testdata/bloated.cpp
new file mode 100644
--- /dev/null
+++ b/testdata/bloated.cpp
@@ -0,0 +1,7 @@
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+	std::cout << "Hello world!" << std::endl;
+	return 0;
+}
diff --git a/testdata/empty b/testdata/empty
new file mode 100644
--- /dev/null
+++ b/testdata/empty
diff --git a/testdata/tiny b/testdata/tiny
new file mode 100644
Binary files /dev/null and b/testdata/tiny differ
diff --git a/testdata/tiny.asm b/testdata/tiny.asm
new file mode 100644
--- /dev/null
+++ b/testdata/tiny.asm
@@ -0,0 +1,8 @@
+SECTION .text
+
+global _start
+
+_start:
+    mov ebx,0
+    mov eax,1
+    int 0x80
diff --git a/testdata/vdso b/testdata/vdso
new file mode 100644
Binary files /dev/null and b/testdata/vdso differ
diff --git a/tests/Data/ElfSpec.hs b/tests/Data/ElfSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Data/ElfSpec.hs
@@ -0,0 +1,143 @@
+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"
+    dynsymContents  <- runIO $ getBinaryFileContents "./testdata/vdso"
+
+    let tinyElf = parseElf tinyContents
+        bloatedElf = parseElf bloatedContents
+        dynsymElf  = parseElf dynsymContents
+
+    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
+    describe "parse DynSym symbols" $ do
+        let dynSymbols    = parseSymbolTables dynsymElf
+        it "parses dyn symbol table" $ do
+          dynSymbols `shouldNotBe` []
+        it "parse (x86_64) vdso dyn symbols" $ do
+          let dynSyms = concat dynSymbols
+          filter (\e -> (snd . steName) e == (Just . C.pack) "__vdso_time") dynSyms `shouldNotBe` []
+          filter (\e -> (snd . steName) e == (Just . C.pack) "__vdso_getcpu") dynSyms `shouldNotBe` []
+          filter (\e -> (snd . steName) e == (Just . C.pack) "__vdso_clock_gettime") dynSyms `shouldNotBe` []
+          filter (\e -> (snd . steName) e == (Just . C.pack) "__vdso_gettimeofday") dynSyms `shouldNotBe` []
diff --git a/tests/Spec.hs b/tests/Spec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/tests/Test.hs b/tests/Test.hs
deleted file mode 100644
--- a/tests/Test.hs
+++ /dev/null
@@ -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
diff --git a/tests/empty.elf b/tests/empty.elf
deleted file mode 100644
--- a/tests/empty.elf
+++ /dev/null
