Search documentation

Search starts with current English documentation · ASPL 0.7.0

Enter a complete name such as read_line, or words about a topic.

    العربية
    Source files

    Open one UTF-8 .aspl or .لبات file, up to 1 MiB, in a new draft. It stays in this browser and does not run automatically.

    ELF packaging without an external linker

    ASPL MVP · Historical MVP scope

    Archived ASPL MVP specification. Original English source only; no Arabic counterpart is available. These historical rules are not a complete ASPL 0.6.0 reference.

    On this page

    ELF packaging without an external linker

    Recommendation

    Use a prelinked x86-64 PIE VM template with a dedicated final, read-only PT_LOAD segment for the ASPL payload. Build and link that template once when producing an ASPL release. Embed the normalized template and a small manifest inside the aspl executable. An end-user build then does four things in-process:

    1. Copy the template to a temporary output file.
    2. Write a versioned payload envelope and the program bytecode into the final segment.
    3. Patch that segment's p_filesz and p_memsz to the exact payload size.
    4. Make the completed file executable and publish it under the requested name.

    This meets the narrow promise that aspl build does not invoke a compiler, assembler, or static linker. The result contains the native VM and the program bytecode, so it does not need ASPL installed to run. It still depends on the Linux ELF loader, the glibc dynamic loader named by the template, glibc itself, and any shared libraries the program requests. Calling the file "standalone" must not imply static linkage.

    The arbitrary C-library feature should use dlopen and dlsym from the prelinked VM. It should not edit DT_NEEDED, relocation tables, symbol tables, or PLT entries for every program. Editing those structures would turn the MVP emitter into a linker.

    Why the mapped payload works

    ELF execution follows the program header table. The generic ABI says each PT_LOAD entry maps p_filesz bytes from the file, requires p_filesz <= p_memsz, and zero-fills any remaining memory up to p_memsz. It also requires load segments to appear in ascending virtual-address order. File offsets and virtual addresses must be congruent modulo the segment alignment. These are the rules the template and emitter must preserve. ELF gABI, program loading

    The release build should place a small payload-envelope placeholder in its own final PT_LOAD segment. A linker-defined symbol such as __aspl_payload_start lets the C VM refer to the start directly. The segment must have the largest virtual address, have read permission only, and end the executable's meaningful file image. Since the segment already exists, the emitter only extends it. It does not add a program header or relocate native code.

    The final segment should contain:

    ASPL payload envelope
      magic
      envelope version
      bytecode format version
      bytecode length
      VM compatibility identifier
      integrity value
    bytecode bytes

    Set p_filesz and p_memsz to the envelope size plus bytecode size. The VM needs no writable bytes in this segment, so PF_R is enough. A writable and executable payload segment would be needless and conflicts with glibc's hardening advice to avoid LOAD segments that are writable and executable at the same time. The template should also retain a non-executable GNU_STACK header. glibc dynamic-linker hardening

    Section headers are not needed for execution. Linux documents that an ELF executable may have a program header table, a section header table, or both. It also defines zero e_shoff and e_shnum values for a file without a section table. The release process can therefore normalize the runtime template so its file ends after the payload-envelope placeholder and it has no trailing section table. Linux elf(5)

    The emitter must use the template's recorded p_align, then pad the payload file offset so it remains congruent with p_vaddr. It should not assume every linker uses the same page alignment. The ASPL release owns this template, so the compiler can store the exact program-header index, payload offset, virtual address, original size, and a hash of the immutable template bytes in an internal manifest.

    Process startup

    Keep the template's startup path unchanged. A dynamically linked ELF file has one PT_INTERP entry. During execve, Linux runs the interpreter named there, which loads the required shared objects before control reaches the program. Linux execve(2) The ELF ABI describes the same handoff and says the dynamic linker maps dependencies, performs relocations, and transfers control to the program. ELF gABI, dynamic linking

    The template should be an ET_DYN position-independent executable so Linux can apply address-space randomization. Native startup reaches the template's usual C entry path and then main. The VM validates the ASPL envelope at __aspl_payload_start, checks the bytecode version and length, and starts the bytecode entry function. No child process, temporary bytecode file, or self-reopen is required.

    The fixed PT_INTERP path is part of the platform contract. So are the glibc symbol versions and DT_NEEDED entries recorded when the VM template is linked. The release specification must name a minimum glibc baseline, build the template against that baseline, and test it on a clean system at that version. Copying the template does not make a binary built against a newer glibc run on an older one.

    Dynamic C dependencies

    The template's link-time dependencies stay fixed. They should contain only what the VM itself needs. Library declarations stored in ASPL bytecode can name an ABI-compatible shared object and symbols. At VM startup or first use, the runtime calls dlopen, obtains symbols with dlsym, and invokes them through the separately specified x86-64 C ABI bridge.

    dlopen accepts an absolute or relative path when the name contains a slash. Otherwise, the glibc loader searches its configured paths, cache, and default library directories. It also loads the selected object's own dependencies recursively. Linux dlopen(3) The MVP should use RTLD_NOW | RTLD_LOCAL: RTLD_NOW reports unresolved dependencies before execution continues, while RTLD_LOCAL avoids exporting a library's symbols into later loads unless ASPL gains an explicit reason to do that.

    This gives "link arbitrary C libraries" a precise boundary. It means load compatible Linux shared objects present on the target system and call symbols covered by ASPL's supported ABI types. It does not mean static archives, C header parsing, link-time symbol resolution, or embedding each library into the output. The system dynamic loader remains a runtime dependency. Its search rules for DT_NEEDED, DT_RUNPATH, the loader cache, and default paths still apply. Linux ld.so(8)

    Feasible alternatives

    Mechanism Bytecode placement Runtime constraint Dynamic dependencies Integrity cost Verdict
    Dedicated final PT_LOAD segment Mapped read-only beside the VM Ordinary ELF and glibc startup Fixed VM dependencies plus runtime dlopen Bounded header patching and payload validation Use for the MVP
    Fixed-size array in the template Preallocated in .rodata Ordinary startup and direct symbol access Same as the recommended design No ELF patch, but unused capacity ships in every file Useful only for an early spike
    Unmapped file overlay Bytes appended after all ELF segments VM must reopen its own executable and read the trailer Same as the recommended design Simple ELF output, but a second file parser and I/O path Keep as a fallback, not the release format
    Rebuild dynamic ELF metadata New sections, segments, symbols, relocations, and dependencies Ordinary startup if every link rule is correct Can encode per-program DT_NEEDED entries Very large validation burden Reject for the MVP

    The overlay is technically workable on Linux. The kernel's ELF loader maps the ranges described by PT_LOAD; its upstream implementation iterates those headers and maps p_filesz bytes for each one. Linux fs/binfmt_elf.c A VM can open /proc/self/exe, which Linux exposes as a link to the executed file, then locate a trailer from the end of the file. Linux proc_pid_exe(5)

    That simplicity is deceptive. The overlay depends on procfs being mounted and accessible, performs file I/O during startup, and has special behavior after unlinking or when access checks reject /proc/self/exe. argv[0] is not a sound substitute because the caller controls it. AT_EXECFN only records the pathname passed to execution, so it does not remove pathname and replacement races. Linux getauxval(3) A mapped segment avoids all of those conditions.

    The fixed-size-array option is even simpler to implement, but it forces an arbitrary maximum bytecode size and pads every executable to that maximum. It is acceptable for a prototype that proves the template-copy idea. It is a poor public format.

    Emitter and integrity contract

    The emitter should parse and write ELF fields as explicit little-endian integers. It should validate the template before writing anything:

    • ELF magic, ELFCLASS64, little-endian encoding, EM_X86_64, and expected ET_DYN type.
    • Exact program-header entry size and count.
    • The manifest's payload header index, offset, virtual address, flags, and alignment.
    • p_filesz <= p_memsz for every load segment, ascending load addresses, non-overlap after page rounding, and overflow-free offset and size arithmetic.
    • A hash of every immutable template byte.

    The compiler should reject bytecode lengths that overflow ELF fields, the process address space, or an implementation limit. It should write the output with no execute bits set, validate the finished file, then add the requested execute permissions. Publishing through a same-directory temporary file and rename prevents another process from observing a half-written replacement because Linux replaces an existing destination atomically. Linux rename(2)

    The payload envelope needs a corruption check over the bytecode and all fields that control parsing. A cryptographic digest is preferable if ASPL already contains a suitable implementation. A simpler checksum detects accidental truncation but does not establish authenticity. Neither choice protects against an attacker who can rewrite both payload and checksum. Release signatures and download checksums are a separate distribution concern.

    Do not treat .note.gnu.build-id as an output checksum. GNU ld says a build ID identifies the originally linked file, is not intended as a content checksum, and remains unchanged if another tool later modifies the file. GNU ld build-ID documentation Retaining the template build ID is useful for identifying native VM code in crash reports. Add a separate ASPL content identifier for the payload, or omit the GNU build ID if downstream tooling would mistake it for the identity of the complete generated program.

    Reproducible output is straightforward. For a fixed ASPL release, identical bytecode and build options should produce identical bytes. The envelope must not contain the current time, random padding, absolute source paths, or host-specific data unless a user explicitly requests them.

    Consequences for the MVP specification

    The native packaging decision can now state the following constraints:

    • aspl build copies and patches a release-owned VM template in-process. It never invokes a compiler, assembler, or static linker.
    • The generated ELF is x86-64, little-endian, PIE, glibc-linked, and contains a final read-only load segment with a versioned bytecode envelope.
    • The template fixes the ELF interpreter, minimum glibc baseline, and native VM dependencies for that ASPL release.
    • Program-specific C libraries are loaded by name at runtime through dlopen and resolved through dlsym; they are not added as ELF link-time dependencies.
    • The VM validates payload bounds, version, compatibility, and corruption metadata before interpreting any instruction.
    • The release build may use a C compiler and linker to create the template. The end-user build path may not.

    This design leaves one deliberate maintenance cost. Each supported native platform needs its own tested runtime template and manifest. That is acceptable for the Linux x86-64 glibc MVP and scales more honestly than hiding a partial linker inside aspl build.

    Browse pages