From f0c25101997515837553b279f7591275f4c4d44b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 13 Nov 2026 17:39:34 +0900 Subject: [PATCH 1/6] 0.6.3: a core runner for an implementation without files, processes or threads One interface (include/okrun.h, run/src/runner.h, the bindings) has two runners, selected in run/mcpp.toml by the interfaces the implementation provides (cfg(interface = ...), mcpp 2026.11.13.1). With openkal.fs, openkal.process, openkal.task, openkal.random and openkal.map it is the full runner (run/src/full), unchanged. Without any of them it is the core runner (run/src/core), which runs what the program carries from memory --- an image, a group, or an archive --- with the same admission and loader, and without cache, snapshot or profiler; okrun_run_file refuses with the reason. A callback of an absent interface is absent, as its other names are. openkal-run requires only abort, env, exec, memory, stream and time. --- .gitignore | 1 + README.md | 17 ++ ld/mcpp.toml | 2 +- run/build.mcpp | 7 +- run/mcpp.toml | 37 +++-- run/src/adapters.cpp | 37 +++++ run/src/bindings.cpp | 41 +++++ run/src/core/prof.cpp | 26 +++ run/src/core/run.cpp | 319 ++++++++++++++++++++++++++++++++++++ run/src/{ => full}/prof.cpp | 32 +--- run/src/{ => full}/run.cpp | 39 +---- run/src/{ => full}/seal.cpp | 6 +- run/src/runner.h | 8 + tools/mcpp.toml | 2 +- 14 files changed, 489 insertions(+), 85 deletions(-) create mode 100644 run/src/adapters.cpp create mode 100644 run/src/core/prof.cpp create mode 100644 run/src/core/run.cpp rename run/src/{ => full}/prof.cpp (95%) rename run/src/{ => full}/run.cpp (98%) rename run/src/{ => full}/seal.cpp (99%) 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..381623c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,23 @@ 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. + ## An image ```sh 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/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/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"] From 62ff15e8ffba54b56d0d1edfaa99d643e803ac30 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 13 Nov 2026 22:36:06 +0900 Subject: [PATCH 2/6] ci: mcpp 2026.11.13.1, the engine this release requires --- .github/workflows/ci.yml | 3 ++- .github/workflows/release-binaries.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 803c300..ecc894f 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: 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: From 060217eef2779fe554ade6da9e245ae50fde0c63 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 14 Nov 2026 01:39:36 +0900 Subject: [PATCH 3/6] 0.6.3: i386 images (ELFCLASS32), read, checked, placed, loaded and debugged libokld opens an image of either class. A view widens an ELF32 header, program headers and dynamic table into the sixty-four bit forms, and reads symbols and relocations through accessors of the class; the bloom filter, packed relative relocations (a bitmap of 31 entries) and the word a relocation writes follow the class. An i386 image states its addends (RELA, `-z rela`); a REL image is refused with that option named. The relocations applied 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 (SPEC 12.2), with the x87 control word 0x037F and MXCSR 0x1F80 where the processor has SSE, asked once by CPUID. okb names the architecture i686. The debugger object is of the image's class: i386's retained REL relocations carry their addend in the word, so the object holds each R_386_32 and R_386_PC32 word less the resolved value. Measured: an i686-openkal image (C++23, import std, an exception across a frame) runs under okrun built for i686-linux-musl on x86-64 Linux, and gdb stops at a source line of it and unwinds into the runner. --- .github/tests/debug-image-builder.cpp | 37 +++++ README.md | 21 +++ docs/debugging.md | 7 +- docs/okb.md | 2 +- ld/src/debug_image.cpp | 214 ++++++++++++++++++-------- ld/src/elf.h | 27 +++- ld/src/image.h | 89 +++++++++-- ld/src/load.cpp | 174 +++++++++++++-------- ld/src/okb.cpp | 2 +- ld/src/place.cpp | 50 +++--- ld/src/read.cpp | 192 ++++++++++++++++------- run/src/image_abi.h | 51 +++++- tools/src/okld.cpp | 3 +- 13 files changed, 640 insertions(+), 229 deletions(-) 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/README.md b/README.md index 381623c..efe2d67 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,27 @@ 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/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/src/image_abi.h b/run/src/image_abi.h index 64391ca..ed79b22 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,53 @@ 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 --- a +// Pentium, which DOS's emulators model, has 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/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]; From 3eaac960a3b6941586f5abdeed839c540f23d52b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 14 Nov 2026 01:40:32 +0900 Subject: [PATCH 4/6] ci: i386 images built, placed, checked and bundled; build-image.sh states addends and builds for i686 --- .github/workflows/ci.yml | 10 ++++++++++ examples/build-image.sh | 11 +++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecc894f..e055697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,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/examples/build-image.sh b/examples/build-image.sh index cea530e..b61eddd 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,9 @@ done work=$(mktemp -d); trap 'rm -rf "$work"' EXIT extra=() [ "$arch" = aarch64 ] && extra=(-ffixed-x18 -mno-outline-atomics) +# i386 (0.6.3): the Pentium's instructions, 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=i586 -mstack-alignment=16) "$OKLD" note-source > "$work/note.S" objs=() for src in "${sources[@]}" "$work/note.S"; do @@ -29,7 +32,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" From b1ed4246755c67667bf593ba974162cad2b88c6c Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 14 Nov 2026 02:28:04 +0900 Subject: [PATCH 5/6] i386 images are built for the 486 (build-image.sh): DOSBox, beneath DOS's carrier, executes no CMPXCHG8B --- examples/build-image.sh | 7 ++++--- run/src/image_abi.h | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/build-image.sh b/examples/build-image.sh index b61eddd..48a8b3b 100755 --- a/examples/build-image.sh +++ b/examples/build-image.sh @@ -18,9 +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 Pentium's instructions, 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=i586 -mstack-alignment=16) +# 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 diff --git a/run/src/image_abi.h b/run/src/image_abi.h index ed79b22..eb5f4b7 100644 --- a/run/src/image_abi.h +++ b/run/src/image_abi.h @@ -36,8 +36,9 @@ inline void okrun_fp_leave(unsigned long long state) { #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 --- a -// Pentium, which DOS's emulators model, has none (CPUID leaf 1, EDX bit 25). +// 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() { From b77dcf6f2a65c48b1d013ed6f570c3abf6e88318 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 14 Nov 2026 22:59:44 +0900 Subject: [PATCH 6/6] ci: the specification's headers of any 0.25 patch the tools resolved (0.25.1) --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e055697..62527da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,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