diff --git a/.github/tests/debug-image-builder.cpp b/.github/tests/debug-image-builder.cpp index d99610e..fdcca11 100644 --- a/.github/tests/debug-image-builder.cpp +++ b/.github/tests/debug-image-builder.cpp @@ -209,6 +209,43 @@ std::vector cases() { ++f.relocation().offset;exercise(f,false); } }); + // An i386 image (0.6.3): ELF32 sections, symbols and REL link relocations, + // whose addend is the word the link resolved; the object holds the addend. + out.emplace_back("i386_rel_word_becomes_its_addend",[]{ + Bytes b(1040,0); + auto& h=*reinterpret_cast(b.data()); + auto* sh=reinterpret_cast(b.data()+64); + h.ident[0]=0x7f;h.ident[1]='E';h.ident[2]='L';h.ident[3]='F';h.ident[4]=1;h.ident[5]=1;h.ident[6]=1; + h.type=e::et_dyn;h.machine=e::em_386;h.version=1;h.ehsize=52;h.shoff=64;h.shentsize=40;h.shnum=8;h.shstrndx=1; + std::string names(1,'\0'); + auto name=[&](const char* s){unsigned at=names.size();names+=s;names+='\0';return at;}; + sh[1]={name(".shstrtab"),3,0,0,576,0,0,0,1,0}; + sh[2]={name(".text"),1,2,0x1000,768,32,0,0,16,0}; + sh[3]={name(".strtab"),3,0,0,800,5,0,0,1,0}; + sh[4]={name(".symtab"),2,0,0,832,32,3,1,4,16}; + sh[5]={name(".debug_info"),1,0,0,896,8,0,0,4,0}; + sh[6]={name(".rel.debug_info"),9,0,0,1024,16,4,5,4,8}; + sh[7]={name(".bss"),8,2,0x2000,0,16,0,0,4,0}; + sh[1].size=names.size();std::memcpy(b.data()+576,names.data(),names.size()); + std::memcpy(b.data()+800,"\0foo",5); + const e::sym32 foo{1,0x1004,4,18,0,2};std::memcpy(b.data()+848,&foo,16); + const e::u32 absolute=0x1004+6, relative=0x1004-4-4; // S+A with A=6; S+A-P with A=-4, P=4 + std::memcpy(b.data()+896,&absolute,4);std::memcpy(b.data()+900,&relative,4); + const e::rel32 r[2]={{0,(1u<<8)|1},{4,(1u<<8)|2}};std::memcpy(b.data()+1024,r,16); + const Bytes before=b; + Heap heap;auto host=heap.host();void* out=nullptr;okld_size size=0;okld_why why{}; + require(okld::debugger_image(b.data(),b.size(),0x40000000,&host,&out,&size,&why),why.text); + require(b==before,"builder mutated source image"); + const auto* p=static_cast(out); + const auto* oh=reinterpret_cast(p+64); + const auto* os=reinterpret_cast(p+848); + e::u32 a0=0,a1=0;std::memcpy(&a0,p+896,4);std::memcpy(&a1,p+900,4); + require(reinterpret_cast(p)->type==1,"ET_REL header"); + require(oh[2].addr==0x40001000 && os->value==4 && os->shndx==2,"placed section, relative symbol"); + require(a0==6 && a1==static_cast(-4),"the words hold their addends"); + host.free(out,size,host.ctx); + require(heap.live.empty() && !heap.bad_release,"allocation leak or incorrect free size"); + }); out.emplace_back("none_relocation_has_no_target_write",[]{Fixture f;f.relocation().offset=~e::u64{0};f.relocation().info=0xffffffff00000000ull;exercise(f,true);}); out.emplace_back("repeated_build_release",[]{for(int i=0;i<100;++i)exercise(Fixture(true),true);}); return out; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 803c300..62527da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,8 @@ env: XLINGS_NON_INTERACTIVE: '1' # 2026.11.10.1: openkal-run binds the interfaces its implementation provides # (MCPP_KERNEL_ABI_INTERFACES). - MCPP_VERSION: 2026.11.10.1 + # 0.6.3: 2026.11.13.1, the floor of openkal-run (the core runner, by `cfg(interface)'). + MCPP_VERSION: 2026.11.13.1 LLVM: llvm@23.1.3 jobs: @@ -76,8 +77,10 @@ jobs: for arch in x86_64 aarch64; do mkdir -p "out/$arch" bash examples/build-image.sh "$arch" "out/$arch/hello.oke" examples/hello/app.c - # The declarations are compiled from the public pinned specification. - spec=$(find "$HOME/.mcpp" -path '*openkal*0.25.0*/include/openkal/process.h' -type f | head -1) + # The declarations are compiled from the public specification the + # tools resolved: any patch of its minor version (openkal clause 8), + # which is 0.25.1 since that patch was published. + spec=$(find "$HOME/.mcpp" -path '*openkal*0.25.*/include/openkal/process.h' -type f | sort -V | tail -1) test -n "$spec" CPATH="${spec%/openkal/process.h}" bash examples/build-image.sh "$arch" "out/$arch/process-unit.oke" examples/process-unit/app.c bash examples/build-image.sh "$arch" "out/$arch/liboktwice.okso" examples/needed/lib.c @@ -127,6 +130,16 @@ jobs: if "$OKLD" check changed.oke 2> changed-placement.txt; then echo "::error::a changed stored segment passed okld check"; exit 1; fi grep -q "SPEC 12.4" changed-placement.txt done + # i386 (0.6.3): read, placed and checked here; a runner of i386 runs + # them where it is built (openkal-platform's row i686-openkal). + mkdir -p out/i686 + bash examples/build-image.sh i686 out/i686/hello.oke examples/hello/app.c + bash examples/build-image.sh i686 out/i686/liboktwice.okso examples/needed/lib.c + bash examples/build-image.sh i686 out/i686/needed.oke examples/needed/app.c -- out/i686/liboktwice.okso + "$OKLD" inspect out/i686/hello.oke | grep -q '^architecture *i686$' \ + || { echo "::error::an i386 image is not named i686"; exit 1; } + "$OKLD" bundle i386.okb --version 0.1.0 out/x86_64/hello.oke out/i686/hello.oke + "$OKLD" bundle check i386.okb | grep -q 'i686 (1 image(s))' "$OKLD" keygen "$RUNNER_TEMP/seed" > out/key.hex for arch in x86_64 aarch64; do "$OKLD" sign "out/$arch/signed.oke" "$RUNNER_TEMP/seed" diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index b820e9a..bb0b383 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -35,7 +35,7 @@ env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }} # 2026.11.10.1: openkal-run binds the interfaces its implementation provides. # mcpp_ref supplies the candidate until this release is installable. - MCPP_VERSION: 2026.11.10.1 + MCPP_VERSION: 2026.11.13.1 # The platform whose hosted arrangement the programs are built over, named by # the dispatch beside the tag: a tag's tools pin exact implementations, and the # platform is the one whose version group holds them (0.1.4: 0.18.0; 0.1.5: diff --git a/.gitignore b/.gitignore index 263d8a1..da06234 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ compile_commands.json mcpp.lock examples/bind/mathlib/lib/ __pycache__/ +okprof-*.json diff --git a/README.md b/README.md index 9250762..efe2d67 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,44 @@ The runner now folds a relative name against the first directory's name --- name beneath it, which is matched against the supplied directories as an absolute one is. No other change. +Version 0.6.3 builds a core runner where the implementation beneath it has no +files, processes or threads --- a firmware's, such as openkal-uefi. One interface +(`include/okrun.h`, `run/src/runner.h`, the bindings) has two runners, and +`run/mcpp.toml` selects one by the interfaces the implementation provides +(`cfg(interface = "openkal.fs")`, mcpp 2026.11.13.1): with `openkal.fs`, +`openkal.process`, `openkal.task`, `openkal.random` and `openkal.map` the full +runner (`run/src/full`), unchanged; without any of them the core runner +(`run/src/core`). The core runner runs what the program carries, from memory: +an image, a group of images, or an archive of them (`okrun_run_bytes`, +`okrun_run_images`, `okrun_run_okb_bytes`), with the same admission --- the +requirements note, the architecture, the signature --- and the same loader. It +has no cache, no snapshot and no profiler, and `okrun_run_file` refuses with the +reason. A name of an interface the implementation does not provide is absent to +the image, so an image that imports it weakly runs (SPEC 12.4). openkal-run +itself requires only `openkal.abort`, `openkal.env`, `openkal.exec`, +`openkal.memory`, `openkal.stream` and `openkal.time`, and mcpp 2026.11.13.1. + +Version 0.6.3 also reads, checks, places and loads an i386 image (`ELFCLASS32`, +`EM_386`; openkal 0.25.1, clauses 5.5 and 12.2), on an i386 runner only. libokld +widens the file's header, program headers and dynamic table to their +sixty-four bit forms when it opens one, and reads its symbols and relocations +through accessors of the class, so one loader, one check and one placement +serve both classes. The image states its addends (RELA, `-z rela`; i386's +default REL is refused with that option named), and its packed relative +relocations are of the four-byte word (a bitmap of 31 entries). The relocations +a loader applies are `R_386_RELATIVE`, `R_386_32`, `R_386_GLOB_DAT` and +`R_386_JMP_SLOT`. The runner enters an i386 image and its callbacks in the System +V i386 convention (arguments on the stack, the stack aligned to sixteen bytes at +a call), with the x87 control word `0x037F` and, where the processor has SSE, +MXCSR `0x1F80`. An okb names the architecture `i686`. The object a debugger +reads (`okrun --debugger`) is of the image's class: i386's retained link +relocations are REL, whose addend is the word at the place, so the object holds +each `R_386_32` and `R_386_PC32` word less the value the link resolved. An image +of the row `i686-openkal`, built through openkal-platform 0.28.0 (C++23 with +`import std`, an exception across a frame), runs under an okrun built for +`i686-linux-musl` on an x86-64 Linux, and gdb stops at a line of it and unwinds +into the runner, as for an x86-64 image (measured). + ## An image ```sh diff --git a/docs/debugging.md b/docs/debugging.md index da6a147..09b0f29 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -38,7 +38,12 @@ native source-debugger compatibility on those systems. ## Image requirements and limits The debugger-object builder accepts little-endian ELF64 metadata for x86-64 -and AArch64. Embedded debug sections may use ELF zlib compression +and AArch64, and ELF32 metadata for i386 (0.6.3). An i386 image's retained +link relocations are REL: their addend is the word at the place, which the link +has resolved, so the object holds each `R_386_32` and `R_386_PC32` word less the +symbol's value (and, for `R_386_PC32`, plus the place), and the debugger adds +its own. The image's run-time relocations (`.rela.dyn`, `.rela.plt`) are data in +the object. Embedded debug sections may use ELF zlib compression (`SHF_COMPRESSED`); the loader expands them in the debugger object. The executable image and its persistent compressed bytes remain unchanged. diff --git a/docs/okb.md b/docs/okb.md index 9c305bf..920a9e3 100644 --- a/docs/okb.md +++ b/docs/okb.md @@ -39,7 +39,7 @@ aarch64/libdep.okso ``` An architecture's directory is named in the manifest; `okld bundle` names it after the -architecture (`x86_64`, `aarch64`). Version 1 holds images and the two manifest members. +architecture (`x86_64`, `aarch64`, `i686`). Version 1 holds images and the two manifest members. A program's data files are delivered beside the okb (in the package it is part of); a view of files inside an okb, through the loader, is reserved for a later version, which the manifest's `okb` number distinguishes. diff --git a/examples/build-image.sh b/examples/build-image.sh index cea530e..48a8b3b 100755 --- a/examples/build-image.sh +++ b/examples/build-image.sh @@ -4,9 +4,9 @@ # with the compiler and linker any LLVM carries (SPEC 12.2): no operating # system in the triple, position-independent, emulated thread-local storage, # x18 left alone on AArch64, segments aligned to 64 KiB, relative relocations -# packed, the notes linked in, the requirements written by `okld note', and the -# image placed at its base by `okld place' (openkal 0.23; PLACE=0 leaves the -# placement note empty). +# packed, the addends stated (RELA; i386's default is REL), the notes linked in, +# the requirements written by `okld note', and the image placed at its base by +# `okld place' (openkal 0.23; PLACE=0 leaves the placement note empty). set -euo pipefail arch="$1"; out="$2"; shift 2 sources=(); link=() @@ -18,6 +18,10 @@ done work=$(mktemp -d); trap 'rm -rf "$work"' EXIT extra=() [ "$arch" = aarch64 ] && extra=(-ffixed-x18 -mno-outline-atomics) +# i386 (0.6.3): the 486's instructions (an image runs where DOSBox does, which +# executes no CMPXCHG8B), and the stack aligned to sixteen bytes at a call +# (SPEC 12.2), which a triple with no system leaves at four. +[ "$arch" = i686 ] && extra=(-march=i486 -mstack-alignment=16) "$OKLD" note-source > "$work/note.S" objs=() for src in "${sources[@]}" "$work/note.S"; do @@ -29,7 +33,7 @@ for src in "${sources[@]}" "$work/note.S"; do done entry=(-e ok_main) case "$out" in *.okso) entry=(-soname "$(basename "$out")") ;; esac -"$LLD" -shared -z max-page-size=65536 -z now -z noexecstack -z pack-relative-relocs --hash-style=both "${entry[@]}" \ +"$LLD" -shared -z max-page-size=65536 -z now -z noexecstack -z pack-relative-relocs -z rela --hash-style=both "${entry[@]}" \ -o "$out" "${objs[@]}" "${link[@]}" "$OKLD" note "$out" [ "${PLACE:-1}" = 0 ] || "$OKLD" place "$out" diff --git a/ld/mcpp.toml b/ld/mcpp.toml index 30598ea..7d80af8 100644 --- a/ld/mcpp.toml +++ b/ld/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-ld" -version = "0.6.2" +version = "0.6.3" description = "libokld: the ok format (openkal image format) --- its definition, a reader of bytes, a checker, a signer, and a loader. Freestanding: no C library, no exceptions." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/ld/src/debug_image.cpp b/ld/src/debug_image.cpp index 9fdd8f4..0947ee5 100644 --- a/ld/src/debug_image.cpp +++ b/ld/src/debug_image.cpp @@ -4,10 +4,44 @@ namespace okld { namespace { -constexpr elf::u32 progbits = 1, strtab = 3, rela = 4, nobits = 8; +constexpr elf::u32 progbits = 1, strtab = 3, rela = 4, nobits = 8, rel = 9; constexpr elf::u64 alloc = 2, compressed = 0x800; -struct compression_header { elf::u32 type, reserved; elf::u64 size, alignment; }; -static_assert(sizeof(compression_header) == 24); + +// THE TWO CLASSES (0.6.3). The object a debugger reads is of the image's class: +// the sixty-four bit one keeps its link relocations with their addends (RELA), +// and i386 keeps them as REL, whose addend is the word at the place. +struct class64 { + using ehdr = elf::ehdr; using shdr = elf::shdr; using sym = elf::sym; using reloc = elf::rela; + static constexpr elf::u8 ident = 2; + static constexpr elf::u32 link_relocations = rela; + static bool machine(elf::u16 m) { return m == elf::em_x86_64 || m == elf::em_aarch64; } + static elf::u32 r_type(elf::u64 info) { return elf::r_type(info); } + static elf::u32 r_sym(elf::u64 info) { return elf::r_sym(info); } + // Elf64_Chdr: type, reserved, size, alignment. + static constexpr elf::u64 chdr_size = 24; + static bool chdr(const unsigned char* p, elf::u64& size, elf::u64& alignment) { + struct { elf::u32 type, reserved; elf::u64 size, alignment; } c{}; + copy(&c, p, sizeof c); + size = c.size; alignment = c.alignment; + return c.type == 1 && !c.reserved; + } +}; +struct class32 { + using ehdr = elf::ehdr32; using shdr = elf::shdr32; using sym = elf::sym32; using reloc = elf::rel32; + static constexpr elf::u8 ident = 1; + static constexpr elf::u32 link_relocations = rel; + static bool machine(elf::u16 m) { return m == elf::em_386; } + static elf::u32 r_type(elf::u64 info) { return static_cast(info & 0xff); } + static elf::u32 r_sym(elf::u64 info) { return static_cast(info >> 8); } + // Elf32_Chdr: type, size, alignment. + static constexpr elf::u64 chdr_size = 12; + static bool chdr(const unsigned char* p, elf::u64& size, elf::u64& alignment) { + struct { elf::u32 type, size, alignment; } c{}; + copy(&c, p, sizeof c); + size = c.size; alignment = c.alignment; + return c.type == 1; + } +}; bool fail(okld_why* why, const char* message) { if (why) sentence(why->text, sizeof why->text) << "debugger image: " << message; @@ -26,15 +60,17 @@ bool align(elf::u64& size, elf::u64 alignment) { if (alignment & (alignment - 1)) return false; return add(size, (alignment - size % alignment) % alignment); } -const char* name(const unsigned char* bytes, const elf::shdr& strings, elf::u32 offset) { +template +const char* name(const unsigned char* bytes, const typename C::shdr& strings, elf::u32 offset) { if (offset >= strings.size) return nullptr; const char* p = reinterpret_cast(bytes + strings.offset + offset); for (elf::u64 i = offset; i < strings.size; ++i) if (!p[i - offset]) return p; return nullptr; } bool debug_section(const char* name) { return starts(name, ".debug_"); } -// Field widths from the x86-64 psABI and AAELF64 relocation tables. These -// are bounds checks only; the debugger still performs the relocations. +// Field widths from the x86-64 psABI, AAELF64 and the i386 psABI relocation +// tables. These are bounds checks only; the debugger still performs the +// relocations. int relocation_width(elf::u16 machine, elf::u32 type) { if (machine == elf::em_x86_64) { switch (type) { @@ -59,6 +95,17 @@ int relocation_width(elf::u16 machine, elf::u32 type) { if (type == 258 || type == 261 || (type >= 263 && type <= 313) || (type >= 512 && type <= 573)) return 4; } + if (machine == elf::em_386) { + switch (type) { + case 0: return 0; + case 20: case 21: return 2; // R_386_16, R_386_PC16 + case 22: case 23: return 1; // R_386_8, R_386_PC8 + default: + if ((type >= 1 && type <= 11) || (type >= 14 && type <= 19) + || (type >= 24 && type <= 43)) return 4; + return -1; + } + } return -1; } bool inflate_section(const unsigned char* source, elf::u64 size, @@ -81,28 +128,31 @@ struct buffer { okld_size size; ~buffer() { if (bytes) host->free(bytes, size, host->ctx); } }; -} // namespace -bool debugger_image(const void* input, okld_size n, okld_u64 base, - const okld_host* host, void** out, okld_size* out_size, - okld_why* why) { - *out = nullptr; - *out_size = 0; - if (!input || !host || !host->alloc || !host->free || n < sizeof(elf::ehdr)) - return fail(why, "missing ELF bytes or allocation callbacks"); - if (reinterpret_cast(input) % alignof(elf::shdr)) - return fail(why, "unaligned ELF storage"); - const auto* bytes = static_cast(input); - elf::ehdr header{}; +// A relocation section the image's link kept for a section of the debugger +// object (its `info' names it), or the image's run-time relocations (no `info', +// or, on i386, of the other form). +template bool relocations(const typename C::shdr& s) { + return s.type == C::link_relocations || s.type == rela; +} + +template +bool build(const unsigned char* bytes, okld_size n, okld_u64 base, + const okld_host* host, void** out, okld_size* out_size, okld_why* why) { + using shdr = typename C::shdr; + using sym = typename C::sym; + using reloc = typename C::reloc; + if (n < sizeof(typename C::ehdr)) return fail(why, "missing ELF bytes"); + typename C::ehdr header{}; copy(&header, bytes, sizeof header); if (header.ident[0] != 0x7f || header.ident[1] != 'E' || header.ident[2] != 'L' - || header.ident[3] != 'F' || header.ident[4] != 2 || header.ident[5] != 1 - || header.type != elf::et_dyn || (header.machine != elf::em_x86_64 && header.machine != elf::em_aarch64) - || header.shentsize != sizeof(elf::shdr) - || !header.shnum || header.shstrndx >= header.shnum || header.shoff % alignof(elf::shdr) - || !range(header.shoff, elf::u64(header.shnum) * sizeof(elf::shdr), n)) - return fail(why, "invalid ELF64 section table"); - const auto* sections = reinterpret_cast(bytes + header.shoff); + || header.ident[3] != 'F' || header.ident[4] != C::ident || header.ident[5] != 1 + || header.type != elf::et_dyn || !C::machine(header.machine) + || header.shentsize != sizeof(shdr) + || !header.shnum || header.shstrndx >= header.shnum || header.shoff % alignof(shdr) + || !range(header.shoff, elf::u64(header.shnum) * sizeof(shdr), n)) + return fail(why, "invalid ELF section table"); + const auto* sections = reinterpret_cast(bytes + header.shoff); const auto& strings = sections[header.shstrndx]; if (strings.type != strtab || !range(strings.offset, strings.size, n)) return fail(why, "invalid section names"); @@ -110,39 +160,41 @@ bool debugger_image(const void* input, okld_size n, okld_u64 base, bool has_debug = false, has_debug_relocations = false; for (unsigned i = 0; i < header.shnum; ++i) { const auto& s = sections[i]; - const char* label = name(bytes, strings, s.name); + const char* label = name(bytes, strings, s.name); if (!label || (s.type != nobits && !range(s.offset, s.size, n))) return fail(why, "section extends outside the file"); if (s.flags & alloc) { - auto address = s.addr; + elf::u64 address = s.addr; if (!add(address, base) || !add(address, s.size)) return fail(why, "placed section address overflow"); } if (starts(label, ".zdebug_")) return fail(why, "legacy GNU compressed debug sections are unsupported"); has_debug |= s.size && (equal(label, ".debug_info") || equal(label, ".debug_line")); - if (s.type == rela) { - if (s.entsize != sizeof(elf::rela) || s.size % sizeof(elf::rela) - || s.offset % alignof(elf::rela) || s.link >= header.shnum + if (relocations(s)) { + // i386's run-time relocations (RELA: `.rela.dyn', `.rela.plt') are data + // to the debugger; only the link's (REL) are read. + if (s.type != C::link_relocations) continue; + if (s.entsize != sizeof(reloc) || s.size % sizeof(reloc) + || s.offset % alignof(reloc) || s.link >= header.shnum || s.info >= header.shnum || (sections[s.link].type != elf::sht_symtab && sections[s.link].type != elf::sht_dynsym)) return fail(why, "invalid relocation table"); if (s.info) { - const char* target = name(bytes, strings, sections[s.info].name); + const char* target = name(bytes, strings, sections[s.info].name); if (!target) return fail(why, "invalid relocation target name"); has_debug_relocations |= s.size && debug_section(target); } } if (s.type == elf::sht_symtab || s.type == elf::sht_dynsym) { - if (s.entsize != sizeof(elf::sym) || s.size % sizeof(elf::sym) - || s.offset % alignof(elf::sym) || s.link >= header.shnum + if (s.entsize != sizeof(sym) || s.size % sizeof(sym) + || s.offset % alignof(sym) || s.link >= header.shnum || sections[s.link].type != strtab) return fail(why, "invalid symbol table"); } if (!(s.flags & compressed)) continue; - if ((s.flags & alloc) || !debug_section(label) || s.size < sizeof(compression_header)) + if ((s.flags & alloc) || !debug_section(label) || s.size < C::chdr_size) return fail(why, "unsupported compressed section"); - compression_header c{}; - copy(&c, bytes + s.offset, sizeof c); - if (c.type != 1 || c.reserved || !align(total, c.alignment) || !add(total, c.size)) + elf::u64 size = 0, alignment = 0; + if (!C::chdr(bytes + s.offset, size, alignment) || !align(total, alignment) || !add(total, size)) return fail(why, "unsupported compression or inflated size overflow"); } if (has_debug && !has_debug_relocations) @@ -150,36 +202,36 @@ bool debugger_image(const void* input, okld_size n, okld_u64 base, if (total > ~okld_size{0}) return fail(why, "debugger object is too large"); buffer result{host, static_cast(host->alloc(static_cast(total), host->ctx)), static_cast(total)}; if (!result.bytes) return fail(why, "no memory for the debugger object"); - if (reinterpret_cast(result.bytes) % alignof(elf::shdr)) + if (reinterpret_cast(result.bytes) % alignof(shdr)) return fail(why, "allocator returned unaligned ELF storage"); - copy(result.bytes, input, n); - auto* eh = reinterpret_cast(result.bytes); - auto* sh = reinterpret_cast(result.bytes + header.shoff); + copy(result.bytes, bytes, n); + auto* eh = reinterpret_cast(result.bytes); + auto* sh = reinterpret_cast(result.bytes + header.shoff); elf::u64 tail = n; for (unsigned i = 0; i < header.shnum; ++i) { const auto& original = sections[i]; - if (original.flags & alloc) sh[i].addr = original.addr + base; + if (original.flags & alloc) sh[i].addr = static_cast(original.addr + base); if (!(original.flags & compressed)) continue; - compression_header c{}; - copy(&c, bytes + original.offset, sizeof c); + elf::u64 size = 0, alignment = 0; + C::chdr(bytes + original.offset, size, alignment); const auto previous = tail; - if (!align(tail, c.alignment)) return fail(why, "invalid inflated alignment"); + if (!align(tail, alignment)) return fail(why, "invalid inflated alignment"); fill(result.bytes + previous, 0, tail - previous); - if (!inflate_section(bytes + original.offset + sizeof c, original.size - sizeof c, - result.bytes + tail, c.size)) return fail(why, "invalid compressed debug data"); - sh[i].offset = tail; - sh[i].size = c.size; - sh[i].addralign = c.alignment ? c.alignment : 1; - sh[i].flags &= ~compressed; - tail += c.size; + if (!inflate_section(bytes + original.offset + C::chdr_size, original.size - C::chdr_size, + result.bytes + tail, size)) return fail(why, "invalid compressed debug data"); + sh[i].offset = static_cast(tail); + sh[i].size = static_cast(size); + sh[i].addralign = static_cast(alignment ? alignment : 1); + sh[i].flags &= ~static_cast(compressed); + tail += size; } // Read original section addresses throughout. Section-table order cannot // decide whether a symbol has already seen the placement adjustment. for (unsigned i = 0; i < header.shnum; ++i) { const auto& s = sections[i]; if (s.type == elf::sht_symtab || s.type == elf::sht_dynsym) { - auto* symbols = reinterpret_cast(result.bytes + s.offset); - for (elf::u64 k = 0; k < s.size / sizeof(elf::sym); ++k) { + auto* symbols = reinterpret_cast(result.bytes + s.offset); + for (elf::u64 k = 0; k < s.size / sizeof(sym); ++k) { auto& symbol = symbols[k]; if (symbol.shndx == elf::shn_undef || symbol.shndx == elf::shn_abs) continue; if (symbol.shndx >= header.shnum || (symbol.info & 15) == 6 /* STT_TLS */) @@ -189,27 +241,46 @@ bool debugger_image(const void* input, okld_size n, okld_u64 base, // can precede the first allocated section. Express those as // absolute runtime addresses, not wrapped relative offsets. if (symbol.value < target.addr) { - if (!(target.flags & alloc) || !add(symbol.value, base)) + elf::u64 value = symbol.value; + if (!(target.flags & alloc) || !add(value, base)) return fail(why, "invalid linker-defined symbol address"); + symbol.value = static_cast(value); symbol.shndx = elf::shn_abs; } else symbol.value -= target.addr; } } - if (s.type != rela) continue; + if (!relocations(s)) continue; // Runtime relocations describe the loaded program, not a section of // the debugger object. Keep their bytes as ordinary data. - if (!s.info) { sh[i].type = progbits; sh[i].link = sh[i].info = 0; sh[i].entsize = 0; continue; } + if (!s.info || s.type != C::link_relocations) { sh[i].type = progbits; sh[i].link = sh[i].info = 0; sh[i].entsize = 0; continue; } const auto& target = sections[s.info]; - const auto symbol_count = sections[s.link].size / sizeof(elf::sym); - auto* relocations = reinterpret_cast(result.bytes + s.offset); - const auto target_size = sh[s.info].size; - for (elf::u64 k = 0; k < s.size / sizeof(elf::rela); ++k) { - auto& r = relocations[k]; - const int width = relocation_width(header.machine, elf::r_type(r.info)); + const auto& table = sections[s.link]; + const auto symbol_count = table.size / sizeof(sym); + auto* list = reinterpret_cast(result.bytes + s.offset); + const elf::u64 target_size = sh[s.info].size; + for (elf::u64 k = 0; k < s.size / sizeof(reloc); ++k) { + auto& r = list[k]; + const elf::u32 type = C::r_type(r.info); + const int width = relocation_width(header.machine, type); if (width == 0) continue; - if (elf::r_sym(r.info) >= symbol_count || r.offset < target.addr + if (C::r_sym(r.info) >= symbol_count || r.offset < target.addr || width < 0 || !range(r.offset - target.addr, static_cast(width), target_size)) return fail(why, "relocation is outside its target section"); + if constexpr (C::link_relocations == rel) { + // The word at the place is the value the link wrote, S + A + // (R_386_32) or S + A - P (R_386_PC32); the debugger reads it as + // the addend A and adds its own S (and subtracts its own P). + if (type == 1 || type == 2) { + sym symbol{}; + copy(&symbol, bytes + table.offset + elf::u64(C::r_sym(r.info)) * sizeof(sym), sizeof symbol); + auto* place = result.bytes + sh[s.info].offset + (r.offset - target.addr); + elf::u32 word = 0; + copy(&word, place, sizeof word); + word -= symbol.value; + if (type == 2) word += r.offset; + copy(place, &word, sizeof word); + } + } r.offset -= target.addr; } } @@ -221,4 +292,19 @@ bool debugger_image(const void* input, okld_size n, okld_u64 base, result.bytes = nullptr; return true; } +} // namespace + +bool debugger_image(const void* input, okld_size n, okld_u64 base, + const okld_host* host, void** out, okld_size* out_size, + okld_why* why) { + *out = nullptr; + *out_size = 0; + if (!input || !host || !host->alloc || !host->free || n < 16) + return fail(why, "missing ELF bytes or allocation callbacks"); + if (reinterpret_cast(input) % alignof(elf::shdr)) + return fail(why, "unaligned ELF storage"); + const auto* bytes = static_cast(input); + if (bytes[4] == class32::ident) return build(bytes, n, base, host, out, out_size, why); + return build(bytes, n, base, host, out, out_size, why); +} } // namespace okld diff --git a/ld/src/elf.h b/ld/src/elf.h index b78a754..37e17b0 100644 --- a/ld/src/elf.h +++ b/ld/src/elf.h @@ -23,7 +23,28 @@ struct nhdr { u32 namesz, descsz, type; }; static_assert(sizeof(ehdr) == 64 && sizeof(phdr) == 56 && sizeof(shdr) == 64); static_assert(sizeof(dyn) == 16 && sizeof(sym) == 24 && sizeof(rela) == 24); -enum : u32 { et_dyn = 3, em_x86_64 = 62, em_aarch64 = 183 }; +// THE THIRTY-TWO BIT CLASS (ELFCLASS32), an i386 image's (SPEC 5.5, 12.2). A view +// widens its header, program headers and dynamic table into the forms above when +// it opens the image; its symbols and relocations are read one at a time. +using i32 = int; +struct ehdr32 { + u8 ident[16]; + u16 type, machine; u32 version; + u32 entry, phoff, shoff; + u32 flags; u16 ehsize, phentsize, phnum, shentsize, shnum, shstrndx; +}; +struct phdr32 { u32 type, offset, vaddr, paddr, filesz, memsz, flags, align; }; +struct dyn32 { i32 tag; u32 val; }; +struct sym32 { u32 name, value, size; u8 info, other; u16 shndx; }; +struct shdr32 { u32 name, type, flags, addr, offset, size, link, info, addralign, entsize; }; +struct rela32 { u32 offset, info; i32 addend; }; +// A link's retained relocations of i386 (`--emit-relocs'): the addend is the word +// at the place, which the link has resolved. +struct rel32 { u32 offset, info; }; +static_assert(sizeof(ehdr32) == 52 && sizeof(phdr32) == 32 && sizeof(shdr32) == 40); +static_assert(sizeof(dyn32) == 8 && sizeof(sym32) == 16 && sizeof(rela32) == 12 && sizeof(rel32) == 8); + +enum : u32 { et_dyn = 3, em_386 = 3, em_x86_64 = 62, em_aarch64 = 183 }; enum : u32 { pt_load = 1, pt_dynamic = 2, pt_interp = 3, pt_note = 4, pt_tls = 7, pt_gnu_relro = 0x6474e552 }; enum : u32 { pf_x = 1, pf_w = 2, pf_r = 4 }; enum : u32 { sht_note = 7, sht_symtab = 2, sht_dynsym = 11 }; @@ -44,10 +65,14 @@ enum : u32 { r_x86_64_none = 0, r_x86_64_64 = 1, r_x86_64_glob_dat = 6, r_x86_64_jump_slot = 7, r_x86_64_relative = 8, r_aarch64_none = 0, r_aarch64_abs64 = 257, r_aarch64_glob_dat = 1025, r_aarch64_jump_slot = 1026, r_aarch64_relative = 1027, + r_386_none = 0, r_386_32 = 1, r_386_glob_dat = 6, r_386_jmp_slot = 7, r_386_relative = 8, }; +// `r_info' of the sixty-four bit class; a view's relocations widen the thirty-two +// bit one (the symbol above eight bits, the type below) into this form. inline u32 r_type(u64 info) { return static_cast(info & 0xffffffffu); } inline u32 r_sym(u64 info) { return static_cast(info >> 32); } +inline u64 r_info(u32 sym, u32 type) { return (static_cast(sym) << 32) | type; } inline u8 st_bind(u8 info) { return static_cast(info >> 4); } } // namespace okld::elf diff --git a/ld/src/image.h b/ld/src/image.h index 547b049..8409b66 100644 --- a/ld/src/image.h +++ b/ld/src/image.h @@ -12,12 +12,38 @@ struct view { const elf::phdr* ph = nullptr; const elf::phdr* dynamic_ph = nullptr; + // THE CLASS (SPEC 12.2): a sixty-four bit image is read where it lies; a + // thirty-two bit one (i386) has its header, program headers and dynamic + // table widened into the forms above when it is opened, so that what reads + // them reads one form, and its symbols, relocations and words are read + // through the accessors below, which know its widths. + bool wide = true; + elf::ehdr eh_wide{}; + static constexpr unsigned max_ph32 = 32, max_dyn32 = 160; + elf::phdr ph_wide[max_ph32]; + elf::dyn dyn_wide[max_dyn32]; + size ndyn_wide = 0; + + view() = default; + view(const view&) = delete; + view& operator=(const view&) = delete; + template const T* at(elf::u64 offset, size count = 1) const { if (offset > len || count > (len - offset) / sizeof(T)) return nullptr; return reinterpret_cast(data + offset); } + // The width of an address of the image, which is the width of a word a + // relocation writes and of a packed relative entry. + size word() const { return wide ? 8 : 4; } + elf::u64 word_of(const unsigned char* p) const { + elf::u64 w = 0; + copy(&w, p, word()); + return w; + } + void put_word(unsigned char* p, elf::u64 value) const { copy(p, &value, word()); } + // The file offset an address of the image lies at, through the segment // that holds it; or ~0 when no segment does. elf::u64 offset_of(elf::u64 addr) const { @@ -29,19 +55,19 @@ struct view { return ~0ull; } + const elf::dyn* dynamic_table(size& n) const { + if (!wide) { n = ndyn_wide; return dyn_wide; } + n = dynamic_ph ? dynamic_ph->filesz / sizeof(elf::dyn) : 0; + return dynamic_ph ? at(dynamic_ph->offset, n) : nullptr; + } // The dynamic table's value for a tag, or `none'. elf::u64 dynamic(elf::i64 tag, elf::u64 none = 0) const { - if (!dynamic_ph) return none; - const auto n = dynamic_ph->filesz / sizeof(elf::dyn); - const auto* d = at(dynamic_ph->offset, n); + size n = 0; + const auto* d = dynamic_table(n); if (!d) return none; for (size i = 0; i < n && d[i].tag != elf::dt_null; ++i) if (d[i].tag == tag) return d[i].val; return none; } - const elf::dyn* dynamic_table(size& n) const { - n = dynamic_ph ? dynamic_ph->filesz / sizeof(elf::dyn) : 0; - return dynamic_ph ? at(dynamic_ph->offset, n) : nullptr; - } const char* string(elf::u64 index) const { const auto strtab = offset_of(dynamic(elf::dt_strtab, ~0ull)); @@ -55,13 +81,56 @@ struct view { // The number of dynamic symbols: DT_HASH says it, and DT_GNU_HASH lets it // be computed. size symbol_count() const; - const elf::sym* symbols() const { + // A dynamic symbol, widened; false beyond the table. + bool symbol(size index, elf::sym& out) const { const auto off = offset_of(dynamic(elf::dt_symtab, ~0ull)); - const auto n = symbol_count(); - return off == ~0ull ? nullptr : at(off, n); + if (off == ~0ull || index >= symbol_count()) return false; + return symbol_at(off, index, out); + } + // A symbol of a table at a file offset (the dynamic one, or a section's). + bool symbol_at(elf::u64 table, size index, elf::sym& out) const { + if (wide) { + const auto* s = at(table + index * sizeof(elf::sym)); + if (!s) return false; + out = *s; + return true; + } + const auto* s = at(table + index * sizeof(elf::sym32)); + if (!s) return false; + out = elf::sym{ s->name, s->info, s->other, s->shndx, s->value, s->size }; + return true; + } + size symbol_size() const { return wide ? sizeof(elf::sym) : sizeof(elf::sym32); } + + // A table of RELA relocations (DT_RELA or DT_JMPREL) and its size in bytes: + // how many it holds, and each, widened. + size relocation_size() const { return wide ? sizeof(elf::rela) : sizeof(elf::rela32); } + size relocation_count(elf::i64 tag, elf::i64 size_tag) const { + const auto at_ = offset_of(dynamic(tag, ~0ull)); + const auto bytes = dynamic(size_tag); + if (at_ == ~0ull || !at(at_, bytes)) return 0; + return bytes / relocation_size(); + } + elf::rela relocation(elf::i64 tag, size index) const { + const auto base = offset_of(dynamic(tag, ~0ull)) + index * relocation_size(); + if (wide) return *at(base); + const auto* r = at(base); + return elf::rela{ r->offset, elf::r_info(r->info >> 8, r->info & 0xff), r->addend }; } }; +// The architecture's relative relocation, and its none (SPEC 12.2). +inline elf::u32 relative_type(const view& v) { + return v.eh->machine == elf::em_x86_64 ? elf::r_x86_64_relative + : v.eh->machine == elf::em_386 ? elf::r_386_relative + : elf::r_aarch64_relative; +} +inline elf::u32 none_type(const view& v) { + return v.eh->machine == elf::em_x86_64 ? elf::r_x86_64_none + : v.eh->machine == elf::em_386 ? elf::r_386_none + : elf::r_aarch64_none; +} + // Opens a view; the sentence says why not. bool open(const void* bytes, size n, view& v, char* why, size why_cap); diff --git a/ld/src/load.cpp b/ld/src/load.cpp index d4e4cbb..2350e21 100644 --- a/ld/src/load.cpp +++ b/ld/src/load.cpp @@ -17,9 +17,11 @@ struct okld_module { const okld_host* host = nullptr; unsigned char* base = nullptr; elf::u64 span = 0; - const elf::phdr* phdr = nullptr; // within the mapped image, or `own_phdr' - elf::phdr* own_phdr = nullptr; // the program headers, copied when no segment holds them - elf::u32 phnum = 0; + // The program headers as the image states them, of its class: within the + // mapped image, or `own_phdr', a copy, when no segment holds them. + const void* phdr = nullptr; + unsigned char* own_phdr = nullptr; + elf::u32 phnum = 0, phentsize = 0; elf::u64 entry = 0; char name[256] = {}; okld_module* needed[16] = {}; @@ -27,8 +29,9 @@ struct okld_module { int references = 1; bool initialised = false; bool finalised = false; - // Within the mapped image, from its dynamic table. - const elf::sym* symtab = nullptr; + // Within the mapped image, from its dynamic table, of the image's class. + bool wide = true; + const unsigned char* symtab = nullptr; const char* strtab = nullptr; elf::u64 strsz = 0; const elf::u32* hash = nullptr; // DT_HASH @@ -36,7 +39,7 @@ struct okld_module { elf::u64 init = 0, fini = 0, init_array = 0, init_arraysz = 0, fini_array = 0, fini_arraysz = 0; // The relocation tables, within the mapped image (okld_bound_imports). elf::u64 rela = ~0ull, relasz = 0, jmprel = ~0ull, pltrelsz = 0; - bool x86 = false; + elf::u16 machine = 0; void* debug_entry = nullptr; // the GDB JIT record void* debug_copy = nullptr; okld_size debug_size = 0; @@ -116,17 +119,41 @@ elf::u32 sysv_hash(const char* s) { return h; } +// A symbol of a module's dynamic table, widened from its class. +elf::sym symbol_of(const okld_module* m, elf::u32 index) { + if (m->wide) { + elf::sym s; + copy(&s, m->symtab + static_cast(index) * sizeof(elf::sym), sizeof s); + return s; + } + elf::sym32 t; + copy(&t, m->symtab + static_cast(index) * sizeof(elf::sym32), sizeof t); + return elf::sym{ t.name, t.info, t.other, t.shndx, t.value, t.size }; +} + +// A RELA relocation of a table in the mapped image, widened from its class. +elf::rela relocation_of(const unsigned char* table, elf::u64 k, bool wide) { + if (wide) { + elf::rela r; + copy(&r, table + k * sizeof(elf::rela), sizeof r); + return r; + } + elf::rela32 r; + copy(&r, table + k * sizeof(elf::rela32), sizeof r); + return elf::rela{ r.offset, elf::r_info(r.info >> 8, r.info & 0xff), r.addend }; +} + // An exported name of a module: defined, not local. void* lookup(const okld_module* m, const char* name) { auto defined = [&](elf::u32 index) -> void* { - const auto& s = m->symtab[index]; + const auto s = symbol_of(m, index); if (s.shndx == elf::shn_undef || elf::st_bind(s.info) == elf::stb_local || !s.name) return nullptr; if (s.name >= m->strsz || !equal(m->strtab + s.name, name)) return nullptr; return s.shndx == elf::shn_abs ? reinterpret_cast(s.value) : m->base + s.value; }; if (m->gnu_hash) { const elf::u32 nbuckets = m->gnu_hash[0], symoffset = m->gnu_hash[1], bloom = m->gnu_hash[2]; - const auto* buckets = m->gnu_hash + 4 + bloom * 2; + const auto* buckets = m->gnu_hash + 4 + bloom * (m->wide ? 2 : 1); // bloom words of the class const auto* chain = buckets + nbuckets; const elf::u32 h = gnu_hash(name); for (elf::u32 i = buckets[h % nbuckets]; i >= symoffset && i != 0; ++i) { @@ -199,7 +226,7 @@ void withdraw(okld_module* m) { // Whether a relocation's symbol is one the module imports: named, and not // defined by it. bool imports(const okld_module* m, elf::u32 index) { - return index != 0 && m->symtab[index].shndx == elf::shn_undef; + return index != 0 && symbol_of(m, index).shndx == elf::shn_undef; } // EACH RELOCATION WRITES A WRITABLE SEGMENT, or the image is refused: a @@ -214,11 +241,13 @@ enum class applying { all, named, imports }; int relocate(okld_module* m, const view& v, const okld_host* host, const writable& w, applying what, okld_why* why) { const bool placed = what != applying::all; const elf::u64 b = reinterpret_cast(m->base); - const bool x86 = v.eh->machine == elf::em_x86_64; + const elf::u16 machine = v.eh->machine; + const elf::u64 wsz = v.word(); + auto put = [&](elf::u64 offset, elf::u64 value) { v.put_word(m->base + offset, value); }; auto symbol_address = [&](elf::u32 index, bool& ok) -> elf::u64 { ok = true; if (index == 0) return 0; - const auto& s = m->symtab[index]; + const auto s = symbol_of(m, index); if (s.shndx != elf::shn_undef) return s.shndx == elf::shn_abs ? s.value : b + s.value; const char* name = s.name < m->strsz ? m->strtab + s.name : ""; if (void* p = host->resolve ? host->resolve(name, host->ctx) : nullptr) return reinterpret_cast(p); @@ -232,37 +261,48 @@ int relocate(okld_module* m, const view& v, const okld_host* host, const writabl auto apply = [&](elf::i64 tag, elf::i64 size_tag) -> bool { const elf::u64 at = v.dynamic(tag, ~0ull), bytes = v.dynamic(size_tag); if (at == ~0ull || bytes == 0) return true; - const auto* r = reinterpret_cast(m->base + at); - for (elf::u64 k = 0; k < bytes / sizeof(elf::rela); ++k) { - if (elf::r_type(r[k].info) != (x86 ? elf::r_x86_64_none : elf::r_aarch64_none) - && !w.holds(r[k].offset, 8)) { + const unsigned char* table = m->base + at; + for (elf::u64 k = 0; k < bytes / v.relocation_size(); ++k) { + const elf::rela r = relocation_of(table, k, v.wide); + const elf::u32 t = elf::r_type(r.info); + if (t != none_type(v) && !w.holds(r.offset, wsz)) { say(why, "a relocation writes outside the image's writable segments (SPEC 12.2)"); return false; } - auto* where = reinterpret_cast(m->base + r[k].offset); - const elf::u32 t = elf::r_type(r[k].info); // AT ITS BASE THE RELATIVE ONES ARE ALREADY IN THE BYTES (SPEC // 12.3, openkal 0.23): what remains names a symbol. if (placed && !names_a_symbol(v, t)) continue; - if (what == applying::imports && !imports(m, elf::r_sym(r[k].info))) continue; + if (what == applying::imports && !imports(m, elf::r_sym(r.info))) continue; bool ok = true; - if (x86) { + const elf::u64 addend = static_cast(r.addend); + if (machine == elf::em_x86_64) { switch (t) { case elf::r_x86_64_none: break; - case elf::r_x86_64_relative: *where = b + static_cast(r[k].addend); break; - case elf::r_x86_64_64: *where = symbol_address(elf::r_sym(r[k].info), ok) + static_cast(r[k].addend); break; + case elf::r_x86_64_relative: put(r.offset, b + addend); break; + case elf::r_x86_64_64: put(r.offset, symbol_address(elf::r_sym(r.info), ok) + addend); break; case elf::r_x86_64_glob_dat: - case elf::r_x86_64_jump_slot: *where = symbol_address(elf::r_sym(r[k].info), ok); break; + case elf::r_x86_64_jump_slot: put(r.offset, symbol_address(elf::r_sym(r.info), ok)); break; + default: say(why, "a relocation type the format does not allow (SPEC 12.2)"); return false; + } + } else if (machine == elf::em_386) { + // i386 with explicit addends (-z rela): GLOB_DAT and JMP_SLOT + // hold the symbol's address, as on x86-64. + switch (t) { + case elf::r_386_none: break; + case elf::r_386_relative: put(r.offset, b + addend); break; + case elf::r_386_32: put(r.offset, symbol_address(elf::r_sym(r.info), ok) + addend); break; + case elf::r_386_glob_dat: + case elf::r_386_jmp_slot: put(r.offset, symbol_address(elf::r_sym(r.info), ok)); break; default: say(why, "a relocation type the format does not allow (SPEC 12.2)"); return false; } } else { switch (t) { case elf::r_aarch64_none: break; - case elf::r_aarch64_relative: *where = b + static_cast(r[k].addend); break; + case elf::r_aarch64_relative: put(r.offset, b + addend); break; case elf::r_aarch64_abs64: case elf::r_aarch64_glob_dat: case elf::r_aarch64_jump_slot: - *where = symbol_address(elf::r_sym(r[k].info), ok) + static_cast(r[k].addend); break; + put(r.offset, symbol_address(elf::r_sym(r.info), ok) + addend); break; default: say(why, "a relocation type the format does not allow (SPEC 12.2)"); return false; } } @@ -275,22 +315,25 @@ int relocate(okld_module* m, const view& v, const okld_host* host, const writabl // at its base. if (const elf::u64 at = v.dynamic(elf::dt_relr, ~0ull); !placed && at != ~0ull) { const elf::u64 bytes = v.dynamic(elf::dt_relrsz); - const auto* e = reinterpret_cast(m->base + at); + const unsigned char* e = m->base + at; elf::u64 next = 0; // the offset the first bit of a bitmap entry names bool ok = true; - for (elf::u64 k = 0; ok && k < bytes / 8; ++k) { - if ((e[k] & 1) == 0) { - ok = w.holds(e[k], 8); - if (ok) *reinterpret_cast(m->base + e[k]) += b; - next = e[k] + 8; + auto add = [&](elf::u64 offset) { + ok = w.holds(offset, wsz); + if (ok) put(offset, v.word_of(m->base + offset) + b); + }; + // An entry is a word of the class: an address, or a bitmap of the + // word's width less one. + for (elf::u64 k = 0; ok && k < bytes / wsz; ++k) { + const elf::u64 entry = v.word_of(e + k * wsz); + if ((entry & 1) == 0) { + add(entry); + next = entry + wsz; } else { - elf::u64 bits = e[k] >> 1; - for (elf::u64 i = 0; ok && bits; ++i, bits >>= 1) { - if (!(bits & 1)) continue; - ok = w.holds(next + 8 * i, 8); - if (ok) *reinterpret_cast(m->base + next + 8 * i) += b; - } - next += 63 * 8; + elf::u64 bits = entry >> 1; + for (elf::u64 i = 0; ok && bits; ++i, bits >>= 1) + if (bits & 1) add(next + wsz * i); + next += (wsz * 8 - 1) * wsz; } } if (!ok) { @@ -307,7 +350,7 @@ namespace { // A placed module's region, its own program headers and its record. void discard(okld_module* m) { const okld_host* host = m->host; - if (m->own_phdr) host->free(m->own_phdr, sizeof(elf::phdr) * m->phnum, host->ctx); + if (m->own_phdr) host->free(m->own_phdr, static_cast(m->phentsize) * m->phnum, host->ctx); host->release(m->base, m->span, host->ctx); host->free(m, sizeof *m, host->ctx); } @@ -344,15 +387,15 @@ void mark_named(const view& v, elf::u64 from, elf::u64 to, named_blocks& out) { out.count = (round_up(to, block_size) - out.first) / block_size; if (out.count > 1024) out.count = 1024; auto mark = [&](elf::i64 tag, elf::i64 size_tag) { - const auto at = v.offset_of(v.dynamic(tag, ~0ull)); - const auto n = v.dynamic(size_tag); - const auto* r = at == ~0ull ? nullptr : v.at(at, n / sizeof(elf::rela)); - for (size k = 0; r && k < n / sizeof(elf::rela); ++k) { - if (!names_a_symbol(v, elf::r_type(r[k].info))) continue; - const elf::u64 o = r[k].offset; - if (o + 8 <= from || o >= to) continue; + const auto n = v.relocation_count(tag, size_tag); + const elf::u64 w = v.word(); + for (size k = 0; k < n; ++k) { + const auto r = v.relocation(tag, k); + if (!names_a_symbol(v, elf::r_type(r.info))) continue; + const elf::u64 o = r.offset; + if (o + w <= from || o >= to) continue; const elf::u64 a = (o > out.first ? o - out.first : 0) / block_size; - const elf::u64 b = (o + 7 - out.first) / block_size; + const elf::u64 b = (o + w - 1 - out.first) / block_size; if (a < out.count) out.named[a] = true; if (b < out.count) out.named[b] = true; } @@ -672,20 +715,23 @@ int load_image(const void* bytes, okld_size n, const char* name, const okld_host } m->entry = v.eh->entry; m->phnum = v.eh->phnum; + m->phentsize = v.eh->phentsize; + const elf::u64 phbytes = static_cast(m->phentsize) * m->phnum; for (unsigned k = 0; k < v.eh->phnum; ++k) - if (v.ph[k].type == elf::pt_load && v.eh->phoff >= v.ph[k].offset && v.eh->phoff < v.ph[k].offset + v.ph[k].filesz) - m->phdr = reinterpret_cast(m->base + v.ph[k].vaddr + (v.eh->phoff - v.ph[k].offset)); + if (v.ph[k].type == elf::pt_load && v.eh->phoff >= v.ph[k].offset && v.eh->phoff + phbytes <= v.ph[k].offset + v.ph[k].filesz) + m->phdr = m->base + v.ph[k].vaddr + (v.eh->phoff - v.ph[k].offset); // THE LOADER KEEPS NO POINTER INTO THE CALLER'S BYTES, which the caller may // release once the image is loaded: program headers no segment holds are - // copied into the record. + // copied into the record, as the image states them. if (!m->phdr) { - m->own_phdr = static_cast(host->alloc(sizeof(elf::phdr) * m->phnum, host->ctx)); + m->own_phdr = static_cast(host->alloc(phbytes, host->ctx)); if (!m->own_phdr) { say(why, "no memory for the image's program headers"); host->release(m->base, m->span, host->ctx); host->free(m, sizeof *m, host->ctx); return -1; } - copy(m->own_phdr, v.ph, sizeof(elf::phdr) * m->phnum); + copy(m->own_phdr, v.data + v.eh->phoff, phbytes); m->phdr = m->own_phdr; } - m->symtab = reinterpret_cast(m->base + v.dynamic(elf::dt_symtab)); + m->wide = v.wide; + m->symtab = m->base + v.dynamic(elf::dt_symtab); m->strtab = reinterpret_cast(m->base + v.dynamic(elf::dt_strtab)); m->strsz = v.dynamic(elf::dt_strsz); if (const elf::u64 h = v.dynamic(elf::dt_hash, ~0ull); h != ~0ull) m->hash = reinterpret_cast(m->base + h); @@ -695,7 +741,7 @@ int load_image(const void* bytes, okld_size n, const char* name, const okld_host m->fini_array = v.dynamic(elf::dt_fini_array); m->fini_arraysz = v.dynamic(elf::dt_fini_arraysz); m->rela = v.dynamic(elf::dt_rela, ~0ull); m->relasz = v.dynamic(elf::dt_relasz); m->jmprel = v.dynamic(elf::dt_jmprel, ~0ull); m->pltrelsz = v.dynamic(elf::dt_pltrelsz); - m->x86 = v.eh->machine == elf::em_x86_64; + m->machine = v.eh->machine; const applying what = !placed ? applying::all : src->snapshot ? applying::imports : applying::named; if (relocate(m, v, host, w, what, why) != 0) { discard(m); return -1; } @@ -798,22 +844,26 @@ extern "C" int okld_bound_imports(okld_module* m, int (*each)(void*, okld_u64, c const elf::u64 tables[2][2] = { { m->rela, m->relasz }, { m->jmprel, m->pltrelsz } }; for (const auto& t : tables) { if (t[0] == ~0ull) continue; - const auto* r = reinterpret_cast(m->base + t[0]); - for (elf::u64 k = 0; k < t[1] / sizeof(elf::rela); ++k) { - const elf::u32 index = elf::r_sym(r[k].info); + const unsigned char* table = m->base + t[0]; + const elf::u64 entry = m->wide ? sizeof(elf::rela) : sizeof(elf::rela32); + for (elf::u64 k = 0; k < t[1] / entry; ++k) { + const elf::rela r = relocation_of(table, k, m->wide); + const elf::u32 index = elf::r_sym(r.info); if (!imports(m, index)) continue; - const auto& sym = m->symtab[index]; + const auto sym = symbol_of(m, index); const char* name = sym.name < m->strsz ? m->strtab + sym.name : ""; elf::u64 value = 0; if (void* p = host->resolve ? host->resolve(name, host->ctx) : nullptr) value = reinterpret_cast(p); for (okld_size n = 0; value == 0 && n < m->needed_count; ++n) if (void* p = lookup(m->needed[n], name)) value = reinterpret_cast(p); // The addend counts where the relocation adds it: every type but - // x86-64's GLOB_DAT and JUMP_SLOT. - const elf::u32 type = elf::r_type(r[k].info); - const bool adds = !m->x86 || (type != elf::r_x86_64_glob_dat && type != elf::r_x86_64_jump_slot); - if (value != 0 && adds) value += static_cast(r[k].addend); - if (const int stop = each(m->base + r[k].offset, value, name, ctx)) return stop; + // GLOB_DAT and JUMP_SLOT of x86-64 and of i386. + const elf::u32 type = elf::r_type(r.info); + const bool adds = m->machine == elf::em_x86_64 ? type != elf::r_x86_64_glob_dat && type != elf::r_x86_64_jump_slot + : m->machine == elf::em_386 ? type != elf::r_386_glob_dat && type != elf::r_386_jmp_slot + : true; + if (value != 0 && adds) value += static_cast(r.addend); + if (const int stop = each(m->base + r.offset, value, name, ctx)) return stop; } } return 0; diff --git a/ld/src/okb.cpp b/ld/src/okb.cpp index 3e10ff3..54162d7 100644 --- a/ld/src/okb.cpp +++ b/ld/src/okb.cpp @@ -380,7 +380,7 @@ extern "C" int okld_okb_admit_layout(okld_u64 archive_size, const okld_zip_membe } extern "C" const char* okld_okb_arch_of(okld_u32 machine) { - return machine == 62 ? "x86_64" : machine == 183 ? "aarch64" : nullptr; + return machine == 62 ? "x86_64" : machine == 183 ? "aarch64" : machine == 3 ? "i686" : nullptr; } extern "C" int okld_okb_chunks(const void* json, okld_size n, const char* member, okld_u64* chunk_size, diff --git a/ld/src/place.cpp b/ld/src/place.cpp index 8972d1d..2a66583 100644 --- a/ld/src/place.cpp +++ b/ld/src/place.cpp @@ -20,7 +20,7 @@ constexpr elf::u64 alignment = 65536; // The end of what the ELF describes: segments, program headers and sections. // What lies beyond it is a placement's stored segments (or nothing). elf::u64 content_end(const view& v) { - elf::u64 end = v.eh->phoff + static_cast(v.eh->phnum) * sizeof(elf::phdr); + elf::u64 end = v.eh->phoff + static_cast(v.eh->phnum) * v.eh->phentsize; for (unsigned k = 0; k < v.eh->phnum; ++k) if (v.ph[k].offset + v.ph[k].filesz > end) end = v.ph[k].offset + v.ph[k].filesz; if (v.eh->shoff) { @@ -62,9 +62,7 @@ bool states_placement(const view& v) { } // namespace bool names_a_symbol(const view& v, elf::u32 type) { - return v.eh->machine == elf::em_x86_64 - ? type != elf::r_x86_64_none && type != elf::r_x86_64_relative - : type != elf::r_aarch64_none && type != elf::r_aarch64_relative; + return type != none_type(v) && type != relative_type(v); } bool read_placement(const view& v, placement& out) { @@ -89,19 +87,15 @@ bool read_placement(const view& v, placement& out) { } void apply_relative(const view& v, elf::u64 base, unsigned char* seg, elf::u64 vaddr, elf::u64 filesz) { - const bool x86 = v.eh->machine == elf::em_x86_64; - const elf::u32 relative = x86 ? elf::r_x86_64_relative : elf::r_aarch64_relative; - auto inside = [&](elf::u64 at) { return at >= vaddr && at <= vaddr + filesz && filesz - (at - vaddr) >= 8; }; + const elf::u32 relative = relative_type(v); + const elf::u64 w = v.word(); + auto inside = [&](elf::u64 at) { return at >= vaddr && at <= vaddr + filesz && filesz - (at - vaddr) >= w; }; auto table = [&](elf::i64 tag, elf::i64 size_tag) { - const auto at = v.offset_of(v.dynamic(tag, ~0ull)); - const auto bytes = v.dynamic(size_tag); - if (at == ~0ull || bytes == 0) return; - const auto* r = v.at(at, bytes / sizeof(elf::rela)); - if (!r) return; - for (size k = 0; k < bytes / sizeof(elf::rela); ++k) { - if (elf::r_type(r[k].info) != relative || !inside(r[k].offset)) continue; - const elf::u64 value = base + static_cast(r[k].addend); - copy(seg + (r[k].offset - vaddr), &value, 8); + const auto n = v.relocation_count(tag, size_tag); + for (size k = 0; k < n; ++k) { + const auto r = v.relocation(tag, k); + if (elf::r_type(r.info) != relative || !inside(r.offset)) continue; + v.put_word(seg + (r.offset - vaddr), base + static_cast(r.addend)); } }; table(elf::dt_rela, elf::dt_relasz); @@ -109,25 +103,25 @@ void apply_relative(const view& v, elf::u64 base, unsigned char* seg, elf::u64 v const auto relr = v.offset_of(v.dynamic(elf::dt_relr, ~0ull)); const auto relr_bytes = v.dynamic(elf::dt_relrsz); if (relr == ~0ull || relr_bytes == 0) return; - const auto* e = v.at(relr, relr_bytes / 8); + const auto* e = v.at(relr, relr_bytes); if (!e) return; auto add = [&](elf::u64 at) { if (!inside(at)) return; - elf::u64 value = 0; - copy(&value, seg + (at - vaddr), 8); - value += base; - copy(seg + (at - vaddr), &value, 8); + v.put_word(seg + (at - vaddr), v.word_of(seg + (at - vaddr)) + base); }; + // A packed entry is a word of the class: an address, or a bitmap of the + // word's width less one naming the words after the last. elf::u64 next = 0; - for (size k = 0; k < relr_bytes / 8; ++k) { - if ((e[k] & 1) == 0) { - add(e[k]); - next = e[k] + 8; + for (size k = 0; k < relr_bytes / w; ++k) { + const elf::u64 entry = v.word_of(e + k * w); + if ((entry & 1) == 0) { + add(entry); + next = entry + w; } else { - elf::u64 bits = e[k] >> 1; + elf::u64 bits = entry >> 1; for (elf::u64 i = 0; bits; ++i, bits >>= 1) - if (bits & 1) add(next + 8 * i); - next += 63 * 8; + if (bits & 1) add(next + w * i); + next += (w * 8 - 1) * w; } } } diff --git a/ld/src/read.cpp b/ld/src/read.cpp index ef03379..efa6392 100644 --- a/ld/src/read.cpp +++ b/ld/src/read.cpp @@ -15,7 +15,7 @@ size view::symbol_count() const { const auto* head = at(g, 4); if (!head) return 0; const elf::u32 nbuckets = head[0], symoffset = head[1], bloom = head[2]; - const auto buckets_at = g + 16 + static_cast(bloom) * 8; + const auto buckets_at = g + 16 + static_cast(bloom) * word(); const auto* buckets = at(buckets_at, nbuckets); if (!buckets) return 0; elf::u32 last = 0; @@ -29,6 +29,51 @@ size view::symbol_count() const { } } +namespace { +// An i386 image (ELFCLASS32, EM_386): its header, program headers and dynamic +// table widened into the view's own storage. +bool open32(view& v, sentence& s) { + const auto* e = v.at(0); + if (!e) { s << "shorter than an ELF header"; return false; } + if (e->type != elf::et_dyn) { s << "not position-independent (ET_DYN; SPEC 12.2)"; return false; } + if (e->machine != elf::em_386) { + s << "the architecture "; s.num(e->machine); s << " has no fixed convention in the 32-bit class (SPEC 12.2)"; + return false; + } + if (e->phentsize != sizeof(elf::phdr32)) { s << "program headers of an unexpected size"; return false; } + if (e->phnum > view::max_ph32) { s << "more program headers than a 32-bit image is read with"; return false; } + const auto* ph = v.at(e->phoff, e->phnum); + if (!ph) { s << "program headers beyond the file"; return false; } + v.wide = false; + auto& w = v.eh_wide; + copy(w.ident, e->ident, sizeof w.ident); + w.type = e->type; w.machine = e->machine; w.version = e->version; + w.entry = e->entry; w.phoff = e->phoff; w.shoff = e->shoff; + w.flags = e->flags; w.ehsize = e->ehsize; w.phentsize = e->phentsize; w.phnum = e->phnum; + w.shentsize = e->shentsize; w.shnum = e->shnum; w.shstrndx = e->shstrndx; + v.eh = &v.eh_wide; + for (unsigned i = 0; i < e->phnum; ++i) + v.ph_wide[i] = elf::phdr{ ph[i].type, ph[i].flags, ph[i].offset, ph[i].vaddr, ph[i].paddr, + ph[i].filesz, ph[i].memsz, ph[i].align }; + v.ph = v.ph_wide; + for (unsigned i = 0; i < e->phnum; ++i) + if (v.ph[i].type == elf::pt_dynamic) v.dynamic_ph = &v.ph[i]; + if (v.dynamic_ph) { + const auto n = v.dynamic_ph->filesz / sizeof(elf::dyn32); + const auto* d = v.at(v.dynamic_ph->offset, n); + if (!d) { s << "a dynamic table beyond the file"; return false; } + size k = 0; + for (; k < n && d[k].tag != elf::dt_null; ++k) { + if (k + 1 >= view::max_dyn32) { s << "a dynamic table longer than a 32-bit image is read with"; return false; } + v.dyn_wide[k] = elf::dyn{ d[k].tag, d[k].val }; + } + v.dyn_wide[k] = elf::dyn{ elf::dt_null, 0 }; + v.ndyn_wide = k + 1; + } + return true; +} +} // namespace + bool open(const void* bytes, size n, view& v, char* why, size cap) { sentence s(why, cap); v.data = static_cast(bytes); @@ -39,10 +84,16 @@ bool open(const void* bytes, size n, view& v, char* why, size cap) { if (!(e.ident[0] == 0x7f && e.ident[1] == 'E' && e.ident[2] == 'L' && e.ident[3] == 'F')) { s << "not ELF: an image is ELF (SPEC 12.2)"; return false; } - if (e.ident[4] != 2 || e.ident[5] != 1) { s << "not 64-bit little-endian ELF (SPEC 12.2)"; return false; } + if (e.ident[5] != 1) { s << "not little-endian ELF (SPEC 12.2)"; return false; } + // The class is the architecture's (SPEC 12.2): sixty-four bits for x86-64 and + // AArch64, thirty-two for i386 (clause 5.5). + if (e.ident[4] == 1) return open32(v, s); + if (e.ident[4] != 2) { s << "neither 32-bit nor 64-bit ELF (SPEC 12.2)"; return false; } if (e.type != elf::et_dyn) { s << "not position-independent (ET_DYN; SPEC 12.2)"; return false; } if (e.machine != elf::em_x86_64 && e.machine != elf::em_aarch64) { - s << "the architecture "; s.num(e.machine); s << " has no fixed convention (SPEC 12.2)"; return false; + s << "the architecture "; s.num(e.machine); + s << (e.machine == elf::em_386 ? " is of the 32-bit class (SPEC 12.2)" : " has no fixed convention (SPEC 12.2)"); + return false; } if (e.phentsize != sizeof(elf::phdr)) { s << "program headers of an unexpected size"; return false; } v.ph = v.at(e.phoff, e.phnum); @@ -126,13 +177,11 @@ void each_import(const void* bytes, okld_size n, bool weak_too, void (*each)(con view v; char why[8]; if (!open(bytes, n, v, why, sizeof why)) return; - const auto* syms = v.symbols(); const auto count = v.symbol_count(); - if (!syms) return; - for (size k = 1; k < count; ++k) - if (syms[k].shndx == elf::shn_undef && syms[k].name - && (weak_too || elf::st_bind(syms[k].info) != elf::stb_weak)) - if (const char* name = v.string(syms[k].name); name && *name) each(name, ctx); + elf::sym s{}; + for (size k = 1; k < count && v.symbol(k, s); ++k) + if (s.shndx == elf::shn_undef && s.name && (weak_too || elf::st_bind(s.info) != elf::stb_weak)) + if (const char* name = v.string(s.name); name && *name) each(name, ctx); } } // namespace @@ -148,12 +197,11 @@ extern "C" void okld_exports(const void* bytes, okld_size n, void (*each)(const view v; char why[8]; if (!open(bytes, n, v, why, sizeof why)) return; - const auto* syms = v.symbols(); const auto count = v.symbol_count(); - if (!syms) return; - for (size k = 1; k < count; ++k) - if (syms[k].shndx != elf::shn_undef && elf::st_bind(syms[k].info) != elf::stb_local && syms[k].name) - if (const char* name = v.string(syms[k].name); name && *name) each(name, ctx); + elf::sym s{}; + for (size k = 1; k < count && v.symbol(k, s); ++k) + if (s.shndx != elf::shn_undef && elf::st_bind(s.info) != elf::stb_local && s.name) + if (const char* name = v.string(s.name); name && *name) each(name, ctx); } namespace { @@ -166,49 +214,51 @@ struct checker { // writes hold what it computes for the base, and every other word is the // file's. Counted rather than copied, since nothing here allocates. void verify_placement(const view& v, const placement& p, checker& c) { - const bool x86 = v.eh->machine == elf::em_x86_64; - const elf::u32 relative = x86 ? elf::r_x86_64_relative : elf::r_aarch64_relative; + const elf::u32 relative = relative_type(v); + const elf::u64 w = v.word(); for (elf::u32 i = 0; i < p.count; ++i) { const auto& e = p.e[i]; const elf::u64 file = v.offset_of(e.vaddr); if (file == ~0ull) { c.fail("a placed segment has no bytes in the file"); return; } const unsigned char* was = v.data + file; const unsigned char* now = v.data + e.offset; - auto word = [](const unsigned char* b, elf::u64 at) { elf::u64 w = 0; copy(&w, b + at, 8); return w; }; - auto inside = [&](elf::u64 at) { return at >= e.vaddr && at <= e.vaddr + e.size && e.size - (at - e.vaddr) >= 8; }; + auto word = [&](const unsigned char* b, elf::u64 at) { return v.word_of(b + at); }; + auto inside = [&](elf::u64 at) { return at >= e.vaddr && at <= e.vaddr + e.size && e.size - (at - e.vaddr) >= w; }; + const elf::u64 mask = w == 8 ? ~0ull : 0xffffffffull; elf::u64 written = 0, wrong = 0; auto expect = [&](elf::u64 at, elf::u64 value) { if (!inside(at)) return; - if (word(was, at - e.vaddr) != value) ++written; - if (word(now, at - e.vaddr) != value) ++wrong; + if (word(was, at - e.vaddr) != (value & mask)) ++written; + if (word(now, at - e.vaddr) != (value & mask)) ++wrong; }; auto table = [&](elf::i64 tag, elf::i64 size_tag) { - const auto at = v.offset_of(v.dynamic(tag, ~0ull)); - const auto n = v.dynamic(size_tag); - const auto* r = at == ~0ull ? nullptr : v.at(at, n / sizeof(elf::rela)); - for (size k = 0; r && k < n / sizeof(elf::rela); ++k) - if (elf::r_type(r[k].info) == relative) expect(r[k].offset, p.base + static_cast(r[k].addend)); + const auto n = v.relocation_count(tag, size_tag); + for (size k = 0; k < n; ++k) { + const auto r = v.relocation(tag, k); + if (elf::r_type(r.info) == relative) expect(r.offset, p.base + static_cast(r.addend)); + } }; table(elf::dt_rela, elf::dt_relasz); table(elf::dt_jmprel, elf::dt_pltrelsz); const auto relr = v.offset_of(v.dynamic(elf::dt_relr, ~0ull)); - const auto relr_n = v.dynamic(elf::dt_relrsz) / 8; - const auto* rr = relr == ~0ull ? nullptr : v.at(relr, relr_n); + const auto relr_n = v.dynamic(elf::dt_relrsz) / w; + const auto* rr = relr == ~0ull ? nullptr : v.at(relr, relr_n * w); elf::u64 next = 0; for (size k = 0; rr && k < relr_n; ++k) { - if ((rr[k] & 1) == 0) { - if (inside(rr[k])) expect(rr[k], word(was, rr[k] - e.vaddr) + p.base); - next = rr[k] + 8; + const elf::u64 entry = v.word_of(rr + k * w); + if ((entry & 1) == 0) { + if (inside(entry)) expect(entry, word(was, entry - e.vaddr) + p.base); + next = entry + w; } else { - elf::u64 bits = rr[k] >> 1; + elf::u64 bits = entry >> 1; for (elf::u64 j = 0; bits; ++j, bits >>= 1) - if ((bits & 1) && inside(next + 8 * j)) expect(next + 8 * j, word(was, next + 8 * j - e.vaddr) + p.base); - next += 63 * 8; + if ((bits & 1) && inside(next + w * j)) expect(next + w * j, word(was, next + w * j - e.vaddr) + p.base); + next += (w * 8 - 1) * w; } } elf::u64 differ = 0; - for (elf::u64 at = 0; at + 8 <= e.size; at += 8) if (word(was, at) != word(now, at)) ++differ; - for (elf::u64 at = e.size & ~elf::u64{7}; at < e.size; ++at) if (was[at] != now[at]) ++differ; + for (elf::u64 at = 0; at + w <= e.size; at += w) if (word(was, at) != word(now, at)) ++differ; + for (elf::u64 at = e.size & ~(w - 1); at < e.size; ++at) if (was[at] != now[at]) ++differ; if (wrong != 0 || differ != written) { c.fail("a placed segment is not its file bytes with the relative relocations applied for the base (SPEC 12.4)"); return; @@ -243,20 +293,25 @@ extern "C" int okld_check(const void* bytes, okld_size n, okld_name_test known_k if (!v.dynamic_ph) c.fail("has no dynamic section: an image is linked -shared or -pie"); if (v.dynamic(elf::dt_textrel, ~0ull) != ~0ull || (v.dynamic(elf::dt_flags) & elf::df_textrel)) c.fail("relocates its text (SPEC 12.2: no text relocation)"); - if (v.dynamic(elf::dt_rel, ~0ull) != ~0ull) c.fail("uses REL relocations; an image uses RELA (SPEC 12.2)"); + if (v.dynamic(elf::dt_rel, ~0ull) != ~0ull) + c.fail(v.wide ? "uses REL relocations; an image uses RELA (SPEC 12.2)" + : "uses REL relocations; an image uses RELA (SPEC 12.2; link an i386 image with -z rela)"); // Relocation types. auto types = [&](elf::i64 tag, elf::i64 size_tag) { const auto at = v.offset_of(v.dynamic(tag, ~0ull)); const auto bytes_ = v.dynamic(size_tag); if (at == ~0ull || bytes_ == 0) return; - const auto* r = v.at(at, bytes_ / sizeof(elf::rela)); - if (!r) { c.fail("relocations beyond the file"); return; } - for (size k = 0; k < bytes_ / sizeof(elf::rela); ++k) { - const auto t = elf::r_type(r[k].info); + if (!v.at(at, bytes_)) { c.fail("relocations beyond the file"); return; } + const auto n = v.relocation_count(tag, size_tag); + for (size k = 0; k < n; ++k) { + const auto t = elf::r_type(v.relocation(tag, k).info); const bool ok = v.eh->machine == elf::em_x86_64 ? (t == elf::r_x86_64_none || t == elf::r_x86_64_64 || t == elf::r_x86_64_glob_dat || t == elf::r_x86_64_jump_slot || t == elf::r_x86_64_relative) + : v.eh->machine == elf::em_386 + ? (t == elf::r_386_none || t == elf::r_386_32 || t == elf::r_386_glob_dat + || t == elf::r_386_jmp_slot || t == elf::r_386_relative) : (t == elf::r_aarch64_none || t == elf::r_aarch64_abs64 || t == elf::r_aarch64_glob_dat || t == elf::r_aarch64_jump_slot || t == elf::r_aarch64_relative); if (!ok) { @@ -271,15 +326,15 @@ extern "C" int okld_check(const void* bytes, okld_size n, okld_name_test known_k types(elf::dt_jmprel, elf::dt_pltrelsz); // Imports: kal_* that the specification declares, or another image's. - const auto* syms = v.symbols(); const auto count = v.symbol_count(); - for (size k = 1; syms && k < count; ++k) { - if (syms[k].shndx != elf::shn_undef || !syms[k].name) continue; - const char* name = v.string(syms[k].name); + elf::sym sk{}; + for (size k = 1; k < count && v.symbol(k, sk); ++k) { + if (sk.shndx != elf::shn_undef || !sk.name) continue; + const char* name = v.string(sk.name); if (!name || !*name) continue; const bool kal = starts(name, "kal_") && (!known_kal || known_kal(name, ctx)); const bool other = exported_elsewhere && exported_elsewhere(name, ctx); - if (!kal && !other && elf::st_bind(syms[k].info) != elf::stb_weak) { + if (!kal && !other && elf::st_bind(sk.info) != elf::stb_weak) { sentence s(why, sizeof why); s << "imports '" << name << "', which is neither a name openkal declares nor one another image exports (SPEC 12.2)"; c.fail(why); @@ -348,6 +403,24 @@ extern "C" int okld_set_requirements(void* bytes, okld_size n, okld_u64 interfac } // --- the symbols of an image's code and data (0.2) ----------------------------- +namespace { +// A section header, widened; false beyond the file. +bool section(const okld::view& v, unsigned i, okld::elf::shdr& out) { + using namespace okld; + if (v.wide) { + const auto* sh = v.at(v.eh->shoff + static_cast(i) * sizeof(elf::shdr)); + if (!sh) return false; + out = *sh; + return true; + } + const auto* sh = v.at(v.eh->shoff + static_cast(i) * sizeof(elf::shdr32)); + if (!sh) return false; + out = elf::shdr{ sh->name, sh->type, sh->flags, sh->addr, sh->offset, sh->size, sh->link, sh->info, + sh->addralign, sh->entsize }; + return true; +} +} // namespace + extern "C" okld_u64 okld_symbols(const void* bytes, okld_size size, void (*each)(okld_u64, okld_u64, const char*, void*), void* ctx) { using namespace okld; @@ -356,28 +429,31 @@ extern "C" okld_u64 okld_symbols(const void* bytes, okld_size size, if (!bytes || !each || !open(bytes, size, v, why, sizeof why)) return 0; // The section table's symbol table, with its string table; else the // dynamic one, whose names are the exports alone. - const elf::sym* syms = nullptr; + elf::u64 table = ~0ull; elf::u64 count = 0; const char* strings = nullptr; elf::u64 strings_size = 0; - if (v.eh->shoff && v.eh->shentsize == sizeof(elf::shdr)) { - const auto* sh = v.at(v.eh->shoff, v.eh->shnum); - for (unsigned i = 0; sh && i < v.eh->shnum && !syms; ++i) { - if (sh[i].type != elf::sht_symtab || sh[i].link >= v.eh->shnum) continue; - const auto& str = sh[sh[i].link]; - const auto n = sh[i].size / sizeof(elf::sym); - const auto* t = v.at(sh[i].offset, n); + const elf::u64 shentsize = v.wide ? sizeof(elf::shdr) : 40; + if (v.eh->shoff && v.eh->shentsize == shentsize) { + for (unsigned i = 0; i < v.eh->shnum && table == ~0ull; ++i) { + elf::shdr sh{}, str{}; + if (!section(v, i, sh) || sh.type != elf::sht_symtab || sh.link >= v.eh->shnum || !section(v, sh.link, str)) + continue; + const auto n = sh.size / v.symbol_size(); const auto* s = v.at(str.offset, str.size); - if (t && s) { syms = t; count = n; strings = s; strings_size = str.size; } + if (v.at(sh.offset, n * v.symbol_size()) && s) { + table = sh.offset; count = n; strings = s; strings_size = str.size; + } } } - if (!syms) { - syms = v.symbols(); - count = syms ? v.symbol_count() : 0; + if (table == ~0ull) { + table = v.offset_of(v.dynamic(elf::dt_symtab, ~0ull)); + count = table == ~0ull ? 0 : v.symbol_count(); } okld_u64 given = 0; for (elf::u64 k = 0; k < count; ++k) { - const auto& s = syms[k]; + elf::sym s{}; + if (!v.symbol_at(table, k, s)) break; const unsigned type = s.info & 0xf; if ((type != 1 /* STT_OBJECT */ && type != 2 /* STT_FUNC */) || s.value == 0 || s.shndx == elf::shn_undef || s.shndx == elf::shn_abs) continue; diff --git a/run/build.mcpp b/run/build.mcpp index 9eea7fc..8c71bad 100644 --- a/run/build.mcpp +++ b/run/build.mcpp @@ -82,8 +82,11 @@ int main() { // of the calls an image makes (okprof, 0.2) keeps its counts. for (std::size_t i = 0; i < names.size(); ++i) { const auto& n = names[i]; - o << (own.contains(n) ? "OKRUN_OWN(" : callbacks.contains(n) ? "OKRUN_CALLBACK(" : - provided.contains(group_of[n]) ? "OKRUN_BIND(" : "OKRUN_ABSENT(") + // A callback of an interface the implementation does not provide is absent, as + // its other names are (0.6.3: a context cannot be started where there are none). + o << (own.contains(n) ? "OKRUN_OWN(" : + !provided.contains(group_of[n]) ? "OKRUN_ABSENT(" : + callbacks.contains(n) ? "OKRUN_CALLBACK(" : "OKRUN_BIND(") << n << ", " << i << ")\n"; } }; diff --git a/run/mcpp.toml b/run/mcpp.toml index 2d39a3e..30ce355 100644 --- a/run/mcpp.toml +++ b/run/mcpp.toml @@ -1,14 +1,15 @@ [package] namespace = "mcpplibs" name = "openkal-run" -version = "0.6.2" +version = "0.6.3" description = "libokrun: the runner's core --- an ok image's names bound to this process's one openkal implementation, in the image's calling convention, and its entry entered." license = "Apache-2.0" authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-image" # `[defaults]' (mcpp 2026.11.9.1, SPEC-002 ยง3.7); the implementation's -# interfaces read by build.mcpp (MCPP_KERNEL_ABI_INTERFACES, 2026.11.10.1). -mcpp = ">=2026.11.10.1" +# interfaces read by build.mcpp (MCPP_KERNEL_ABI_INTERFACES, 2026.11.10.1); the +# runner's sources selected by them (`cfg(interface = ...)', 2026.11.13.1). +mcpp = ">=2026.11.13.1" # The runner binds an image's names to this process's one implementation of # openkal: whichever the graph has --- the consumer's, or the one its # platform adds --- else the default of the system below. @@ -27,22 +28,32 @@ macos = "openkal-macos@^0.23.0" windows = "openkal-windows@^0.21.0" 'cfg(os = "freebsd")' = "openkal-freebsd@^0.1.0" -# THE INTERFACES THE RUNNER ITSELF CALLS (0.6): an implementation that does not -# provide one of them is refused when the graph is resolved. Every other -# interface is bound where the implementation provides it (build.mcpp reads -# its `provides-interfaces'), and an image that requires one it does not is -# refused at load, naming it (SPEC 12.4). An implementation that states no list -# is taken to provide every interface. +# THE INTERFACES THE RUNNER ITSELF CALLS (0.6; 0.6.3 the core runner): an implementation that does +# not provide one of them is refused when the graph is resolved. Every other interface is bound +# where the implementation provides it (build.mcpp reads its `provides-interfaces'), and an image +# that requires one it does not is refused at load, naming it (SPEC 12.4). An implementation that +# states no list is taken to provide every interface. [kernel-abi] requires-interfaces = [ - "openkal.abort", "openkal.stream", "openkal.memory", "openkal.env", "openkal.time", - "openkal.random", "openkal.fs", "openkal.process", "openkal.task", "openkal.exec", - "openkal.map", + "openkal.abort", "openkal.stream", "openkal.memory", "openkal.env", "openkal.time", "openkal.exec", ] [build] -sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp", "src/seal.cpp", "src/prof.cpp"] +sources = ["src/okrun.cppm", "src/bindings.cpp", "src/adapters.cpp"] include_dirs = ["include"] flags = [ { glob = "src/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] }, + { glob = "src/full/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] }, + { glob = "src/core/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] }, ] + +# ONE INTERFACE, TWO RUNNERS (0.6.3), chosen by what the implementation provides. The full runner +# reads images from files, keeps a cache and snapshots, starts itself again and digests in +# parallel; the core runner runs what the program carries, from memory --- on firmware, or any +# implementation without files, processes or threads. Both answer the same C interface +# (include/okrun.h). +[target.'cfg(all(interface = "openkal.fs", interface = "openkal.process", interface = "openkal.task", interface = "openkal.random", interface = "openkal.map"))'.build] +sources = ["src/full/*.cpp"] + +[target.'cfg(not(all(interface = "openkal.fs", interface = "openkal.process", interface = "openkal.task", interface = "openkal.random", interface = "openkal.map")))'.build] +sources = ["src/core/*.cpp"] diff --git a/run/src/adapters.cpp b/run/src/adapters.cpp new file mode 100644 index 0000000..311e1c6 --- /dev/null +++ b/run/src/adapters.cpp @@ -0,0 +1,37 @@ +// The implementation's measurement and names (openkal.prof, openkal.debug), for both runners. +#include +#include "runner.h" + +namespace okrun { + +// THE IMPLEMENTATION'S MEASUREMENT AND NAMES (openkal.prof, openkal.debug), +// referred to only where it provides them (0.6): an implementation without +// them is not asked to define a name it does not have (SPEC 6.1). +#if OKRUN_HAS_PROF +kal_uintptr impl_prof_props() { return kal_prof_props(); } +int impl_prof_sample_begin(kal_u32 per_second) { return kal_prof_sample_begin(per_second); } +kal_intptr impl_prof_sample_take(kal_prof_sample* out, kal_uintptr count) { return kal_prof_sample_take(out, count); } +int impl_prof_sample_end() { return kal_prof_sample_end(); } +int impl_prof_touch_begin(const void* at, kal_uintptr size) { return kal_prof_touch_begin(at, size); } +kal_intptr impl_prof_touch_take(kal_uintptr* out, kal_uintptr count) { return kal_prof_touch_take(out, count); } +int impl_prof_touch_end() { return kal_prof_touch_end(); } +#else +kal_uintptr impl_prof_props() { return 0; } +int impl_prof_sample_begin(kal_u32) { return kal_err_not_supported; } +kal_intptr impl_prof_sample_take(kal_prof_sample*, kal_uintptr) { return 0; } +int impl_prof_sample_end() { return kal_err_invalid; } +int impl_prof_touch_begin(const void*, kal_uintptr) { return kal_err_not_supported; } +kal_intptr impl_prof_touch_take(kal_uintptr*, kal_uintptr) { return 0; } +int impl_prof_touch_end() { return kal_err_invalid; } +#endif +#if OKRUN_HAS_DEBUG +kal_uintptr impl_debug_props() { return kal_debug_props(); } +kal_intptr impl_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start) { + return kal_debug_symbolize(address, out, cap, start); +} +#else +kal_uintptr impl_debug_props() { return 0; } +kal_intptr impl_debug_symbolize(kal_uintptr, char*, kal_uintptr, kal_uintptr*) { return -kal_err_not_found; } +#endif + +} // namespace okrun diff --git a/run/src/bindings.cpp b/run/src/bindings.cpp index 517a6b3..5f863a2 100644 --- a/run/src/bindings.cpp +++ b/run/src/bindings.cpp @@ -52,6 +52,7 @@ void enter_image(void* p) { b.fn(b.arg); } +#if OKRUN_HAS_TASK OKRUN_IMAGE_ABI int callback_kal_task_start(image_entry fn, void* arg, struct kal_task* out) { auto* b = static_cast(kal_alloc(sizeof(box), alignof(box))); if (!b) return kal_err_no_memory; @@ -69,6 +70,7 @@ OKRUN_IMAGE_ABI int callback_kal_task_start_sized(image_entry fn, void* arg, kal if (rc != kal_ok) kal_free(b, sizeof(box), alignof(box)); return rc; } +#endif // OKRUN_HAS_TASK // The image's own arguments and name (SPEC 12.3), answered by the runner. kal_intptr give(const char* v, kal_uintptr n, char* out, kal_uintptr cap) { @@ -224,3 +226,42 @@ void* space_props_binding() { return nullptr; } #endif } // namespace okrun + +// --- the names a runner binds, and a call's floating-point state: both runners' (0.6.3) ---- + +using namespace okrun; + +extern "C" const char* const* okrun_names(unsigned long* count) { + static const char* names[256]; + static unsigned long n = 0; + if (n == 0) { + unsigned long k = 0; + const binding* t = bindings(k); + for (unsigned long i = 0; i < k && n < 256; ++i) names[n++] = t[i].name; + t = space_bindings(k); + for (unsigned long i = 0; t && i < k && n < 256; ++i) names[n++] = t[i].name; + } + if (count) *count = n; + return names; +} + +// A NAME'S SLOT, from a hash under which no two of the surface's names share +// one (build.mcpp, 0.4): one name compared, where every name was. +#include "surface_hash.inc" + +extern "C" void* okrun_resolve(const char* name) { + if (!name) return nullptr; + unsigned h = 2166136261u ^ kResolveSeed; + for (const char* c = name; *c; ++c) { h ^= static_cast(*c); h *= 16777619u; } + const unsigned short slot = kResolveSlot[h % kResolveSlots]; + if (slot == 0) return nullptr; + unsigned long n = 0; + const unsigned long i = slot - 1u; + const binding* t = i < kResolveMain ? bindings(n) : space_bindings(n); + const unsigned long k = i < kResolveMain ? i : i - kResolveMain; + if (!t || k >= n || !same(t[k].name, name)) return nullptr; + return t[k].address; +} + +extern "C" unsigned long long okrun_call_enter(void) { return okrun_fp_enter(); } +extern "C" void okrun_call_leave(unsigned long long state) { okrun_fp_leave(state); } diff --git a/run/src/core/prof.cpp b/run/src/core/prof.cpp new file mode 100644 index 0000000..b38f08c --- /dev/null +++ b/run/src/core/prof.cpp @@ -0,0 +1,26 @@ +// The core runner measures nothing (openkal-image 0.6.3). A measurement writes files and names +// processes, which an implementation without openkal.fs and openkal.process does not have; the +// calls the shared bindings make are answered here as the full runner answers them unasked. +#include +#include "../runner.h" + +namespace okrun { + +bool profile_active() { return false; } +bool profile_calls() { return false; } +bool profile_samples() { return false; } +bool profile_touches() { return false; } +void profile_phase(const char*) {} +void profile_note(const char*, const char*, kal_u64) {} +void profile_begin_run() {} +void record_call(unsigned long, kal_u64) {} +int profile_counter(const char*, kal_uintptr, const volatile kal_u64*) { return kal_err_not_supported; } +void profile_mark(const char*, kal_uintptr, int) {} +void profile_finish() {} +void profile_loaded(const char*, const void*, kal_u64, const char*, kal_u64) {} +void profile_settle() {} +kal_intptr symbolize(kal_uintptr, char*, kal_uintptr, kal_uintptr*) { return -kal_err_not_found; } + +} // namespace okrun + +extern "C" void okrun_profile(const char*) {} diff --git a/run/src/core/run.cpp b/run/src/core/run.cpp new file mode 100644 index 0000000..7206ca4 --- /dev/null +++ b/run/src/core/run.cpp @@ -0,0 +1,319 @@ +// THE CORE RUNNER (0.6.3): an image, or an archive of images, that the program carries runs from +// memory where the implementation provides no file system, processes or contexts --- firmware, +// a single-task system (mcpp.toml, `cfg(interface = ...)`). Its code is placed through openkal.exec, the names +// it imports are bound as the full runner binds them (bindings.cpp), and the images it needs are +// the archive's. It keeps no cache and makes no snapshot: there is nowhere to keep one. The C +// interface is the full runner's; what needs a file answers that this runner reads none. +#include +#include +#include + +#include "../image_abi.h" +#include "../runner.h" + + +namespace okrun { + +namespace { + +image_state g_state; + +struct text { + char* out; unsigned long long cap; unsigned long long at = 0; + text(char* o, unsigned long long c) : out(o), cap(c) { if (cap) out[0] = 0; } + text& operator<<(const char* s) { while (s && *s && at + 1 < cap) out[at++] = *s++; if (cap) out[at] = 0; return *this; } +}; + +unsigned long length(const char* s) { unsigned long n = 0; while (s && s[n]) ++n; return n; } +void copy_text(char* d, unsigned long cap, const char* s) { + unsigned long i = 0; + for (; s && s[i] && i + 1 < cap; ++i) d[i] = s[i]; + if (cap) d[i] = 0; +} + +// --- the host the loader asks ------------------------------------------------------------------ +void* reserve(okld_size n, void*) { return kal_exec_alloc(n); } +void* reserve_at(void* at, okld_size n, void*) { return kal_exec_alloc_at(at, n); } +int publish(void* region, okld_size off, okld_size n, void*) { return kal_exec_publish_part(region, off, n); } +int seal(void* region, okld_size off, okld_size n, int forever, void*) { + return kal_exec_seal_part(region, off, n, forever ? KAL_EXEC_SEAL_FOREVER : 0); +} +void release(void* region, okld_size n, void*) { kal_exec_free(region, n); } +okld_size granularity(void*) { return kal_exec_granularity(); } +void* resolve(const char* name, void*) { return okrun_resolve(name); } +void* allocate(okld_size n, void*) { return kal_alloc(n, 16); } +void deallocate(void* p, okld_size n, void*) { kal_free(p, n, 16); } + +// --- the images a run holds: a group in memory, or an archive's members ------------------------ +struct search { + const okrun_options* options = nullptr; + const okrun_image* images = nullptr; + unsigned long image_count = 0; + const unsigned char* archive = nullptr; + unsigned long long archive_size = 0; + const void* json = nullptr; + unsigned long long json_size = 0; + char arch_dir[64] = {}; + char refusal[256] = {}; + okld_host host{}; +}; +search g_search; + +// A member of the archive: stored, its bytes in place; compressed, expanded into memory the run +// keeps (the loader keeps no pointer into the bytes, but the run never returns to free them). +const void* archive_member(search& s, const char* member, unsigned long long& size) { + okld_zip_member m{}; + okld_why why{}; + if (okld_zip_find(s.archive, s.archive_size, member, &m, &why) != 0) return nullptr; + if (m.method == 8) { + void* out = kal_alloc(m.original ? m.original : 1, 16); + if (!out) { copy_text(s.refusal, sizeof s.refusal, "no memory to expand a compressed member"); return nullptr; } + if (okld_okb_expand(s.archive, s.archive_size, &m, s.json, s.json_size, out, m.original, &why) != 0) { + copy_text(s.refusal, sizeof s.refusal, why.text[0] ? why.text : "a compressed member could not be expanded"); + kal_free(out, m.original ? m.original : 1, 16); + return nullptr; + } + size = m.original; + return out; + } + if (okld_okb_admit(s.archive, s.archive_size, &m, s.json, s.json_size, &why) != 0) { + copy_text(s.refusal, sizeof s.refusal, why.text); + return nullptr; + } + size = m.size; + return s.archive + m.data; +} + +bool join(char* out, unsigned long cap, const char* a, const char* b) { + text t(out, cap); + t << a << "/" << b; + return t.at + 1 < cap; +} + +const void* archive_image(search& s, const char* name, unsigned long long& size) { + char member[512]; + if (s.arch_dir[0] && join(member, sizeof member, s.arch_dir, name)) + if (const void* p = archive_member(s, member, size)) return p; + if (s.refusal[0]) return nullptr; + return archive_member(s, name, size); +} + +const void* find_needed(const char* name, okld_size* size, void* ctx) { + auto& s = *static_cast(ctx); + for (unsigned long i = 0; i < s.image_count; ++i) + if (same(s.images[i].name, name)) { *size = s.images[i].size; return s.images[i].bytes; } + if (s.archive) { + unsigned long long n = 0; + if (const void* p = archive_image(s, name, n)) { *size = n; return p; } + } + return nullptr; +} +void release_needed(const void*, okld_size, void*) {} + +const char* interface_name(int bit) { + static const char* const names[] = { "openkal.abort", "openkal.stream", "openkal.memory", "openkal.env", + "openkal.time", "openkal.random", "openkal.fs", "openkal.process", "openkal.task", "openkal.exec", + "openkal.terminal", "openkal.net", "openkal.datagram", "openkal.space", "openkal.timeout", + "openkal.map", "openkal.region", "openkal.prof", "openkal.debug" }; + return bit >= 0 && bit < static_cast(sizeof names / sizeof *names) ? names[bit] : "an interface"; +} + +bool key_is_trusted(const okrun_options* options, const unsigned char key[32]) { + if (!options || !options->trusted_count) return true; + for (unsigned i = 0; i < options->trusted_count; ++i) { + bool same_key = true; + for (int k = 0; k < 32; ++k) if (options->trusted[i * 32 + k] != key[k]) { same_key = false; break; } + if (same_key) return true; + } + return false; +} + +// What every image is asked before it is loaded, as the full runner asks it. +bool admit(const void* bytes, unsigned long long size, const char* name, const okrun_options* options, + char* why, unsigned long long cap) { + okld_info info{}; + okld_why reason{}; + if (okld_read(bytes, size, &info, &reason) != 0) { text(why, cap) << name << ": " << reason.text; return false; } + if (!info.has_requirements) { text(why, cap) << name << ": no requirements note (SPEC 12.4); not an image"; return false; } + if (info.format_major > OKLD_FORMAT_MAJOR) { text(why, cap) << name << ": written to a later version of the format than this runner reads"; return false; } + if (const char* arch = okld_okb_arch_of(info.machine); !arch || !same(arch, okrun_arch)) { + text(why, cap) << name << ": an image of " << (arch ? arch : "another architecture") << ", and this machine is " << okrun_arch; + return false; + } + if (const unsigned long long missing = info.interfaces & ~own_interfaces()) { + text t(why, cap); + t << name << " requires"; + for (int bit = 0; bit < 64; ++bit) if (missing & (1ull << bit)) t << " " << interface_name(bit); + t << ", which this system's openkal does not provide"; + return false; + } + if (options && (options->require_signature || options->trusted_count)) { + unsigned char key[32] = {}; + const int v = okld_verify(bytes, size, key); + if (v < 0) { text(why, cap) << name << ": its signature does not hold"; return false; } + if (v == 0) { text(why, cap) << name << ": it is not signed, and a signature is required"; return false; } + if (!key_is_trusted(options, key)) { text(why, cap) << name << ": it is signed by a key that is not trusted"; return false; } + } + return true; +} + +using image_function = void (OKRUN_IMAGE_ABI*)(void); +using image_main = int (OKRUN_IMAGE_ABI*)(void); +void call_image(void* fn, void*) { + const unsigned long long fp = okrun_fp_enter(); + reinterpret_cast(fn)(); + okrun_fp_leave(fp); +} + +// Loads an admitted image with the images it needs, and enters it. Returns only when it could not +// be started. +int enter(search& s, const void* bytes, unsigned long long size, const char* name, + int argc, const char* const* argv, char* why, unsigned long long cap) { + if (!admit(bytes, size, name, s.options, why, cap)) return -1; + g_state.argc = argc; + g_state.argv = argv; + g_state.name = name; + g_state.carried = s.options && s.options->carried; + g_state.started = true; + okld_host& h = s.host; + h.reserve = &reserve; h.publish = &publish; h.release = &release; h.map = nullptr; + h.granularity = &granularity; h.resolve = &resolve; + h.alloc = &allocate; h.free = &deallocate; + h.find_needed = &find_needed; h.release_bytes = &release_needed; + h.reserve_at = (kal_exec_props() & KAL_EXEC_PROP_PLACE) ? &reserve_at : nullptr; + h.seal = &seal; + h.debugger = s.options ? s.options->debugger : 0; + h.permanent = 1; + h.prefetch = 0; + h.ctx = &s; + okld_module* m = nullptr; + okld_why reason{}; + if (okld_load(bytes, size, name, &h, &m, &reason) != 0) { + text t(why, cap); + t << name << ": " << (s.refusal[0] ? s.refusal : reason.text); + return -1; + } + void* entry = okld_entry(m); + if (!entry) { text(why, cap) << name << ": the image has no entry"; return -1; } + okrun_fp_enter(); + okld_initialise(m, &call_image, nullptr); + const int status = reinterpret_cast(entry)(); + okld_finalise(m, &call_image, nullptr); + kal_exit(status); +} + +int run_archive(search& s, const char* entry, const char* name, int argc, const char* const* argv, + char* why, unsigned long long cap) { + okld_why reason{}; + okld_zip_member json{}; + const int has_json = okld_zip_find(s.archive, s.archive_size, "okb.json", &json, &reason); + if (has_json < 0) { text(why, cap) << name << ": " << reason.text; return -1; } + okld_okb_manifest manifest{}; + if (has_json == 0) { + if (okld_okb_admit(s.archive, s.archive_size, &json, nullptr, 0, &reason) != 0 + || okld_okb_read_manifest(s.archive + json.data, json.size, &manifest, &reason) != 0) { + text(why, cap) << name << ": " << reason.text; + return -1; + } + s.json = s.archive + json.data; + s.json_size = json.size; + const okld_okb_arch* mine = nullptr; + for (okld_u32 i = 0; i < manifest.arch_count; ++i) + if (same(manifest.archs[i].name, okrun_arch)) mine = &manifest.archs[i]; + if (!mine) { text(why, cap) << name << " holds no image for " << okrun_arch; return -1; } + copy_text(s.arch_dir, sizeof s.arch_dir, mine->dir); + if (!entry) entry = mine->entry; + } else { + copy_text(s.arch_dir, sizeof s.arch_dir, okrun_arch); + } + if (!entry || !*entry) { text(why, cap) << name << ": no entry is named, and it has no okb.json that names one"; return -1; } + unsigned long long size = 0; + s.refusal[0] = 0; + const void* bytes = archive_image(s, entry, size); + if (!bytes) { + text t(why, cap); + t << name << ": " << (s.refusal[0] ? s.refusal : "no such member: ") << (s.refusal[0] ? "" : entry); + return -1; + } + return enter(s, bytes, size, s.options && s.options->carried ? entry : name, argc, argv, why, cap); +} + +bool is_archive(const void* bytes, unsigned long long size) { + const auto* b = static_cast(bytes); + return size >= 4 && b[0] == 'P' && b[1] == 'K' && b[2] == 3 && b[3] == 4; +} + +} // namespace + +image_state& state() { return g_state; } + +} // namespace okrun + +using namespace okrun; + +extern "C" int okrun_run_bytes(const void* bytes, unsigned long long size, const char* name, + int argc, const char* const* argv, const okrun_options* options, + char* why, unsigned long long cap) { + search& s = g_search; + s.options = options; + if (is_archive(bytes, size)) { + s.archive = static_cast(bytes); + s.archive_size = size; + return run_archive(s, nullptr, name, argc, argv, why, cap); + } + return enter(s, bytes, size, name, argc, argv, why, cap); +} + +extern "C" int okrun_run_file(const char* path, int, const char* const*, const okrun_options*, + char* why, unsigned long long cap) { + text(why, cap) << path << ": this runner reads no file (the implementation provides no openkal.fs); " + << "a program carries its image"; + return -1; +} + +extern "C" int okrun_run_okb_bytes(const void* bytes, unsigned long long size, const char* entry, + int argc, const char* const* argv, const okrun_options* options, + char* why, unsigned long long cap) { + search& s = g_search; + s.options = options; + if (!is_archive(bytes, size)) { text(why, cap) << "the bytes are not an archive of images (docs/okb.md)"; return -1; } + s.archive = static_cast(bytes); + s.archive_size = size; + return run_archive(s, entry, entry ? entry : "the archive", argc, argv, why, cap); +} + +extern "C" int okrun_run_images(const okrun_image* images, unsigned long count, const char* entry, + int argc, const char* const* argv, const okrun_options* options, + char* why, unsigned long long cap) { + search& s = g_search; + s.options = options; + s.images = images; + s.image_count = count; + for (unsigned long i = 0; i < count; ++i) + if (entry && same(images[i].name, entry)) + return enter(s, images[i].bytes, images[i].size, images[i].name, argc, argv, why, cap); + text(why, cap) << "the group of images has no " << (entry ? entry : "entry"); + return -1; +} + +// A library of this system's program is a full runner's: the core runner has no file to open one +// from and no second program to place one beside. +extern "C" okrun_library* okrun_open_library(const char* path, const okrun_options*, char* why, + unsigned long long cap) { + text(why, cap) << (path ? path : "a library") << ": this runner opens no library"; + return nullptr; +} +extern "C" okrun_library* okrun_open_library_bytes(const void*, unsigned long long, const char* name, + const okrun_options*, char* why, unsigned long long cap) { + text(why, cap) << (name ? name : "a library") << ": this runner opens no library"; + return nullptr; +} +extern "C" void* okrun_symbol(okrun_library*, const char*) { return nullptr; } +extern "C" void okrun_close(okrun_library*) {} + +// The core runner keeps nothing for this machine. +extern "C" unsigned long long okrun_cache_list(void (*)(const char*, unsigned long long, int, void*), void*) { + return 0; +} +extern "C" unsigned long long okrun_cache_reclaim(int) { return 0; } + diff --git a/run/src/prof.cpp b/run/src/full/prof.cpp similarity index 95% rename from run/src/prof.cpp rename to run/src/full/prof.cpp index 40f72c6..344d1c7 100644 --- a/run/src/prof.cpp +++ b/run/src/full/prof.cpp @@ -32,42 +32,12 @@ // as it always was and `kal_prof_active' answers zero. #include #include -#include "runner.h" +#include "../runner.h" extern "C" void okrun_profile(const char* spec); namespace okrun { -// THE IMPLEMENTATION'S MEASUREMENT AND NAMES (openkal.prof, openkal.debug), -// referred to only where it provides them (0.6): an implementation without -// them is not asked to define a name it does not have (SPEC 6.1). -#if OKRUN_HAS_PROF -kal_uintptr impl_prof_props() { return kal_prof_props(); } -int impl_prof_sample_begin(kal_u32 per_second) { return kal_prof_sample_begin(per_second); } -kal_intptr impl_prof_sample_take(kal_prof_sample* out, kal_uintptr count) { return kal_prof_sample_take(out, count); } -int impl_prof_sample_end() { return kal_prof_sample_end(); } -int impl_prof_touch_begin(const void* at, kal_uintptr size) { return kal_prof_touch_begin(at, size); } -kal_intptr impl_prof_touch_take(kal_uintptr* out, kal_uintptr count) { return kal_prof_touch_take(out, count); } -int impl_prof_touch_end() { return kal_prof_touch_end(); } -#else -kal_uintptr impl_prof_props() { return 0; } -int impl_prof_sample_begin(kal_u32) { return kal_err_not_supported; } -kal_intptr impl_prof_sample_take(kal_prof_sample*, kal_uintptr) { return 0; } -int impl_prof_sample_end() { return kal_err_invalid; } -int impl_prof_touch_begin(const void*, kal_uintptr) { return kal_err_not_supported; } -kal_intptr impl_prof_touch_take(kal_uintptr*, kal_uintptr) { return 0; } -int impl_prof_touch_end() { return kal_err_invalid; } -#endif -#if OKRUN_HAS_DEBUG -kal_uintptr impl_debug_props() { return kal_debug_props(); } -kal_intptr impl_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start) { - return kal_debug_symbolize(address, out, cap, start); -} -#else -kal_uintptr impl_debug_props() { return 0; } -kal_intptr impl_debug_symbolize(kal_uintptr, char*, kal_uintptr, kal_uintptr*) { return -kal_err_not_found; } -#endif - namespace { kal_uintptr length(const char* s) { kal_uintptr n = 0; while (s && s[n]) ++n; return n; } diff --git a/run/src/run.cpp b/run/src/full/run.cpp similarity index 98% rename from run/src/run.cpp rename to run/src/full/run.cpp index 97e21e2..41347cf 100644 --- a/run/src/run.cpp +++ b/run/src/full/run.cpp @@ -18,8 +18,9 @@ #include #include #include -#include "image_abi.h" -#include "runner.h" +#include "../image_abi.h" +#include "../runner.h" + namespace okrun { @@ -1756,37 +1757,6 @@ bool locate_path(const char* path, kal_dir& dir, const char*& rest) { return loc using namespace okrun; -extern "C" const char* const* okrun_names(unsigned long* count) { - static const char* names[256]; - static unsigned long n = 0; - if (n == 0) { - unsigned long k = 0; - const binding* t = bindings(k); - for (unsigned long i = 0; i < k && n < 256; ++i) names[n++] = t[i].name; - t = space_bindings(k); - for (unsigned long i = 0; t && i < k && n < 256; ++i) names[n++] = t[i].name; - } - if (count) *count = n; - return names; -} - -// A NAME'S SLOT, from a hash under which no two of the surface's names share -// one (build.mcpp, 0.4): one name compared, where every name was. -#include "surface_hash.inc" - -extern "C" void* okrun_resolve(const char* name) { - if (!name) return nullptr; - unsigned h = 2166136261u ^ kResolveSeed; - for (const char* c = name; *c; ++c) { h ^= static_cast(*c); h *= 16777619u; } - const unsigned short slot = kResolveSlot[h % kResolveSlots]; - if (slot == 0) return nullptr; - unsigned long n = 0; - const unsigned long i = slot - 1u; - const binding* t = i < kResolveMain ? bindings(n) : space_bindings(n); - const unsigned long k = i < kResolveMain ? i : i - kResolveMain; - if (!t || k >= n || !same(t[k].name, name)) return nullptr; - return t[k].address; -} extern "C" int okrun_run_bytes(const void* bytes, unsigned long long size, const char* name, int argc, const char* const* argv, const okrun_options* options, @@ -1973,8 +1943,6 @@ bool has_separator(const char* s) { for (; *s; ++s) if (separator(*s)) return tr } // namespace -extern "C" unsigned long long okrun_call_enter(void) { return okrun_fp_enter(); } -extern "C" void okrun_call_leave(unsigned long long state) { okrun_fp_leave(state); } extern "C" okrun_library* okrun_open_library(const char* path, const okrun_options* options, char* why, unsigned long long cap) { @@ -2024,3 +1992,4 @@ extern "C" void okrun_close(okrun_library* lib) { } free_library(lib); } + diff --git a/run/src/seal.cpp b/run/src/full/seal.cpp similarity index 99% rename from run/src/seal.cpp rename to run/src/full/seal.cpp index 156b29c..a57d21a 100644 --- a/run/src/seal.cpp +++ b/run/src/full/seal.cpp @@ -12,8 +12,9 @@ // making it (`seal_refused', src/run.cpp), and the program itself, which runs // beside, never meets it. #include -#include "image_abi.h" -#include "runner.h" +#include "../image_abi.h" +#include "../runner.h" + namespace okrun { @@ -380,3 +381,4 @@ kal_u32 environment_facts(kal_u64 asked) { } } // namespace okrun + diff --git a/run/src/image_abi.h b/run/src/image_abi.h index 64391ca..eb5f4b7 100644 --- a/run/src/image_abi.h +++ b/run/src/image_abi.h @@ -1,5 +1,5 @@ -// The image's calling convention (SPEC clause 12.2): System V on x86-64 and the -// procedure call standard on AArch64, whatever the host's; and the name of the +// The image's calling convention (SPEC clause 12.2): System V on x86-64 and on +// i386 and the procedure call standard on AArch64, whatever the host's; and the name of the // architecture, as an okb names its directory (docs/okb.md). The one place the // architecture appears in this library. #pragma once @@ -33,6 +33,54 @@ inline void okrun_fp_leave(unsigned long long state) { if (now_x87 != x87) __asm__ volatile("fldcw %0" : : "m"(x87)); if ((now_mxcsr & 0xFFC0) != (mxcsr & 0xFFC0)) __asm__ volatile("ldmxcsr %0" : : "m"(mxcsr)); } +#elif defined(__i386__) +// THE SYSTEM V i386 CONVENTION (SPEC 12.2, clause 5.5): arguments on the stack, +// the stack aligned to sixteen bytes at a call, the result in eax. The x87 +// control word 0x037F, and MXCSR 0x1F80 where the processor has SSE --- the +// 486 and the Pentium that DOS's emulators model have none (CPUID leaf 1, EDX +// bit 25). +#define OKRUN_IMAGE_ABI __attribute__((cdecl)) +inline constexpr char okrun_arch[] = "i686"; +inline bool okrun_detect_sse() { + unsigned a = 1, b = 0, c = 0, d = 0; + unsigned long flags_before = 0, flags_after = 0; + // CPUID exists where the ID flag (bit 21 of EFLAGS) can be changed. + __asm__ volatile("pushfl\n\tpopl %0\n\tmovl %0, %1\n\txorl $0x200000, %1\n\tpushl %1\n\tpopfl\n\t" + "pushfl\n\tpopl %1\n\tpushl %0\n\tpopfl" + : "=&r"(flags_before), "=&r"(flags_after)); + if (((flags_before ^ flags_after) & 0x200000) == 0) return false; + __asm__ volatile("cpuid" : "+a"(a), "=b"(b), "=c"(c), "=d"(d)); + return (d >> 25) & 1; +} +// Asked once: CPUID is a serialising instruction, and a trap to the hypervisor in a virtual machine. +inline bool okrun_has_sse() { + static const bool sse = okrun_detect_sse(); + return sse; +} +inline unsigned long long okrun_fp_enter() { + unsigned short x87 = 0; + unsigned mxcsr = 0; + const bool sse = okrun_has_sse(); + __asm__ volatile("fnstcw %0" : "=m"(x87)); + if (sse) __asm__ volatile("stmxcsr %0" : "=m"(mxcsr)); + const unsigned short image_x87 = 0x037F; + const unsigned image_mxcsr = 0x1F80; + if (x87 != image_x87) __asm__ volatile("fldcw %0" : : "m"(image_x87)); + if (sse && (mxcsr & 0xFFC0) != image_mxcsr) __asm__ volatile("ldmxcsr %0" : : "m"(image_mxcsr)); + return static_cast(mxcsr) << 16 | x87; +} +inline void okrun_fp_leave(unsigned long long state) { + const unsigned short x87 = static_cast(state & 0xFFFF); + const unsigned mxcsr = static_cast(state >> 16); + unsigned short now_x87 = 0; + __asm__ volatile("fnstcw %0" : "=m"(now_x87)); + if (now_x87 != x87) __asm__ volatile("fldcw %0" : : "m"(x87)); + if (okrun_has_sse()) { + unsigned now_mxcsr = 0; + __asm__ volatile("stmxcsr %0" : "=m"(now_mxcsr)); + if ((now_mxcsr & 0xFFC0) != (mxcsr & 0xFFC0)) __asm__ volatile("ldmxcsr %0" : : "m"(mxcsr)); + } +} #elif defined(__aarch64__) #define OKRUN_IMAGE_ABI inline constexpr char okrun_arch[] = "aarch64"; diff --git a/run/src/runner.h b/run/src/runner.h index cee0cd1..c109454 100644 --- a/run/src/runner.h +++ b/run/src/runner.h @@ -5,6 +5,14 @@ // OKRUN_HAS_ and OKRUN_COMPILED_INTERFACES (build.mcpp, 0.6). #include "provided.inc" +// ONE INTERFACE, TWO RUNNERS (0.6.3). This header, bindings.cpp and adapters.cpp are both +// runners'. The full runner (full/) reads images from files, keeps a cache of their placements and +// snapshots for this machine, starts itself again, and digests in parallel: it needs a file system, +// processes, contexts, random bytes and file mapping. An implementation without them --- firmware, +// a single-task system --- has the core runner (core/): an image, or an archive of images, that the +// program carries runs from memory, its code placed through openkal.exec. The C interface is the +// same; mcpp.toml selects the directory by the interfaces the implementation provides. + namespace okrun { struct binding { const char* name; void* address; }; diff --git a/tools/mcpp.toml b/tools/mcpp.toml index 042969c..1130906 100644 --- a/tools/mcpp.toml +++ b/tools/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-tools" -version = "0.6.2" +version = "0.6.3" description = "okrun, which runs an ok image on this system, and okld, the format's tool on a build machine (check, note, sign, inspect)." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/tools/src/okld.cpp b/tools/src/okld.cpp index 15bafb2..6a198cb 100644 --- a/tools/src/okld.cpp +++ b/tools/src/okld.cpp @@ -820,7 +820,8 @@ int main(int argc, char** argv) { okld_info info{}; okld_why why{}; if (okld_read(image->data(), image->size(), &info, &why) != 0) { std::println(std::cerr, "okld: {}", why.text); return 1; } - std::println("architecture {}", info.machine == 62 ? "x86_64" : info.machine == 183 ? "aarch64" : "other"); + const char* arch = okld_okb_arch_of(info.machine); + std::println("architecture {}", arch ? arch : "other"); std::println("format {}", info.has_requirements ? std::format("{}.{}", info.format_major, info.format_minor) : std::string("no requirements note")); std::println("requires {}", interfaces(info.interfaces)); unsigned char key[32];