about summary refs log tree commit diff
path: root/compilation/elf.e
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-09-28 18:07:52 -0700
committerIrene Knapp <ireneista@irenes.space>2026-09-28 19:18:54 -0700
commit77e8ce989010fbd94080e780fb0b5cb6c10434e8 (patch)
treec1eac8abaa388d37fac957244ebe7b2c76435d78 /compilation/elf.e
parent54b8077d3296ae994c2943d0d39e84a4d4047416 (diff)
move all the source files into subdirectories
and rename one, linux-dynamic.e to language/dynamic/linux.e.

also make sure all references in comments and documentation refer to the new locations and names.

this directory structure reflects code organization which had existed in Irenes' head, but was likely not obvious to other readers. now hopefully it is more discoverable.

Force-Push: yes
Change-Id: Icfef3eae812a50fe20202191999e68e1f7db445a
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.
+