packages feed

melf-1.0.2: examples/README_ru.md

---
title: Работа с файлами формата ELF из Хаскела
date: December 21, 2021
---

Работа с файлами формата ELF -- популярная тема на Хабре.
(["Введение в ELF-файлы в Linux: понимание и анализ"](https://habr.com/ru/post/480642/),
["Минимизация файла ELF – попробуем в 2021?"](https://habr.com/ru/company/ruvds/blog/583576/) и т. д.)

Существуют библиотеки для Хаскела для работы с этими файлами: `elf`
([Hackage](https://hackage.haskell.org/package/elf)) и `data-elf`
([Hackage](https://hackage.haskell.org/package/data-elf)).
Эти библиотеки работают только с заголовками и элементами таблиц и не дают возможности
сгенерировать объектный файл.

Библиотека `melf`
([GitHub](https://github.com/aleksey-makarov/melf), [Hackage](https://hackage.haskell.org/package/melf))
даёт возможность полностью разобрать файл ELF и сгенерировать такой файл по несложной структуре данных.
Ниже даются примеры её использования.

## Внутреннее устройство ELF


В файле формата ELF последовательно размещены заголовок файла,
секции, сегменты, таблица секций, таблица сегментов.
Сегменты в свою очередь скомпанованы из таких же элементов.
Порядок этих элементов произвольный, за исключением того, что заголовок файла всегда размещается в начале файла,
а таблиц секций и таблиц сегментов может быть не более одной.
Каждый такой участок выровнен в файле, например, сегменты обычно выравниваются на размер страницы,
а секции с данными -- на размер слова.

В заголовке описано, где располагаются таблица секций и таблица сегментов,
которые в свою очередь описывают, где располагаются секции и сегменты.

Сегменты указывают, что нужно поместить в память при загрузки программы,
а секции я бы определил как неделимые результаты работы компилятора.
В секциях размещены исполняемый код, таблицы символов, инициализированные данные.
Линковщик объединяет секции из различных единиц трансляции в сегменты.

Вполне валидным может быть файл, где сегмент содержит данные, не размеченные как какая-либо секция.

## Базовый уровень

В модуле
[`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.1/docs/Data-Elf-Headers.html#t:ElfClass)

``` Haskell
data ElfClass
    = ELFCLASS32 -- ^ 32-bit ELF format
    | ELFCLASS64 -- ^ 64-bit ELF format
    deriving (Eq, Show)
```

Некоторые поля заголовка и элементов таблиц секций и сегментов имеют разную ширину в битах, зависящую от
`ElfClass`, поэтому нужен тип
[`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`.
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
```

Заголовок файла ELF представлен с помощью типа
[`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
        }
```

Для однообразной работы с форматами с разной шириной слова определён тип
[`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`
type Header = Sigma ElfClass (TyCon1 HeaderXX)
```

`Header` это пара, первый элемент которой -- объект типа `ElfClass`, определяющий ширину слова,
второй -- `HeaderXX`, параметризованный первым элементом (Σ-тип из языков с зависимыми типами).
Для симуляции Σ-типов использована библиотека `singletons`
([Hackage](https://hackage.haskell.org/package/singletons),
 ["Introduction to singletons"](https://blog.jle.im/entry/introduction-to-singletons-1.html)).

`Header` является экземпляром класса
[`Binary`](https://hackage.haskell.org/package/binary-0.10.0.0/docs/Data-Binary.html#t:Binary).
Таким образом, имея ленивую строку байт, содержащую достаточно длинный начальный отрезок файла ELF,
можно получить заголовок этого файла, например, следующей функцией:

``` 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
```

Здесь
[`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.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
-- context with an implicit ElfClass available.
withElfClass :: Sing c -> (IsElfClass c => a) -> a
withElfClass SELFCLASS64 x = x
withElfClass SELFCLASS32 x = x
```

В `Data.Elf.Headers` определены также типы
[`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.1/docs/Data-Elf.html)
реализованы полные разбор и сериализация файлов формата ELF.
Чтобы разобрать такой файл читаются заголовок ELF, таблицa секций и таблица сегментов и
на основании этой информации создаётся список элементов типа
[`ElfXX`](https://hackage.haskell.org/package/melf-1.0.1/docs/Data-Elf.html#t:ElfXX),
отображающий рекурсивную
структуру файла 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`.
-- 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
        }

```

Не каждый объект такого типа может быть сериализован.

  * В конструкторе `ElfSection`
    остался номер секции.  Он нужен, так как таблица символов и некоторые другие структуры
    ссылаются на секци по их номерам.  Поэтому при построении объекта такого типа нужно убедиться,
    что секции пронумерованы корректно, т. е. последовательными целыми числами от 1 до количества секций.
    Секция с номером 0 всегда пустая, она добавляется автоматически.

  * В структуре должен быть единственный `ElfHeader`, он должен быть самым первым непустым
    узлом в дереве.

  * Если есть хотя бы один узел `ElfSection`, то должен присутсвовать в точности один узел `ElfSectionTable`
    и в точности одна секция, поле `esData` которой равно `ElfSectionDataStringTable` (таблица строк для имён секций).

  * Если есть хотя бы один узел `ElfSegment`, то должен присутсвовать в точности один узел `ElfSegmentTable`.

Корректно сформированный объект можно сериализовать с помощью функции
[`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.1/docs/Data-Elf.html#v:parseElf):

``` Haskell
serializeElf :: MonadThrow m => Elf -> m ByteString
parseElf :: MonadCatch m => ByteString -> m Elf
```

Экземпляр класса `Binary` для `ELF` не определён, так как
[`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.2/examples/AsmAArch64.hs)).
Сгенерированный код использует системные вызовы чтобы вывести на стандартный вывод "Hello World!" и завершить приложение.
Идея позаимствована из вдохновляющей статьи Стивена Дила "От монад к машинному коду"
([Stephen Diehl "Monads to Machine Code"](https://www.stephendiehl.com/posts/monads_machine_code.html)).
Так же как в статье, используется монада состояния, в нашем случае `CodeState`.

``` Haskell
data CodeState = CodeState
    { offsetInPool    :: CodeOffset
    , poolReversed    :: [Builder]
    , codeReversed    :: [InstructionGen]
    , symbolsRefersed :: [(String, Label)]
    }
```

`CodeState` содержит размер массива литералов, сам массив литералов,
массив машинных кодов и массив символов.

Массив литералов (literal pools) это участок секции в которой расположен
исполняемый код, используемый для хранения константных данных.
К таким данным легко обращаться с помощью команд,
вычисляющих адрес данных с использованием счётчика команд.

Для создания меток и ссылок на данные в массиве литералов введён тип `Label`

``` Haskell
newtype CodeOffset  = CodeOffset  { getCodeOffset  :: Int64 }
    deriving (Eq, Show, Ord, Num, Enum, Real, Integral,
    Bits, FiniteBits)

data Label = CodeRef CodeOffset
           | PoolRef CodeOffset
```

Конструктор `CodeRef` используется для ссылки на код (для создания меток):

``` Haskell
label :: MonadState CodeState m => m Label
label = gets (CodeRef . (* instructionSize)
                      . fromIntegral
                      . P.length
                      . codeReversed)
```

Конструктор `PoolRef` хранит смещение данных от начала массива литералов.
Он используется для создания `CodeOffset` в функции `emitPool` (см. ниже).

В массиве машинных кодов хранятся функции для генерации машинного кода
из смещения команды от начала секции и смещения массива литералов (которое будет
известно только после обработки всех ассемблерных команд, так как массив литералов
располагается после кода):

```Haskell
type InstructionGen = CodeOffset ->
                      CodeOffset -> Either String Instruction
```

Для добавления функций в массив машинных кодов используется функция `emit'`:

```Haskell
emit' :: MonadState CodeState m => InstructionGen -> m ()
emit' g = modify f where
    f CodeState {..} = CodeState { codeReversed = g : codeReversed
                                 , ..
                                 }

emit :: MonadState CodeState m => Instruction -> m ()
emit i = emit' $ \ _ _ -> Right i
```

Каждая встретившаяся ассемблерная команда добавляет в этот массив очередной машинный код,
например:

``` Haskell
-- | C6.2.317 SVC
svc :: MonadState CodeState m => Word16 -> m ()
svc imm = emit $ 0xd4000001 .|. (fromIntegral imm `shift` 5)
```

Многие команды архитектуры AArch64 могут работать с регистрами
как с 64-битными или как с 32-битными значениями.
Для указания разрядности регистров для них используются разные имена:
`x0`, `x1`... -- для 64-битных, `w0`, `w1`... -- для 32-битных.
Регистры определены с помощью [фантомного](https://wiki.haskell.org/Phantom_type) типа:


``` Haskell
data RegisterWidth = X | W

type Register :: RegisterWidth -> Type
newtype Register c = R Word32

x0, x1 :: Register 'X
x0 = R 0
x1 = R 1

w0, w1 :: Register 'W
w0 = R 0
w1 = R 1

-- | C6.2.187 MOV (wide immediate)
mov :: (MonadState CodeState m, SingI w) =>
                              Register w ->
                                  Word16 -> m ()
```

В системе команд AArch64 есть несколько вариантов команды `mov`.
Реализована только команда с непосредственным широким аргументом (wide immediate).

Команда `adr` работает с регистрами только как с 64-битными значениями:

``` Haskell
-- | C6.2.10 ADR
adr :: MonadState CodeState m =>
                  Register 'X ->
                  Label -> m ()
```

Для добавления данных в массив литералов используется функция `emitPool`:

``` Haskell
emitPool :: MonadState CodeState m =>
                              Word ->
                        ByteString -> m Label
```

Здесь первый аргумент -- необходимое выравнивание, второй -- данные, которые нужно
разместить в массиве.  Функция вычисляет, сколько нужно добавить байт чтобы выравнять
данные, заносит соответствующую нулевую последовательность байт в массив `poolReversed`,
добавляет в этот же массив данные и корректирует `offsetInPool`.

С помощью этой функции можно, например, реализовать аналог ассемблерной директивы `.ascii`:

``` Haskell
ascii :: MonadState CodeState m => String -> m Label
ascii s = emitPool 1 $ BSLC.pack s
```

Символы создаются из меток:

``` Haskell
exportSymbol :: MonadState CodeState m => String -> Label -> m ()
exportSymbol s r = modify f where
    f (CodeState {..}) = CodeState { symbolsRefersed = (s, r) : symbolsRefersed
                                   , ..
                                   }
```

Используя таким образом определённые примитивы можно написать код
для вывода "Hello World!" на встроенном в Хаскел DSL
([файл `HelloWorld.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/HelloWorld.hs)):

``` Haskell
msg :: String
msg = "Hello World!\n"

-- | syscalls
sysExit, sysWrite :: Word16
sysWrite = 64
sysExit = 93

helloWorld :: MonadCatch m => StateT CodeState m ()
helloWorld = do

    start <- label
    exportSymbol "_start" start
    mov x0 1
    helloString <- ascii msg
    adr x1 helloString
    mov x2 $ fromIntegral $ P.length msg
    mov x8 sysWrite
    svc 0

    mov x0 0
    mov x8 sysExit
    svc 0
```
Если нужно сослаться на метку, сформированную ниже по коду, нужно работать в монаде `MonadFix`
и использовать ключевое слово `mdo` вместо `do`
(см. файл [`ForwardLabel.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/ForwardLabel.hs)).

## Генерация объектных файлов

Функция `assemble`
(см. [`AsmAArch64.hs`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/AsmAArch64.hs))
транслирует код на встроенном в Хаскел ассемблере в машинные коды и возвращает объект типа `Elf`:

``` Haskell
assemble :: MonadCatch m => StateT CodeState m () -> m Elf
```

Она запускает переданную в качестве аргумента монаду `State`, представляющую ассемблерный код.
Конечное состояние этой монады содержит всю информацию о содержимом секции, которая будет содержать код
(секция с именем `.text`), и таблицы символов,
a этого достаточно чтобы сгенерировать объектный файл.
На содержимое секции `.text` ссылается имя `txt`,
на содержимое таблицы символов -- имя `symbolTableData`,
на содержимое таблицы строк, связанной с таблицей символов -- имя `stringTableData`:

``` 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
        ]
```

Здесь имена с суффиксом `SecN` (`textSecN`, `shstrtabSecN`, `symtabSecN`, `strtabSecN`) -- предопределённые
номера секций, удовлетворяющие сформулированным выше условиям.

Для простоты не реализовано обращение ко внешним символам и размещение данных в
отдельных секциях.
Всё это требует реализации таблиц перемещений, с другой стороны, сгенерированный код
получается позиционно-независимым.

Сгенерируем с помощью этого модуля объектный файл и попробуем его слинковать:

```
[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]$ 
```

Как видим, линковщик благополучно принял сгенерированный объектный файл.
Попробуем запустить результат:

```
[nix-shell:examples]$ qemu-aarch64 helloWorld
Hello World!

[nix-shell:examples]$ 
```

Работает.

## Генерация исполняемых файлов

Код из модуля
[`DummyLd`](https://github.com/aleksey-makarov/melf/blob/v1.0.2/examples/DummyLd.hs)
использует секцию `.text` объектного файла для того чтобы создать исполняемый файл.
Перемещение кода и разрешение символов не реализовано, поэтому такая процедура сработает только
с позиционно-независимым кодом, не ссылающимся на посторонние единицы трансляции,
например, с кодом, который описан в предыдущем разделе.

Функция `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`:

``` 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
```

Попробуем использовать этот код для получения исполняемого файла без участия линковщика GNU:

```
[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]$ 
```

Работает.

## Заключение

В статье даны примеры использования библиотеки `melf` и показано, как может быть определён встроенный в Хаскел DSL для генерации машинного кода.