about summary refs log tree commit diff
path: root/compilation/elf.e
diff options
context:
space:
mode:
Diffstat (limited to 'compilation/elf.e')
-rw-r--r--compilation/elf.e179
1 files changed, 179 insertions, 0 deletions
diff --git a/compilation/elf.e b/compilation/elf.e
new file mode 100644
index 0000000..f39a1a0
--- /dev/null
+++ b/compilation/elf.e
@@ -0,0 +1,179 @@
+~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~ ~~ 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 compilation/labels.e. Make
+~ sure to load that first.
+~
+~   This file makes use of magic comments for the hex transform, described in
+~ detail in compilation/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.
+~
+~   It's also worth noticing that we set the ABI version to 3. An ABI version
+~ of 0 (for the System V ABI) would work fine too, but 3 allows FreeBSD's
+~ Linux compatibility features to kick in.
+
+: 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
+  3 pack8                             ~ : -1 Linux ABI
+  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.
+