melf 1.0.1 → 1.0.2
raw patch · 12 files changed
+777/−289 lines, 12 files
Files
- ChangeLog.md +9/−2
- README.md +489/−1
- examples/AsmAArch64.hs +13/−13
- examples/DummyLd.hs +48/−39
- examples/ForwardLabel.hs +8/−2
- examples/HelloWorld.hs +6/−5
- examples/README_ru.md +189/−212
- examples/forwardLabelExe.dump.golden +3/−3
- examples/forwardLabelExe.layout.golden +4/−4
- examples/helloWorldExe.dump.golden +2/−2
- examples/helloWorldExe.layout.golden +4/−4
- melf.cabal +2/−2
ChangeLog.md view
@@ -1,8 +1,15 @@+1.0.2+=====++- Add documentation to `README.md`+- examples: Rework `DummyLd`, add an x86 test file.+- examples: Rework the last sections of the `README_ru.md`+ 1.0.1 ===== -- Rename Aarch64 -> AArch64-- Rework examples: fuse `MkObj` into `AsmAArch64`, rewrite `MkExe` as `DummyLd`+- examples: Rename Aarch64 -> AArch64+- examples: Fuse `MkObj` into `AsmAArch64`, rewrite `MkExe` as `DummyLd` - Implement `elfFindSectionByName` - Add test data into the hackage tarball
README.md view
@@ -2,7 +2,495 @@ > A [Haskell](https://www.haskell.org/) library to parse/serialize > Executable and Linkable Format ([ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format))+> files. +## Parsing the header and table entries++Module+[`Data.Elf.Headers`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html)+implements parsing and serialization of the ELF file header and the entries of section and segment tables.++ELF files come in two flavors: 64-bit and 32-bit.+To differentiate between them type+[`ElfClass`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:ElfClass)+is defined:++``` Haskell+data ElfClass+ = ELFCLASS32 -- ^ 32-bit ELF format+ | ELFCLASS64 -- ^ 64-bit ELF format+ deriving (Eq, Show)+```++Some fields of the header and table entries have different bitwidth for 64-bit and 32-bit files.+So the type+[`WordXX a`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:WordXX)+was borrowed from the `data-elf` package:++``` Haskell+-- | @IsElfClass a@ is defined for each constructor of `ElfClass`.+-- It defines @WordXX a@, which is `Word32` for `ELFCLASS32`+-- and `Word64` for `ELFCLASS64`.+class ( SingI c+ , Typeable c+ , Typeable (WordXX c)+ , Data (WordXX c)+ , Show (WordXX c)+ , Read (WordXX c)+ , Eq (WordXX c)+ , Ord (WordXX c)+ , Bounded (WordXX c)+ , Enum (WordXX c)+ , Num (WordXX c)+ , Integral (WordXX c)+ , Real (WordXX c)+ , Bits (WordXX c)+ , FiniteBits (WordXX c)+ , Binary (Be (WordXX c))+ , Binary (Le (WordXX c))+ ) => IsElfClass c where+ type WordXX c = r | r -> c++instance IsElfClass 'ELFCLASS32 where+ type WordXX 'ELFCLASS32 = Word32++instance IsElfClass 'ELFCLASS64 where+ type WordXX 'ELFCLASS64 = Word64+```++The header of the ELF file is represented with the type+[`HeaderXX a`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:HeaderXX):++``` Haskell+-- | Parsed ELF header+data HeaderXX c =+ HeaderXX+ { hData :: ElfData -- ^ Data encoding (big- or little-endian)+ , hOSABI :: ElfOSABI -- ^ OS/ABI identification+ , hABIVersion :: Word8 -- ^ ABI version+ , hType :: ElfType -- ^ Object file type+ , hMachine :: ElfMachine -- ^ Machine type+ , hEntry :: WordXX c -- ^ Entry point address+ , hPhOff :: WordXX c -- ^ Program header offset+ , hShOff :: WordXX c -- ^ Section header offset+ , hFlags :: Word32 -- ^ Processor-specific flags+ , hPhEntSize :: Word16 -- ^ Size of program header entry+ , hPhNum :: Word16 -- ^ Number of program header entries+ , hShEntSize :: Word16 -- ^ Size of section header entry+ , hShNum :: Word16 -- ^ Number of section header entries+ , hShStrNdx :: ElfSectionIndex -- ^ Section name string table index+ }+```++So we have two types `HeaderXX 'ELFCLASS64` and `HeaderXX 'ELFCLASS32`.+To be able to work with headers uniformly the type+[`Header`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:Header)+was introduced:++``` Haskell+-- | Sigma type where `ElfClass` defines the type of `HeaderXX`+type Header = Sigma ElfClass (TyCon1 HeaderXX)+```++`Header` is a pair.+The first element is an object of the type `ElfClass` defining the width of the word.+The second element is `HeaderXX` parametrized with the first element (i. e. Σ-type from+the languages with dependent types).+To simulate Σ-types the library+`singletons`+([Hackage](https://hackage.haskell.org/package/singletons),+ ["Introduction to singletons"](https://blog.jle.im/entry/introduction-to-singletons-1.html))+was used.++`Header` is an instance of the+[`Binary`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#t:Binary)+class.++So given a lazy bytestring containing large enough initial part of ELF file one can get the header of+that file with a function like this:++``` Haskell+withHeader :: BSL.ByteString ->+ (forall a . IsElfClass a => HeaderXX a -> b) -> Either String b+withHeader bs f =+ case decodeOrFail bs of+ Left (_, _, err) -> Left err+ Right (_, _, (classS :&: hxx) :: Header) ->+ Right $ withElfClass classS f hxx+```++The function+[`decodeOrFail`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#v:decodeOrFail)+is defined in the package+[`binary`](https://hackage.haskell.org/package/binary).+The function+[`withElfClass`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#v:withElfClass)+creates a context with an implicit word width available and looks like+[`withSingI`](https://hackage.haskell.org/package/singletons-3.0.1/docs/Data-Singletons.html#v:withSingI):+++``` Haskell+-- | Convenience function for creating a+-- context with an implicit ElfClass available.+withElfClass :: Sing c -> (IsElfClass c => a) -> a+withElfClass SELFCLASS64 x = x+withElfClass SELFCLASS32 x = x+```++The module `Data.Elf.Headers` also defines the types+[`SectionXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SectionXX),+[`SegmentXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SegmentXX) and+[`SymbolXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SymbolXX)+for the elements of section, segment and symbol tables.++## Parsing the whole ELF file++The module+[`Data.Elf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html)+implements parsing and serialization of the whole ELF files.+To parse ELF file it reads ELF header, section table and segment table and uses that data to create+a list of elements of the type+[`ElfXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#t:ElfXX)+representing the recursive structure of the ELF file.+It also restores section names from the the string table indexes.+That results in creating an object of type+[`Elf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#t:Elf):++``` Haskell+-- | `Elf` is a forrest of trees of type `ElfXX`.+-- Trees are composed of `ElfXX` nodes, `ElfSegment` can contain subtrees+newtype ElfList c = ElfList [ElfXX c]++-- | Elf is a sigma type where `ElfClass` defines the type of `ElfList`+type Elf = Sigma ElfClass (TyCon1 ElfList)++-- | 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+ = ElfSectionData BSL.ByteString -- ^ Regular section data+ | ElfSectionDataStringTable -- ^ Section data will be generated from section names++-- | The type of node that defines Elf structure.+data ElfXX (c :: ElfClass)+ = 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+ }+ | ElfSectionTable+ | ElfSegmentTable+ | 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 -- ^ The content of the section+ }+ | 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 :: [ElfXX c] -- ^ Content of the segment+ }+ | ElfRawData -- ^ Some ELF files (some executables) don't bother to define+ -- sections for linking and have just raw data in segments.+ { edData :: BSL.ByteString -- ^ Raw data in ELF file+ }+ | ElfRawAlign -- ^ 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 @+ { eaOffset :: WordXX c -- ^ Align value+ , eaAlign :: WordXX c -- ^ Align module+ }++```++Not each object of that type can be serialized.++ * Constructor `ElfSection` still has a section number.+ It is required as the symbol table and some other structures+ refer to the sections by theirs indexes.+ So the section indexes should be consecutive integers starting from 1.+ Section with index 0 is always empty and is created by the library.++ * There should be a single `ElfHeader`. It should be the first nonempty node of the tree.++ * If there exists at least one node `ElfSection` then there should exist exactly one+ node `ElfSectionTable` and exactly one section that has `ElfSectionDataStringTable` as the value+ of its `esData` field (the string table for the names of sections).++ * If there exists at least one node `ElfSegment` then there should exist exactly one+ node `ElfSegmentTable`.++Correctly composed ELF object can be serialized with the function+[`serializeElf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:serializeElf)+and parsed with the function+[`parseElf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:parseElf):++``` Haskell+serializeElf :: MonadThrow m => Elf -> m ByteString+parseElf :: MonadCatch m => ByteString -> m Elf+```++`ELF` is not an instance of the class `Binary` because+[`PutM`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary-Put.html#t:PutM)+is not an instance of the class `MonadFail`.++## Generation of object files++To create machine code that is used in the examples a pair of modules were created.+The module+[`AsmAArch64`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/AsmAarch64.hs)+provides a DSL embedded in Haskell.+This DSL is a kind of assembler language for the AArch64 platform.+It exports some primitives to generate machine instructions and organize machine code.+It also exports function `assemble` that consumes the monad composed of those primitives and+produces an object of the type `Elf`:++``` Haskell+assemble :: MonadCatch m => StateT CodeState m () -> m Elf+```++The idea was inspired by the article+([Stephen Diehl "Monads to Machine Code"](https://www.stephendiehl.com/posts/monads_machine_code.html)).+Detailed description of this module is available in russian:+[README_ru.md](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/README_ru.md).++The module+[`HelloWorld`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/HelloWorld.hs)+uses primitives from `AsmAArch64` to compose relocatable executable code that uses system calls+to output a "Hello World!" message into standard output and exit:++``` Haskell+helloWorld :: MonadCatch m => StateT CodeState m ()+```++Function `assemble` uses the `melf` library to generate an object file:++``` Haskell+ return $ SELFCLASS64 :&: ElfList+ [ ElfHeader+ { ehData = ELFDATA2LSB+ , ehOSABI = ELFOSABI_SYSV+ , ehABIVersion = 0+ , ehType = ET_REL+ , ehMachine = EM_AARCH64+ , ehEntry = 0+ , ehFlags = 0+ }+ , ElfSection+ { esName = ".text"+ , esType = SHT_PROGBITS+ , esFlags = SHF_EXECINSTR .|. SHF_ALLOC+ , esAddr = 0+ , esAddrAlign = 8+ , esEntSize = 0+ , esN = textSecN+ , esLink = 0+ , esInfo = 0+ , esData = ElfSectionData txt+ }+ , ElfSection+ { esName = ".shstrtab"+ , esType = SHT_STRTAB+ , esFlags = 0+ , esAddr = 0+ , esAddrAlign = 1+ , esEntSize = 0+ , esN = shstrtabSecN+ , esLink = 0+ , esInfo = 0+ , esData = ElfSectionDataStringTable+ }+ , ElfSection+ { esName = ".symtab"+ , esType = SHT_SYMTAB+ , esFlags = 0+ , esAddr = 0+ , esAddrAlign = 8+ , esEntSize = symbolTableEntrySize ELFCLASS64+ , esN = symtabSecN+ , esLink = fromIntegral strtabSecN+ , esInfo = 1+ , esData = ElfSectionData symbolTableData+ }+ , ElfSection+ { esName = ".strtab"+ , esType = SHT_STRTAB+ , esFlags = 0+ , esAddr = 0+ , esAddrAlign = 1+ , esEntSize = 0+ , esN = strtabSecN+ , esLink = 0+ , esInfo = 0+ , esData = ElfSectionData stringTableData+ }+ , ElfSectionTable+ ]+```++It runs the `State` monad that was passed as an argument.+As a result the final state of `CodeState` includes all the data neсessary to produce ELF file, in+particular:++ * `txt` refers to the content of the `.text` section,+ * `symbolTableData` refers to the content of the symbol table section,+ * `stringTableData` refers to the content of the string table section linked to the symbol table.++Names with `SecN` suffixes (`textSecN`, `shstrtabSecN`, `symtabSecN`, `strtabSecN`)+are predefined section numbers that conform to the conditions stated above.++For the sake of simplicity external symbol resolution and data section allocation were not implemented.+It requires implementation of relocation tables. On the other hand, the resulting code+is position-independent.++Use this module to produce object file and try to link it:++```+[nix-shell:examples]$ ghci +GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help+Prelude> :l AsmAArch64.hs HelloWorld.hs +[1 of 2] Compiling AsmAArch64 ( AsmAArch64.hs, interpreted )+[2 of 2] Compiling HelloWorld ( HelloWorld.hs, interpreted )+Ok, two modules loaded.+*AsmAArch64> import HelloWorld+*AsmAArch64 HelloWorld> elf <- assemble helloWorld+*AsmAArch64 HelloWorld> bs <- serializeElf elf+*AsmAArch64 HelloWorld> BSL.writeFile "helloWorld.o" bs+*AsmAArch64 HelloWorld> +Leaving GHCi.++[nix-shell:examples]$ aarch64-unknown-linux-gnu-gcc -nostdlib helloWorld.o -o helloWorld++[nix-shell:examples]$ +```++The linker accepted the object file. Try to run the result:++```+[nix-shell:examples]$ qemu-aarch64 helloWorld+Hello World!++[nix-shell:examples]$ +```++It works.++## Generation of executable files++The module+[`DummyLd`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/DummyLd.hs)+uses the section `.text` of object file to create an executable file.+Code relocation and symbol resolution is not implemented so that procedure works only+for position-independent code that does not refer to external translation units,+for example, it works with the code described above.++Function `dummyLd` consumes an object of the type `Elf` and finds a section `.text`+(using [`elfFindSectionByName`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:elfFindSectionByName))+and header+(using [`elfFindHeader`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:elfFindHeader))+in it.+Then the header type is changed to `ET_EXEC`, the address of the first executable instruction is specified and+a loadable segment containing the header and the content of `.text` is formed:++``` Haskell+data MachineConfig (a :: ElfClass)+ = MachineConfig+ { mcAddress :: WordXX a -- ^ Virtual address of the executable segment+ , mcAlign :: WordXX a -- ^ Required alignment of the executable segment+ -- in physical memory (depends on max page size)+ }++getMachineConfig :: (IsElfClass a, MonadThrow m) => ElfMachine -> m (MachineConfig a)+getMachineConfig EM_AARCH64 = return $ MachineConfig 0x400000 0x10000+getMachineConfig EM_X86_64 = return $ MachineConfig 0x400000 0x1000+getMachineConfig _ = $chainedError "could not find machine config for this arch"++dummyLd' :: forall a m . (MonadThrow m, IsElfClass a) => ElfList a -> m (ElfList a)+dummyLd' (ElfList es) = do++ txtSection <- elfFindSectionByName es ".text"+ txtSectionData <- case txtSection of+ ElfSection { esData = ElfSectionData textData } -> return textData+ _ -> $chainedError "could not find correct \".text\" section"++ header <- elfFindHeader es+ case header of+ ElfHeader { .. } -> do+ MachineConfig { .. } <- getMachineConfig ehMachine+ return $ ElfList+ [ ElfSegment+ { epType = PT_LOAD+ , epFlags = PF_X .|. PF_R+ , epVirtAddr = mcAddress+ , epPhysAddr = mcAddress+ , epAddMemSize = 0+ , epAlign = mcAlign+ , epData =+ [ ElfHeader+ { ehType = ET_EXEC+ , ehEntry = mcAddress + headerSize (fromSing $ sing @a)+ , ..+ }+ , ElfRawData+ { edData = txtSectionData+ }+ ]+ }+ , ElfSegmentTable+ ]+ _ -> $chainedError "could not find ELF header"++-- | @dummyLd@ places the content of ".text" section of the input ELF+-- into the loadable segment of the resulting ELF.+-- This could work if there are no relocations or references to external symbols.+dummyLd :: MonadThrow m => Elf -> m Elf+dummyLd (c :&: l) = (c :&:) <$> withElfClass c dummyLd' l+```++Try to use this code to produce executable file without GNU linker:++```+[nix-shell:examples]$ ghci+GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help+Prelude> :l DummyLd.hs+[1 of 1] Compiling DummyLd ( DummyLd.hs, interpreted )+Ok, one module loaded.+*DummyLd> import Data.ByteString.Lazy as BSL+*DummyLd BSL> i <- BSL.readFile "helloWorld.o"+*DummyLd BSL> elf <- parseElf i+*DummyLd BSL> elf' <- dummyLd elf+*DummyLd BSL> o <- serializeElf elf'+*DummyLd BSL> BSL.writeFile "helloWorld2" o+*DummyLd BSL> +Leaving GHCi.++[nix-shell:examples]$ chmod +x helloWorld2++[nix-shell:examples]$ qemu-aarch64 helloWorld2+Hello World!++[nix-shell:examples]$ +```++It works.+ ## Related work - [elf](https://github.com/wangbj/elf)@@ -18,7 +506,7 @@ ## Tests Test data is committed with [git-lfs](https://git-lfs.github.com/).-To run tests, issue this command in `nix-shell`: `cabal new-test --test-show-details=direct`+Only testdata/orig/* tests are included to hackage distributive to keep the tarball size small. ## License
examples/AsmAArch64.hs view
@@ -15,7 +15,7 @@ module AsmAArch64 ( CodeState , Register- , RelativeRef+ , Label , adr , b , mov@@ -56,8 +56,8 @@ newtype CodeOffset = CodeOffset { getCodeOffset :: Int64 } deriving (Eq, Show, Ord, Num, Enum, Real, Integral, Bits, FiniteBits) newtype Instruction = Instruction { getInstruction :: Word32 } deriving (Eq, Show, Ord, Num, Enum, Real, Integral, Bits, FiniteBits) -data RelativeRef = CodeRef !CodeOffset- | PoolRef !CodeOffset+data Label = CodeRef !CodeOffset+ | PoolRef !CodeOffset -- Args: -- Offset of the instruction@@ -68,7 +68,7 @@ { offsetInPool :: CodeOffset , poolReversed :: [Builder] , codeReversed :: [InstructionGen]- , symbolsRefersed :: [(String, RelativeRef)]+ , symbolsRefersed :: [(String, Label)] } emit' :: MonadState CodeState m => InstructionGen -> m ()@@ -80,7 +80,7 @@ emit :: MonadState CodeState m => Instruction -> m () emit i = emit' $ \ _ _ -> Right i -emitPool :: MonadState CodeState m => Word -> ByteString -> m RelativeRef+emitPool :: MonadState CodeState m => Word -> ByteString -> m Label emitPool a bs = state f where f CodeState {..} = let@@ -94,7 +94,7 @@ } ) -label :: MonadState CodeState m => m RelativeRef+label :: MonadState CodeState m => m Label label = gets (CodeRef . (* instructionSize) . fromIntegral . P.length . codeReversed) x0, x1, x2, x8 :: Register 'X@@ -125,7 +125,7 @@ SW -> 0 -- | C6.2.10 ADR-adr :: MonadState CodeState m => Register 'X -> RelativeRef -> m ()+adr :: MonadState CodeState m => Register 'X -> Label -> m () adr (R n) rr = emit' f where offsetToImm :: CodeOffset -> Either String Word32@@ -147,7 +147,7 @@ .|. n -- | C6.2.26 B-b :: MonadState CodeState m => RelativeRef -> m ()+b :: MonadState CodeState m => Label -> m () b rr = emit' f where offsetToImm26 :: CodeOffset -> Either String Word32@@ -176,12 +176,12 @@ h = w .&. m in if w >= 0 then h == 0 else h == m -findOffset :: CodeOffset -> RelativeRef -> CodeOffset+findOffset :: CodeOffset -> Label -> CodeOffset findOffset _poolOffset (CodeRef codeOffset) = codeOffset findOffset poolOffset (PoolRef offsetInPool) = poolOffset + offsetInPool -- | C6.2.132 LDR (literal)-ldr :: (MonadState CodeState m, SingI w) => Register w -> RelativeRef -> m ()+ldr :: (MonadState CodeState m, SingI w) => Register w -> Label -> m () ldr r@(R n) rr = emit' f where offsetToImm19 :: CodeOffset -> Either String Word32@@ -202,10 +202,10 @@ svc :: MonadState CodeState m => Word16 -> m () svc imm = emit $ 0xd4000001 .|. (fromIntegral imm `shift` 5) -ascii :: MonadState CodeState m => String -> m RelativeRef+ascii :: MonadState CodeState m => String -> m Label ascii s = emitPool 1 $ BSLC.pack s -exportSymbol :: MonadState CodeState m => String -> RelativeRef -> m ()+exportSymbol :: MonadState CodeState m => String -> Label -> m () exportSymbol s r = modify f where f (CodeState {..}) = CodeState { symbolsRefersed = (s, r) : symbolsRefersed , ..@@ -248,7 +248,7 @@ -- resolve symbolTable let- ff :: (String, RelativeRef) -> ElfSymbolXX 'ELFCLASS64+ ff :: (String, Label) -> ElfSymbolXX 'ELFCLASS64 ff (s, r) = let steName = s
examples/DummyLd.hs view
@@ -1,14 +1,16 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module DummyLd (dummyLd) where import Control.Monad.Catch import Data.Bits-import Data.ByteString.Lazy as BSL-import Data.Word+import Data.Singletons import Data.Singletons.Sigma import Data.Elf@@ -16,48 +18,55 @@ import Data.Elf.Headers import Control.Exception.ChainedException -addr :: Word64-addr = 0x400000+data MachineConfig (a :: ElfClass)+ = MachineConfig+ { mcAddress :: WordXX a -- ^ Virtual address of the executable segment+ , mcAlign :: WordXX a -- ^ Required alignment of the executable segment+ -- in physical memory (depends on max page size)+ } -mkExe :: MonadThrow m => BSL.ByteString -> m (ElfList 'ELFCLASS64)-mkExe txt = return $ ElfList [ segment ]- where- segment = ElfSegment- { epType = PT_LOAD- , epFlags = PF_X .|. PF_R- , epVirtAddr = addr- , epPhysAddr = addr- , epAddMemSize = 0- , epAlign = 0x10000- , epData =- [ ElfHeader- { ehData = ELFDATA2LSB- , ehOSABI = ELFOSABI_SYSV- , ehABIVersion = 0- , ehType = ET_EXEC- , ehMachine = EM_AARCH64- , ehEntry = addr + headerSize ELFCLASS64- , ehFlags = 0- }- , ElfRawData- { edData = txt- }- , ElfSegmentTable- ]- }+getMachineConfig :: (IsElfClass a, MonadThrow m) => ElfMachine -> m (MachineConfig a)+getMachineConfig EM_AARCH64 = return $ MachineConfig 0x400000 0x10000+getMachineConfig EM_X86_64 = return $ MachineConfig 0x400000 0x1000+getMachineConfig _ = $chainedError "could not find machine config for this arch" -dummyLd' :: MonadThrow m => ElfList 'ELFCLASS64 -> m (ElfList 'ELFCLASS64)+dummyLd' :: forall a m . (MonadThrow m, IsElfClass a) => ElfList a -> m (ElfList a) dummyLd' (ElfList es) = do txtSection <- elfFindSectionByName es ".text"-- case txtSection of- ElfSection{esData = ElfSectionData textData} -> mkExe textData+ txtSectionData <- case txtSection of+ ElfSection { esData = ElfSectionData textData } -> return textData _ -> $chainedError "could not find correct \".text\" section" --- | It places the content of ".text" section of the input ELF into the--- loadable segment of the resulting ELF.--- It could work if there are no relocations or references to external symbols+ header <- elfFindHeader es+ case header of+ ElfHeader { .. } -> do+ MachineConfig { .. } <- getMachineConfig ehMachine+ return $ ElfList+ [ ElfSegment+ { epType = PT_LOAD+ , epFlags = PF_X .|. PF_R+ , epVirtAddr = mcAddress+ , epPhysAddr = mcAddress+ , epAddMemSize = 0+ , epAlign = mcAlign+ , epData =+ [ ElfHeader+ { ehType = ET_EXEC+ , ehEntry = mcAddress + headerSize (fromSing $ sing @a)+ , ..+ }+ , ElfRawData+ { edData = txtSectionData+ }+ ]+ }+ , ElfSegmentTable+ ]+ _ -> $chainedError "could not find ELF header"++-- | @dummyLd@ places the content of ".text" section of the input ELF+-- into the loadable segment of the resulting ELF.+-- This could work if there are no relocations or references to external symbols. dummyLd :: MonadThrow m => Elf -> m Elf-dummyLd (SELFCLASS32 :&: _) = $chainedError "AArch64 arch object expected"-dummyLd (SELFCLASS64 :&: es) = (SELFCLASS64 :&:) <$> dummyLd' es+dummyLd (c :&: l) = (c :&:) <$> withElfClass c dummyLd' l
examples/ForwardLabel.hs view
@@ -8,6 +8,7 @@ import Control.Monad.Catch import Control.Monad.Fix import Control.Monad.State+import Data.Word import AsmAArch64 @@ -17,6 +18,11 @@ bad :: String bad = "bad\n" +-- | syscalls+sysExit, sysWrite :: Word16+sysWrite = 64+sysExit = 93+ forwardLabel :: (MonadCatch m, MonadFix m) => StateT CodeState m () forwardLabel = mdo @@ -37,9 +43,9 @@ skipBad <- label - mov x8 64+ mov x8 sysWrite svc 0 mov x0 0- mov x8 93+ mov x8 sysExit svc 0
examples/HelloWorld.hs view
@@ -13,9 +13,10 @@ msg :: String msg = "Hello World!\n" -sys_exit, sys_write :: Word16-sys_write = 64-sys_exit = 93+-- | syscalls+sysExit, sysWrite :: Word16+sysWrite = 64+sysExit = 93 helloWorld :: MonadCatch m => StateT CodeState m () helloWorld = do@@ -26,9 +27,9 @@ helloString <- ascii msg adr x1 helloString mov x2 $ fromIntegral $ P.length msg- mov x8 sys_write+ mov x8 sysWrite svc 0 mov x0 0- mov x8 sys_exit+ mov x8 sysExit svc 0
examples/README_ru.md view
@@ -1,6 +1,6 @@ --- title: Работа с файлами формата ELF из Хаскела-date: November 17, 2021+date: December 21, 2021 --- Работа с файлами формата ELF -- популярная тема на Хабре.@@ -13,37 +13,40 @@ Эти библиотеки работают только с заголовками и элементами таблиц и не дают возможности сгенерировать объектный файл. -Я написал библиотеку для работы с файлами формата ELF на Хаскеле-([GitHub](https://github.com/aleksey-makarov/melf),- [Hackage](https://hackage.haskell.org/package/melf))-и хочу показать как её использовать.+Библиотека `melf`+([GitHub](https://github.com/aleksey-makarov/melf), [Hackage](https://hackage.haskell.org/package/melf))+даёт возможность полностью разобрать файл ELF и сгенерировать такой файл по несложной структуре данных.+Ниже даются примеры её использования. ## Внутреннее устройство ELF -Первые несколько байт файла занимает заголовок ELF.-В нём, в частности, указано, где в файле расположены таблица секций и таблица сегментов. -Сегменты и секции -- непрерывные участки файла.-Сегменты описывают, что нужно поместить в память при загрузки программы,-а секции я бы описал как неделимые результаты работы-компилятора.+В файле формата ELF последовательно размещены заголовок файла,+секции, сегменты, таблица секций, таблица сегментов.+Сегменты в свою очередь скомпанованы из таких же элементов.+Порядок этих элементов произвольный, за исключением того, что заголовок файла всегда размещается в начале файла,+а таблиц секций и таблиц сегментов может быть не более одной.+Каждый такой участок выровнен в файле, например, сегменты обычно выравниваются на размер страницы,+а секции с данными -- на размер слова.++В заголовке описано, где располагаются таблица секций и таблица сегментов,+которые в свою очередь описывают, где располагаются секции и сегменты.++Сегменты указывают, что нужно поместить в память при загрузки программы,+а секции я бы определил как неделимые результаты работы компилятора. В секциях размещены исполняемый код, таблицы символов, инициализированные данные.+Линковщик объединяет секции из различных единиц трансляции в сегменты. -Файл формата ELF можно представлять как список деревьев.-Деревьями могуть быть секции, сегменты, заголовок файла, таблица секций, таблица сегментов.-Поддеревья могут содержаться только в сегментах.-Узлы дерева выравниваются в файле, например, сегменты обычно выравниваются на размер страницы,-а секции с данными -- на размер слова. Вполне валидным может быть файл, где сегмент содержит данные, не размеченные как какая-либо секция. ## Базовый уровень В модуле-[`Data.Elf.Headers`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html)+[`Data.Elf.Headers`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html) реализованы разбор и сериализация заголовка файла ELF и элементов таблиц секций и сегментов. Для различения 64- и 32-битных структур определён тип-[`ElfClass`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:ElfClass)+[`ElfClass`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:ElfClass) ``` Haskell data ElfClass@@ -54,14 +57,13 @@ Некоторые поля заголовка и элементов таблиц секций и сегментов имеют разную ширину в битах, зависящую от `ElfClass`, поэтому нужен тип-[`WordXX a`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:WordXX),+[`WordXX a`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:WordXX), который был позаимствован из пакета `data-elf`: ``` Haskell -- | @IsElfClass a@ is defined for each constructor of `ElfClass`. -- It defines @WordXX a@, which is `Word32` for `ELFCLASS32` -- and `Word64` for `ELFCLASS64`.-type IsElfClass :: ElfClass -> Constraint class ( SingI c , Typeable c , Typeable (WordXX c)@@ -90,11 +92,10 @@ ``` Заголовок файла ELF представлен с помощью типа-[`HeaderXX a`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:HeaderXX):+[`HeaderXX a`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:HeaderXX): ``` Haskell -- | Parsed ELF header-type HeaderXX :: ElfClass -> Type data HeaderXX c = HeaderXX { hData :: ElfData -- ^ Data encoding (big- or little-endian)@@ -114,9 +115,8 @@ } ``` -Но это разные типы для 64- и 32-битных форматов. Для однообразной работы с форматами с разной шириной слова определён тип-[`Header`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:Header):+[`Header`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:Header): ``` Haskell -- | Sigma type where `ElfClass` defines the type of `HeaderXX`@@ -124,14 +124,13 @@ ``` `Header` это пара, первый элемент которой -- объект типа `ElfClass`, определяющий ширину слова,-второй -- `HeaderXX`, параметризованный первым элементом ($\Sigma$-тип из языков с зависимыми типами).-Для симуляции $\Sigma$-типов использована библиотека `singletons`+второй -- `HeaderXX`, параметризованный первым элементом (Σ-тип из языков с зависимыми типами).+Для симуляции Σ-типов использована библиотека `singletons` ([Hackage](https://hackage.haskell.org/package/singletons), ["Introduction to singletons"](https://blog.jle.im/entry/introduction-to-singletons-1.html)). -Для `Header` определён экземпляр класса+`Header` является экземпляром класса [`Binary`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#t:Binary).- Таким образом, имея ленивую строку байт, содержащую достаточно длинный начальный отрезок файла ELF, можно получить заголовок этого файла, например, следующей функцией: @@ -148,9 +147,10 @@ Здесь [`decodeOrFail`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#v:decodeOrFail) определена в пакете [`binary`](https://hackage.haskell.org/package/binary), а-[`withElfClass`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#v:withElfClass)-похожа на-[`withSing`](https://hackage.haskell.org/package/singletons-3.0.1/docs/Data-Singletons.html#v:withSing):+[`withElfClass`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#v:withElfClass)+делает явный аргумент, определяющий размер слова, неявным (constraint).+Функция похожа на+[`withSingI`](https://hackage.haskell.org/package/singletons-3.0.1/docs/Data-Singletons.html#v:withSingI): ``` Haskell -- | Convenience function for creating a@@ -161,23 +161,23 @@ ``` В `Data.Elf.Headers` определены также типы-[`SectionXX`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:SectionXX),-[`SegmentXX`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:SegmentXX) и-[`SymbolXX`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf-Headers.html#t:SymbolXX)+[`SectionXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SectionXX),+[`SegmentXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SegmentXX) и+[`SymbolXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf-Headers.html#t:SymbolXX) для элементов таблиц секций, сегментов и символов. ## Верхний уровень В модуле-[`Data.Elf`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf.html)+[`Data.Elf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html) реализованы полные разбор и сериализация файлов формата ELF. Чтобы разобрать такой файл читаются заголовок ELF, таблицa секций и таблица сегментов и на основании этой информации создаётся список элементов типа-[`ElfXX`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf.html#t:ElfXX),+[`ElfXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#t:ElfXX), отображающий рекурсивную-структуру файла ELF. Кроме восстановления структуры в процессе разбора, например, по номерам+структуру файла ELF. Кроме восстановления структуры в процессе разбора, по номерам секций восстанавливаются их имена. В результате получается объект типа-[`Elf`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf.html#t:Elf):+[`Elf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#t:Elf): ``` Haskell -- | `Elf` is a forrest of trees of type `ElfXX`.@@ -246,10 +246,10 @@ Не каждый объект такого типа может быть сериализован. * В конструкторе `ElfSection`-остался номер секции. Он нужен, так как таблица символов и некоторые другие структуры-ссылаются на секци по их номерам. Поэтому при построении объекта такого типа нужно убедиться,-что секции пронумерованы корректно, т. е. последовательными целыми от 1 до количества секций.-Секция с номером 0 всегда пустая и она добавляется автоматически.+ остался номер секции. Он нужен, так как таблица символов и некоторые другие структуры+ ссылаются на секци по их номерам. Поэтому при построении объекта такого типа нужно убедиться,+ что секции пронумерованы корректно, т. е. последовательными целыми числами от 1 до количества секций.+ Секция с номером 0 всегда пустая, она добавляется автоматически. * В структуре должен быть единственный `ElfHeader`, он должен быть самым первым непустым узлом в дереве.@@ -257,12 +257,12 @@ * Если есть хотя бы один узел `ElfSection`, то должен присутсвовать в точности один узел `ElfSectionTable` и в точности одна секция, поле `esData` которой равно `ElfSectionDataStringTable` (таблица строк для имён секций). - * Если есть хотя бы один узел с конструктором `ElfSegment`, то должен присутсвовать в точности один узел `ElfSegmentTable`.+ * Если есть хотя бы один узел `ElfSegment`, то должен присутсвовать в точности один узел `ElfSegmentTable`. Корректно сформированный объект можно сериализовать с помощью функции-[`serializeElf`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf.html#v:serializeElf)+[`serializeElf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:serializeElf) и разобрать с помощью функции-[`parseElf`](https://hackage.haskell.org/package/melf-1.0.0/docs/Data-Elf.html#v:parseElf):+[`parseElf`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:parseElf): ``` Haskell serializeElf :: MonadThrow m => Elf -> m ByteString@@ -270,52 +270,51 @@ ``` Экземпляр класса `Binary` для `ELF` не определён, так как-[`Put`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#t:Put)+[`PutM`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary-Put.html#t:PutM) не является экземпляром класса `MonadFail`. ## Ассемблер как EDSL для Хаскела Для использования в демонстрационных приложениях написан модуль, генерирующий машинный код для AArch64-(файл [`AsmAarch64.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.0/examples/AsmAarch64.hs)).+(файл [`AsmAArch64.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/AsmAArch64.hs)). Сгенерированный код использует системные вызовы чтобы вывести на стандартный вывод "Hello World!" и завершить приложение. Идея позаимствована из вдохновляющей статьи Стивена Дила "От монад к машинному коду" ([Stephen Diehl "Monads to Machine Code"](https://www.stephendiehl.com/posts/monads_machine_code.html)). Так же как в статье, используется монада состояния, в нашем случае `CodeState`. -Инициализированные, в частности, константные данные часто размещаются в отдельных секциях.-В нашем случае для константной строки использованы массивы литералов (literal pools).-При этом данные размещаются в той же секции, что и машинный код, в месте,-недоступном потоку исполнения, например, после команды возвращения из подпрограммы.-При этом оказывается легко обращаться к таким данным с помощью команд,-вычисляющих адрес данных с использованием счётчика команд.-Код, обращающийся к таким данным, не требует использования таблицы перемещений.-`CodeState` содержит размер массива литералов, сам массив литералов, массив машинных кодов и массив символов:- ``` Haskell data CodeState = CodeState { offsetInPool :: CodeOffset , poolReversed :: [Builder] , codeReversed :: [InstructionGen]- , symbolsRefersed :: [(String, RelativeRef)]+ , symbolsRefersed :: [(String, Label)] } ``` -Для создания меток и ссылок на данные в массиве литералов введён тип `RelativeRef`+`CodeState` содержит размер массива литералов, сам массив литералов,+массив машинных кодов и массив символов. +Массив литералов (literal pools) это участок секции в которой расположен+исполняемый код, используемый для хранения константных данных.+К таким данным легко обращаться с помощью команд,+вычисляющих адрес данных с использованием счётчика команд.++Для создания меток и ссылок на данные в массиве литералов введён тип `Label`+ ``` Haskell newtype CodeOffset = CodeOffset { getCodeOffset :: Int64 } deriving (Eq, Show, Ord, Num, Enum, Real, Integral, Bits, FiniteBits) -data RelativeRef = CodeRef CodeOffset- | PoolRef CodeOffset+data Label = CodeRef CodeOffset+ | PoolRef CodeOffset ``` Конструктор `CodeRef` используется для ссылки на код (для создания меток): ``` Haskell-label :: MonadState CodeState m => m RelativeRef+label :: MonadState CodeState m => m Label label = gets (CodeRef . (* instructionSize) . fromIntegral . P.length@@ -323,8 +322,9 @@ ``` Конструктор `PoolRef` хранит смещение данных от начала массива литералов.+Он используется для создания `CodeOffset` в функции `emitPool` (см. ниже). -Фактически в массиве машинных кодов хранятся функции для генерации машинного кода+В массиве машинных кодов хранятся функции для генерации машинного кода из смещения команды от начала секции и смещения массива литералов (которое будет известно только после обработки всех ассемблерных команд, так как массив литералов располагается после кода):@@ -334,7 +334,7 @@ CodeOffset -> Either String Instruction ``` -Для добавления в массив машинных кодов используется функция `emit'`:+Для добавления функций в массив машинных кодов используется функция `emit'`: ```Haskell emit' :: MonadState CodeState m => InstructionGen -> m ()@@ -356,7 +356,7 @@ svc imm = emit $ 0xd4000001 .|. (fromIntegral imm `shift` 5) ``` -Как и многие другие команды архитектуры AArch64, команда `mov` может работать с регистрами+Многие команды архитектуры AArch64 могут работать с регистрами как с 64-битными или как с 32-битными значениями. Для указания разрядности регистров для них используются разные имена: `x0`, `x1`... -- для 64-битных, `w0`, `w1`... -- для 32-битных.@@ -392,7 +392,7 @@ -- | C6.2.10 ADR adr :: MonadState CodeState m => Register 'X ->- RelativeRef -> m ()+ Label -> m () ``` Для добавления данных в массив литералов используется функция `emitPool`:@@ -400,7 +400,7 @@ ``` Haskell emitPool :: MonadState CodeState m => Word ->- ByteString -> m RelativeRef+ ByteString -> m Label ``` Здесь первый аргумент -- необходимое выравнивание, второй -- данные, которые нужно@@ -411,60 +411,32 @@ С помощью этой функции можно, например, реализовать аналог ассемблерной директивы `.ascii`: ``` Haskell-ascii :: MonadState CodeState m => String -> m RelativeRef+ascii :: MonadState CodeState m => String -> m Label ascii s = emitPool 1 $ BSLC.pack s ``` -Кроме того, есть массив символов. Символы создаются из меток:+Символы создаются из меток: ``` Haskell-exportSymbol :: MonadState CodeState m => String -> RelativeRef -> m ()+exportSymbol :: MonadState CodeState m => String -> Label -> m () exportSymbol s r = modify f where f (CodeState {..}) = CodeState { symbolsRefersed = (s, r) : symbolsRefersed , .. } ``` -Функция `assemble` генерирует код из `CodeState`:--``` Haskell-assemble :: MonadCatch m =>- ElfSectionIndex ->- StateT CodeState m () -> m (BSL.ByteString, [ElfSymbolXX 'ELFCLASS64])-```--Первый аргумент -- номер секции, которая будет содержать код,-второй -- программа, которую нужно преобразовать в код.-Функция возвращает пару, первый элемент которой -- содержание-секции ".text", второй -- таблица символов.--Для простоты не реализовано обращение к внешним символам и размещение данных в-отдельных секциях.-Всё это требует реализации таблиц перемещений, с другой стороры, сгенерированный код-получается позиционно-независимым.--Код для вывода "Hello World!" на встроенном в Хаскелл DSL выглядит так-([файл `HelloWordl.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.0/examples/HelloWorld.hs)):+Используя таким образом определённые примитивы можно написать код+для вывода "Hello World!" на встроенном в Хаскел DSL+([файл `HelloWorld.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/HelloWorld.hs)): ``` Haskell-{-# LANGUAGE DataKinds #-}--module HelloWorld (helloWorld) where--import Prelude as P--import Control.Monad.Catch-import Control.Monad.State-import Data.Word--import AsmAarch64- msg :: String msg = "Hello World!\n" -sys_exit, sys_write :: Word16-sys_write = 64-sys_exit = 93+-- | syscalls+sysExit, sysWrite :: Word16+sysWrite = 64+sysExit = 93 helloWorld :: MonadCatch m => StateT CodeState m () helloWorld = do@@ -475,52 +447,36 @@ helloString <- ascii msg adr x1 helloString mov x2 $ fromIntegral $ P.length msg- mov x8 sys_write+ mov x8 sysWrite svc 0 mov x0 0- mov x8 sys_exit+ mov x8 sysExit svc 0 ``` Если нужно сослаться на метку, сформированную ниже по коду, нужно работать в монаде `MonadFix` и использовать ключевое слово `mdo` вместо `do`-(см. файл [`ForwardLabel.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.0/examples/ForwardLabel.hs)).+(см. файл [`ForwardLabel.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/ForwardLabel.hs)). ## Генерация объектных файлов -Используем библиотеку `melf` для генерации объектных файлов-([файл `MkObj.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.0/examples/MkObj.hs)):+Функция `assemble`+(см. [`AsmAArch64.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/AsmAArch64.hs))+транслирует код на встроенном в Хаскел ассемблере в машинные коды и возвращает объект типа `Elf`: ``` Haskell-{-# LANGUAGE DataKinds #-}--module MkObj (mkObj) where--import Prelude as P--import Control.Monad.Catch-import Control.Monad.State-import Data.Bits-import Data.Singletons.Sigma--import Data.Elf-import Data.Elf.Constants-import Data.Elf.Headers--import AsmAarch64--textSecN, shstrtabSecN, strtabSecN, symtabSecN :: ElfSectionIndex-textSecN = 1-shstrtabSecN = 2-strtabSecN = 3-symtabSecN = 4--mkObj :: MonadCatch m => StateT CodeState m () -> m Elf-mkObj m = do+assemble :: MonadCatch m => StateT CodeState m () -> m Elf+``` - (txt, symbolTable) <- assemble textSecN m- (symbolTableData, stringTableData) <- serializeSymbolTable ELFDATA2LSB symbolTable+Она запускает переданную в качестве аргумента монаду `State`, представляющую ассемблерный код.+Конечное состояние этой монады содержит всю информацию о содержимом секции, которая будет содержать код+(секция с именем `.text`), и таблицы символов,+a этого достаточно чтобы сгенерировать объектный файл.+На содержимое секции `.text` ссылается имя `txt`,+на содержимое таблицы символов -- имя `symbolTableData`,+на содержимое таблицы строк, связанной с таблицей символов -- имя `stringTableData`: +``` Haskell return $ SELFCLASS64 :&: ElfList [ ElfHeader { ehData = ELFDATA2LSB@@ -583,27 +539,31 @@ ] ``` +Здесь имена с суффиксом `SecN` (`textSecN`, `shstrtabSecN`, `symtabSecN`, `strtabSecN`) -- предопределённые+номера секций, удовлетворяющие сформулированным выше условиям.++Для простоты не реализовано обращение ко внешним символам и размещение данных в+отдельных секциях.+Всё это требует реализации таблиц перемещений, с другой стороны, сгенерированный код+получается позиционно-независимым.+ Сгенерируем с помощью этого модуля объектный файл и попробуем его слинковать: ``` [nix-shell:examples]$ ghci -GHCi, version 8.10.4: https://www.haskell.org/ghc/ :? for help-Prelude> :l MkObj.hs HelloWorld.hs-[1 of 3] Compiling AsmAarch64 ( AsmAarch64.hs, interpreted )-[2 of 3] Compiling HelloWorld ( HelloWorld.hs, interpreted )-[3 of 3] Compiling MkObj ( MkObj.hs, interpreted )-Ok, three modules loaded.-*MkObj> import MkObj -*MkObj MkObj> import HelloWorld -*MkObj MkObj HelloWorld> elf <- mkObj helloWorld-*MkObj MkObj HelloWorld> import Data.Elf-*MkObj MkObj HelloWorld Data.Elf> bs <- serializeElf elf-*MkObj MkObj HelloWorld Data.Elf> import Data.ByteString.Lazy as BSL-*MkObj MkObj HelloWorld Data.Elf BSL> BSL.writeFile "hello.o" bs-*MkObj MkObj HelloWorld Data.Elf BSL> +GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help+Prelude> :l AsmAArch64.hs HelloWorld.hs +[1 of 2] Compiling AsmAArch64 ( AsmAArch64.hs, interpreted )+[2 of 2] Compiling HelloWorld ( HelloWorld.hs, interpreted )+Ok, two modules loaded.+*AsmAArch64> import HelloWorld+*AsmAArch64 HelloWorld> elf <- assemble helloWorld+*AsmAArch64 HelloWorld> bs <- serializeElf elf+*AsmAArch64 HelloWorld> BSL.writeFile "helloWorld.o" bs+*AsmAArch64 HelloWorld> Leaving GHCi. -[nix-shell:examples]$ aarch64-unknown-linux-gnu-gcc -nostdlib hello.o -o hello +[nix-shell:examples]$ aarch64-unknown-linux-gnu-gcc -nostdlib helloWorld.o -o helloWorld [nix-shell:examples]$ ```@@ -612,7 +572,7 @@ Попробуем запустить результат: ```-[nix-shell:examples]$ qemu-aarch64 hello+[nix-shell:examples]$ qemu-aarch64 helloWorld Hello World! [nix-shell:examples]$ @@ -622,85 +582,102 @@ ## Генерация исполняемых файлов -Так как сгенерированный код для распечатки "Hello World!" является позиционно-независимым-и не ссылается на другие секции, то его можно без изменений использовать для получения-исполняемого файла-([файл `MkExe.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.0/examples/MkExe.hs)):+Код из модуля+[`DummyLd`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/DummyLd.hs)+использует секцию `.text` объектного файла для того чтобы создать исполняемый файл.+Перемещение кода и разрешение символов не реализовано, поэтому такая процедура сработает только+с позиционно-независимым кодом, не ссылающимся на посторонние единицы трансляции,+например, с кодом, который описан в предыдущем разделе. -``` Haskell-module MkExe (mkExe) where+Функция `dummyLd` принимает объект типа `Elf`, ищет в нём секцию `.text`+(функцией [`elfFindSectionByName`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:elfFindSectionByName))+и заголовок ELF+(функцией [`elfFindHeader`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#v:elfFindHeader)).+Тип заголовка меняется на `ET_EXEC`, прописывается адрес, по которому будет располагаться+первая инструкция кода и формируется сегмент, в который помещается заголовок и содежимое `.text`: -import Control.Monad.Catch-import Control.Monad.State-import Data.Bits-import Data.Word-import Data.Singletons.Sigma+``` Haskell+data MachineConfig (a :: ElfClass)+ = MachineConfig+ { mcAddress :: WordXX a -- ^ Virtual address of the executable segment+ , mcAlign :: WordXX a -- ^ Required alignment of the executable segment+ -- in physical memory (depends on max page size)+ } -import Data.Elf-import Data.Elf.Constants-import Data.Elf.Headers+getMachineConfig :: (IsElfClass a, MonadThrow m) => ElfMachine -> m (MachineConfig a)+getMachineConfig EM_AARCH64 = return $ MachineConfig 0x400000 0x10000+getMachineConfig EM_X86_64 = return $ MachineConfig 0x400000 0x1000+getMachineConfig _ = $chainedError "could not find machine config for this arch" -import AsmAarch64+dummyLd' :: forall a m . (MonadThrow m, IsElfClass a) => ElfList a -> m (ElfList a)+dummyLd' (ElfList es) = do -addr :: Word64-addr = 0x400000+ txtSection <- elfFindSectionByName es ".text"+ txtSectionData <- case txtSection of+ ElfSection { esData = ElfSectionData textData } -> return textData+ _ -> $chainedError "could not find correct \".text\" section" -mkExe :: MonadCatch m => StateT CodeState m () -> m Elf-mkExe m = do- (txt, _) <- assemble 1 m- let- segment = ElfSegment- { epType = PT_LOAD- , epFlags = PF_X .|. PF_R- , epVirtAddr = addr- , epPhysAddr = addr- , epAddMemSize = 0- , epAlign = 0x10000- , epData =- [ ElfHeader- { ehData = ELFDATA2LSB- , ehOSABI = ELFOSABI_SYSV- , ehABIVersion = 0- , ehType = ET_EXEC- , ehMachine = EM_AARCH64- , ehEntry = addr + headerSize ELFCLASS64- , ehFlags = 0- }- , ElfRawData- { edData = txt+ header <- elfFindHeader es+ case header of+ ElfHeader { .. } -> do+ MachineConfig { .. } <- getMachineConfig ehMachine+ return $ ElfList+ [ ElfSegment+ { epType = PT_LOAD+ , epFlags = PF_X .|. PF_R+ , epVirtAddr = mcAddress+ , epPhysAddr = mcAddress+ , epAddMemSize = 0+ , epAlign = mcAlign+ , epData =+ [ ElfHeader+ { ehType = ET_EXEC+ , ehEntry = mcAddress + headerSize (fromSing $ sing @a)+ , ..+ }+ , ElfRawData+ { edData = txtSectionData+ }+ ] } , ElfSegmentTable ]- }- return $ SELFCLASS64 :&: ElfList [ segment ]+ _ -> $chainedError "could not find ELF header"++-- | @dummyLd@ places the content of ".text" section of the input ELF+-- into the loadable segment of the resulting ELF.+-- This could work if there are no relocations or references to external symbols.+dummyLd :: MonadThrow m => Elf -> m Elf+dummyLd (c :&: l) = (c :&:) <$> withElfClass c dummyLd' l ``` -Попробуем его сгенерировать и запустить:+Попробуем использовать этот код для получения исполняемого файла без участия линковщика GNU: ```-[nix-shell:examples]$ ghci -GHCi, version 8.10.4: https://www.haskell.org/ghc/ :? for help-Prelude> :l MkExe.hs HelloWorld.hs-[1 of 3] Compiling AsmAarch64 ( AsmAarch64.hs, interpreted )-[2 of 3] Compiling HelloWorld ( HelloWorld.hs, interpreted )-[3 of 3] Compiling MkExe ( MkExe.hs, interpreted )-Ok, three modules loaded.-*MkExe> import MkExe-*MkExe MkExe> import HelloWorld-*MkExe MkExe HelloWorld> elf <- mkExe helloWorld -*MkExe MkExe HelloWorld> import Data.Elf-*MkExe MkExe HelloWorld Data.Elf> bs <- serializeElf elf-*MkExe MkExe HelloWorld Data.Elf> import Data.ByteString.Lazy as BSL-*MkExe MkExe HelloWorld Data.Elf BSL> BSL.writeFile "hellox" bs-*MkExe MkExe HelloWorld Data.Elf BSL> +[nix-shell:examples]$ ghci+GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help+Prelude> :l DummyLd.hs+[1 of 1] Compiling DummyLd ( DummyLd.hs, interpreted )+Ok, one module loaded.+*DummyLd> import Data.ByteString.Lazy as BSL+*DummyLd BSL> i <- BSL.readFile "helloWorld.o"+*DummyLd BSL> elf <- parseElf i+*DummyLd BSL> elf' <- dummyLd elf+*DummyLd BSL> o <- serializeElf elf'+*DummyLd BSL> BSL.writeFile "helloWorld2" o+*DummyLd BSL> Leaving GHCi. -[nix-shell:examples]$ chmod +x hellox+[nix-shell:examples]$ chmod +x helloWorld2 -[nix-shell:examples]$ qemu-aarch64 hellox+[nix-shell:examples]$ qemu-aarch64 helloWorld2 Hello World! [nix-shell:examples]$ ``` Работает.++## Заключение++В статье даны примеры использования библиотеки `melf` и показано, как может быть определён встроенный в Хаскел DSL для генерации машинного кода.
examples/forwardLabelExe.dump.golden view
@@ -20,8 +20,8 @@ Data: 20 00 80 d2 61 01 00 10 62 00 80 d2 03 00 00 14 # ...a...b....... 01 01 00 70 82 00 80 d2 08 08 80 d2 01 00 00 d4 # ...p............ ...- 01 00 00 d4 00 00 00 00 6f 6b 0a 62 61 64 0a 00 # ........ok.bad..- total: 56+ d2 01 00 00 d4 00 00 00 00 6f 6b 0a 62 61 64 0a # .........ok.bad.+ total: 55 }- segment table }+segment table
examples/forwardLabelExe.layout.golden view
@@ -2,7 +2,7 @@ │┎ 0x00000000 H │┖ 0x0000003f │╓ 0x00000040 R-│╙ 0x00000077-│┎ 0x00000078 PT (1)-│┖ 0x000000af-└─ 0x000000af+│╙ 0x00000076+└─ 0x00000076+ ┎ 0x00000078 PT (1)+ ┖ 0x000000af
examples/helloWorldExe.dump.golden view
@@ -19,7 +19,7 @@ raw data { Data: 20 00 80 d2 e1 00 00 10 a2 01 80 d2 08 08 80 d2 # ............... 01 00 00 d4 00 00 80 d2 a8 0b 80 d2 01 00 00 d4 # ................- 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 0a 00 00 00 # Hello World!....+ 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 0a # Hello World!. }- segment table }+segment table
examples/helloWorldExe.layout.golden view
@@ -2,7 +2,7 @@ │┎ 0x00000000 H │┖ 0x0000003f │╓ 0x00000040 R-│╙ 0x0000006f-│┎ 0x00000070 PT (1)-│┖ 0x000000a7-└─ 0x000000a7+│╙ 0x0000006c+└─ 0x0000006c+ ┎ 0x00000070 PT (1)+ ┖ 0x000000a7
melf.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.18 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.34.5. -- -- see: https://github.com/sol/hpack name: melf-version: 1.0.1+version: 1.0.2 synopsis: An Elf parser description: Parser for ELF object format category: Data