evocation/elf.e
2026-09-24 17:47:58 +03:00

177 lines
6.8 KiB
Text

~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ ~~ Executable file format ~~
~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~
~ Before we do anything specific to the actual program we're building, we
~ do a lot of ELF-specific stuff to ensure that our output is in a format
~ Linux knows how to run.
~
~ This relies on the label facility defined in labels.e. Make sure to load
~ that first.
~
~ This file makes use of magic comments for the hex transform, described in
~ detail in transform.e, but its use is trivial and doesn't require accounting
~ conventions.
~ ~~~~~~~~~~~~~~~~~~~~~
~ ~~ ELF file header ~~
~ ~~~~~~~~~~~~~~~~~~~~~
~
~ First, we output ELF's top-level file header. This header describes the
~ entire file. An ELF always has exactly one of this header, which is always
~ at the start of the file.
~
~ The program we're building should call this word as the first output it
~ generates.
~
~ The most interesting thing here is the entry pointer.
~
~ See constants-*.e for the elf-osabi definitions.
: elf-file-header
~ : blank-line
~ : ELF file header
~ : indent
~ : * denotes mandatory fields according to breadbox
current-offset 3unroll
~ : 1 suppress
0x7f pack8 s" ELF" pack-raw-string ~ : -4 *magic number
2 pack8 ~ : -1 64-bit
1 pack8 ~ : -1 little-endian
1 pack8 ~ : -1 ELF header format v1
elf-osabi pack8
0 pack64 ~ : -8 (padding)
~ : blank-line
2 pack16 ~ : -2 *executable
0x3e pack16 ~ : -2 *Intel x86-64
1 pack32 ~ : -4 ELF format version
~ : blank-line
L@' cold-start L@' origin + pack64 ~ : -8 *entry point
~ This includes the origin, intentionally.
L@' elf-program-header pack64 ~ : -8 *program header offset
~ We place the program header immediately after the ELF header. This
~ offset is from the start of the file.
0 pack64 ~ : -8 section header offset
0 pack32 ~ : -4 processor flags
~ : blank-line
L@' elf-header-size pack16 ~ : -2 ELF header size
L@' elf-program-header-size pack16 ~ : -2 *program header entry size
1 pack16 ~ : -2 *number of program header entries
0 pack16 ~ : -2 section header entry size
0 pack16 ~ : -2 number of section header entries
0 pack16 ~ : -2 section name string table index
~ Though hardcoding the size of this header would work fine, it's easier
~ to use the label system to keep track of its size. The only place this is
~ actually referenced is right here in the header.
current-offset 4 roll - L!' elf-header-size
L' elf-header-size set-label-non-offset
~ : deindent
;
~ ~~~~~~~~~~~~~~~~~~~~~~~~
~ ~~ ELF program header ~~
~ ~~~~~~~~~~~~~~~~~~~~~~~~
~
~ Second, we output ELF's program header, which lists the memory regions
~ ("segments") we want to have and where we want them to come from. There may
~ be any number of these entries, one per segment, , and they may be anywhere
~ in the file as long as they're consecutive.
~
~ We list just a single region, which is the entire contents of the ELF file
~ from disk, and we put the program header immediately after the file header.
~ The program we're building should call this word as the second output it
~ generates.
~
~ It would be more typical to use this header to ask the loader to give us
~ separate code and data segments, and perhaps a stack or heap, but this keeps
~ things simple, and we can create those things for ourselves later.
~
~ We do have a little stack space available, though we don't explicitly
~ request any; the kernel allocates it for us as part of exec() so that it can
~ pass us argc and argv (which we ignore). That stack space will be at a
~ random address, different every time, because of ASLR; that's a neat
~ security feature, so we leave it as-is. Note that ASLR doesn't happen when
~ you run under gdb, so if you aren't seeing it, that's probably why.
~ ~~
: elf-program-header
~ : blank-line
~ : ELF program header
~ : indent
~ * denotes mandatory fields according to breadbox
current-offset L!' elf-program-header
current-offset 3unroll
1 pack32 ~ : -4 *"loadable" segment type
0x05 pack32 ~ : -4 *read+execute permission
0 pack64 ~ : -8 *offset in file
L@' origin pack64 ~ : -8 *virtual address
~ required, but can be anything, subject to alignment
0 pack64 ~ : -8 physical address (ignored)
~ : blank-line
L@' total-size pack64 ~ : -8 *size in file
L@' total-size pack64 ~ : -8 *size in memory
~ : blank-line
0 pack64 ~ : -8 segment alignment
~ for relocation, but this doesn't apply to us
~ As with the file header, we use the label system to keep track of the
~ program header's size.
current-offset 4 roll - L!' elf-program-header-size
L' elf-program-header-size set-label-non-offset
~ : deindent
;
~ This alternate version of the program header isn't used by evoke.e, but
~ may be useful for other programs, such as hex.e. Its only difference from
~ the base version is that it adds the write permission.
: elf-program-header-writable
~ : blank-line
~ : ELF program header
~ : indent
~ * denotes mandatory fields according to breadbox
current-offset L!' elf-program-header
current-offset 3unroll
1 pack32 ~ : -4 *"loadable" segment type
0x07 pack32 ~ : -4 *read+write+execute permission
0 pack64 ~ : -8 *offset in file
L@' origin pack64 ~ : -8 *virtual address
~ required, but can be anything, subject to alignment
0 pack64 ~ : -8 physical address (ignored)
~ : blank-line
L@' total-size pack64 ~ : -8 *size in file
L@' total-size pack64 ~ : -8 *size in memory
~ : blank-line
0 pack64 ~ : -8 segment alignment
~ for relocation, but this doesn't apply to us
~ As with the file header, we use the label system to keep track of the
~ program header's size.
current-offset 4 roll - L!' elf-program-header-size
L' elf-program-header-size set-label-non-offset
~ : deindent
;
~ ~~~~~~~~~~~~~~~~
~ ~~ That's it! ~~
~ ~~~~~~~~~~~~~~~~
~
~ ELF is a simple format, really. Now you can output your own machine code
~ that you generate however you want; make sure to define the labels "origin"
~ and "cold-start". Origin will control the address the code loads at;
~ cold-start will be the first thing that runs. The origin is arbitrary, but
~ can't be zero.