From 6721f8248c8cc163d678de7dfb4402ea9e867afa Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 13 Nov 2026 12:51:16 +0900 Subject: [PATCH 1/3] 0.26.2: i386 (openkal 0.25.1, clause 5.5); one rt_sigaction record and one return from a handler --- .github/workflows/ci.yml | 10 ++ README.md | 30 ++++- mcpp.toml | 7 +- src/env.cpp | 3 +- src/exec.cpp | 10 +- src/fs.cpp | 50 +++----- src/handle.h | 28 +++- src/map.cpp | 6 +- src/memory.cpp | 4 +- src/observe.cpp | 45 +++++-- src/process.cpp | 141 ++++++++++++-------- src/region.cpp | 4 +- src/rights.cpp | 4 +- src/start.cpp | 15 +++ src/sys.h | 269 ++++++++++++++++++++++++++++++++++++++- src/task.cpp | 55 +++++++- src/time.cpp | 9 +- src/timeout.cpp | 2 +- src/tls.h | 38 +++++- tests/word32/runs.cpp | 74 +++++++++++ tools/word32-check.sh | 25 ++++ 21 files changed, 691 insertions(+), 138 deletions(-) create mode 100644 tests/word32/runs.cpp create mode 100755 tools/word32-check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66634c1..da6dc6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,6 +468,16 @@ jobs: rm -f "$RUNNER_TEMP/android.bad" echo "both Android ABIs build from this implementation unchanged, and name no C library symbol" + # A THIRTY-TWO BIT BUILD RUNS (openkal 0.25.1, clause 5.5). The same + # sources, standalone, for i386: built with the toolchain's clang and lld + # and RUN here, since an x86_64 kernel executes a static i386 program. A + # file past four gibibytes, a task with its own thread-local storage and a + # sleep through the sixty-four bit clock; the conformance suite runs for + # i686 where the family's C library and C++ runtime are built for it. + - name: An i386 build of this implementation runs + if: matrix.toolchain == 'llvm@22.1.8' + run: OPENKAL_INCLUDE="$PWD/.spec/include" bash tools/word32-check.sh + # A checker is only useful if it fails when it should. - name: The independence check detects a dependence run: | diff --git a/README.md b/README.md index a3c443b..6ad32af 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,30 @@ for Linux, written on the kernel's own system-call interface. openkal = "0.25.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.26.1" +openkal-linux = "0.26.2" ``` +## What 0.26.2 adds + +**i386** (openkal 0.25.1, clause 5.5). The kernel's i386 gate (`int $0x80`, the sixth +argument in `ebp`) and its own table of numbers; where a call has a sixty-four bit +time form that form is used (`clock_gettime64`, `ppoll_time64`, `futex_time64`, +`utimensat_time64`, `clock_nanosleep_time64`), and the status of a node comes from +`statx`, so no answer ends in 2038. The calls whose arguments differ between +architectures --- a position in two words (`_llseek`, `mmap2`, `pread64`, +`ftruncate64`), `statfs64`, `O_LARGEFILE` --- are functions of `src/sys.h` that every +caller uses on every architecture. The thread pointer is a segment set by +`set_thread_area` and given to a new context by `clone`. A handle is a thirty-two +bit word there: seventeen bits of descriptor and fifteen of generation; a program's +handle carries its number and its kind, and an opened program's descriptor. +`tools/word32-check.sh` builds the implementation standalone for i386 and runs +`tests/word32/runs.cpp`: a file past four gibibytes, a task with its own +thread-local storage, a sleep through the sixty-four bit clock. + +Also: one record of `rt_sigaction` (the mask is the kernel's sixty-four bit set; it +was declared three times with an `unsigned long` mask) and one return from a +handler (it was written twice). + ## What 0.26.1 corrects **A handle is retired before its descriptor is closed.** A connection, a listener @@ -398,9 +419,10 @@ rather than call. ## Architectures -`x86_64` and `aarch64`. Adding one is `src/sys.h`: a calling convention, a table -of system-call numbers, the kernel's `struct stat`, and the two thread-pointer -conventions in `src/tls.h`. +`x86_64`, `aarch64` and `i386`. Adding one is `src/sys.h`: a calling convention, a +table of system-call numbers, the kernel's `struct stat` (or `statx`), the +functions for the calls whose arguments the architecture lays out otherwise, and +the thread-pointer convention in `src/tls.h`. ## Verification diff --git a/mcpp.toml b/mcpp.toml index 0260a02..53f0873 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.26.1" +version = "0.26.2" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" @@ -94,6 +94,11 @@ flags = [ "-fno-asynchronous-unwind-tables"] }, ] +# tests/word32/ is built standalone for i386 by tools/word32-check.sh, with the C library facilities +# it supplies itself; the host's test programs are the others. +[test] +discover = ["tests/**/*.cpp", "!tests/word32/**"] + [features] default = [] diff --git a/src/env.cpp b/src/env.cpp index 03c2bb0..128f750 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -136,8 +136,7 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) { // with. What was found here is recorded, and `kal_process_start' gives a // started program the disposition this one was started with (process.cpp). [[gnu::constructor(101)]] void quiet_the_signal_openkal_cannot_report() { - // struct k_sigaction as this kernel takes it: handler, flags, restorer, mask. - struct k_sigaction { void* handler; unsigned long flags; void* restorer; unsigned long mask; }; + using okl::k_sigaction; k_sigaction ignore{ reinterpret_cast(1) /* SIG_IGN */, 0, nullptr, 0 }; k_sigaction found{}; if (!okl::failed(okl::sys(okl::nr_rt_sigaction, 13 /* SIGPIPE */, diff --git a/src/exec.cpp b/src/exec.cpp index 36208c0..fb9b9ea 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -63,7 +63,7 @@ extern "C" { void* kal_exec_alloc(kal_uintptr size) { if (size == 0) return nullptr; const okl_uptr bytes = round_up(static_cast(size), page()); - const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast(bytes), + const okl_long r = okl::mmap( 0, static_cast(bytes), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); if (okl::failed(r)) return nullptr; @@ -78,7 +78,7 @@ void* kal_exec_alloc_at(void* at, kal_uintptr size) { const okl_uptr where = reinterpret_cast(at); if (size == 0 || where == 0 || (where & (page() - 1)) != 0) return nullptr; const okl_uptr bytes = round_up(static_cast(size), page()); - const okl_long r = okl::sys(okl::nr_mmap, static_cast(where), static_cast(bytes), + const okl_long r = okl::mmap( static_cast(where), static_cast(bytes), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous | 0x100000 /* MAP_FIXED_NOREPLACE */, -1, 0); @@ -189,7 +189,7 @@ int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_fi || ((how & KAL_EXEC_MAP_WRITE) && (how & KAL_EXEC_MAP_EXECUTE))) return kal_err_invalid; okl::kstat st{}; - const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast(&st)); + const okl_long s = okl::fstat(fd, &st); if (okl::failed(s)) return okl::translate(s); if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported; const kal_u64 length = st.size > 0 ? static_cast(st.size) : 0; @@ -205,10 +205,10 @@ int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_fi | ((how & KAL_EXEC_MAP_WRITE) ? okl::prot_write : 0) | (bti ? 0x10 /* PROT_BTI */ : 0); const okl_long populate = (how & KAL_EXEC_MAP_PREFETCH) ? 0x8000 /* MAP_POPULATE */ : 0; - const okl_long r = okl::sys(okl::nr_mmap, + const okl_long r = okl::mmap( reinterpret_cast(static_cast(region) + offset), static_cast(size), prot, okl::map_private | okl::map_fixed | populate, fd, - static_cast(at)); + static_cast(at)); if (okl::failed(r)) { // ENODEV: the file system does not map. EPERM: its files are not // executed (a volume mounted noexec). The resource refuses, not the diff --git a/src/fs.cpp b/src/fs.cpp index 847725b..a3f30ff 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -129,8 +129,7 @@ bool granted(preopen* t, kal_uintptr* n) { for (okl_uptr i = 0; i < len; ++i) if (v[i] == '\0') return false; okl::kstat st{}; - const bool dir = !okl::failed(okl::sys(okl::nr_fstat, static_cast(fd), - reinterpret_cast(&st))) + const bool dir = !okl::failed(okl::fstat(static_cast(fd), &st)) && (st.mode & okl::s_ifmt) == okl::s_ifdir; if (dir) okl::sys(okl::nr_fcntl, static_cast(fd), okl::f_setfd, okl::fd_cloexec); t[k++] = { v, len, dir ? okl::pack(static_cast(fd)) : 0u }; @@ -180,9 +179,9 @@ preopen* table(kal_uintptr* count) { if (!okl::failed(n) && n > 1) cwd_len = static_cast(n) - 1; else { g_cwd[0] = '.'; g_cwd[1] = '\0'; cwd_len = 1; } - const okl_long fd0 = okl::sys(okl::nr_openat, okl::at_fdcwd, + const okl_long fd0 = okl::openat( okl::at_fdcwd, reinterpret_cast("."), dir_handle_flags, 0); - const okl_long fd1 = okl::sys(okl::nr_openat, okl::at_fdcwd, + const okl_long fd1 = okl::openat( okl::at_fdcwd, reinterpret_cast("/"), dir_handle_flags, 0); t[0] = { g_cwd, cwd_len, okl::failed(fd0) ? 0u : okl::pack(static_cast(fd0)) }; t[1] = { "/", 1, okl::failed(fd1) ? 0u : okl::pack(static_cast(fd1)) }; @@ -222,10 +221,10 @@ kal_uintptr volume_props(int fd) { if (fd < 0) return 0; okl::kstatfs sf{}; - const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast(&sf)); + const okl_long r = okl::fstatfs(fd, &sf); if (okl::failed(r)) return conservative; - switch (sf.f_type) { + switch (static_cast(sf.f_type)) { // Formats with a case-sensitive namespace and nodes that name others. // // A rename that does not replace (0.17) is `renameat2' with @@ -293,7 +292,7 @@ kal_uintptr volume_props_of(int fd) { return volume_props(fd); } // asked about, so a reader takes the sequence before and after reading them and // reads again if a writer was between; writers take the lock. The lock taken on // every enquiry was a fifth of what an enquiry cost beyond the kernel's own. -struct volume_answer { okl_u64 dev; kal_uintptr props; bool known; }; +struct volume_answer { alignas(8) okl_u64 dev; kal_uintptr props; bool known; }; // aligned: an atomic word on i386 too volume_answer g_volumes[8]; unsigned g_volume_next = 0; volatile int g_volume_lock = 0; @@ -534,7 +533,7 @@ int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* ou asked(b, false, name, len); return kal_err_not_found; } - const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast(t.buf), + const okl_long fd = okl::openat( b, reinterpret_cast(t.buf), dir_handle_flags, 0); if (okl::failed(fd)) { if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len); @@ -574,7 +573,7 @@ int kal_fs_open(kal_dir base, const char* name, kal_uintptr len, asked(b, false, name, len); return kal_err_not_found; } - const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast(t.buf), f, 0666); + const okl_long fd = okl::openat( b, reinterpret_cast(t.buf), f, 0666); if (okl::failed(fd)) { if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len); return okl::translate(fd); @@ -616,8 +615,8 @@ int kal_fs_seek(kal_file f, kal_i64 offset, int whence, kal_u64* result) { int w = 0; if (whence == KAL_SEEK_CURRENT) w = 1; else if (whence == KAL_SEEK_END) w = 2; - const okl_long r = okl::sys(okl::nr_lseek, fd, static_cast(offset), w); - if (okl::failed(r)) return okl::translate(r); + const okl_i64 r = okl::lseek(fd, static_cast(offset), w); + if (r < 0) return okl::translate(static_cast(r)); if (result) *result = static_cast(r); return kal_ok; } @@ -625,7 +624,7 @@ int kal_fs_seek(kal_file f, kal_i64 offset, int whence, kal_u64* result) { int kal_fs_truncate(kal_file f, kal_u64 size) { const int fd = okl::unpack(f.h); if (fd < 0) return kal_err_invalid; - const okl_long r = okl::sys(okl::nr_ftruncate, fd, static_cast(size)); + const okl_long r = okl::ftruncate(fd, static_cast(size)); return okl::failed(r) ? okl::translate(r) : kal_ok; } @@ -638,8 +637,7 @@ kal_intptr kal_fs_read_at(kal_file f, void* buf, kal_uintptr len, kal_u64 offset const int fd = okl::unpack(f.h); if (fd < 0) return -kal_err_invalid; for (;;) { - const okl_long r = okl::sys(okl::nr_pread64, fd, reinterpret_cast(buf), - static_cast(len), static_cast(offset)); + const okl_long r = okl::pread(fd, buf, static_cast(len), static_cast(offset)); if (okl::interrupted(r)) continue; if (okl::failed(r)) return -okl::translate(r); return static_cast(r); @@ -652,9 +650,7 @@ kal_intptr kal_fs_write_at(kal_file f, const void* buf, kal_uintptr len, kal_u64 const auto* p = static_cast(buf); kal_uintptr done = 0; while (done < len) { - const okl_long r = okl::sys(okl::nr_pwrite64, fd, reinterpret_cast(p + done), - static_cast(len - done), - static_cast(offset + done)); + const okl_long r = okl::pwrite(fd, p + done, static_cast(len - done), static_cast(offset + done)); if (okl::interrupted(r)) continue; if (okl::failed(r)) { if (done != 0) return static_cast(done); @@ -686,8 +682,7 @@ int kal_fs_info(kal_dir base, const char* name, kal_uintptr len, // A C library above reported a link where the host reports a regular file, // and one symbolic link made a whole tree uncopyable. const okl_long at = (flags & KAL_FS_NO_RESOLVE) ? okl::at_symlink_nofollow : 0; - const okl_long r = okl::sys(okl::nr_newfstatat, b, reinterpret_cast(t.buf), - reinterpret_cast(&st), at); + const okl_long r = okl::fstatat(b, t.buf, &st, at); if (okl::failed(r)) { // Clause 7.7: enquiry about a name that does not exist is answered, // not refused. A component of the name that is not a directory is the @@ -709,7 +704,7 @@ int kal_fs_file_info(kal_file f, kal_u32 wanted, kal_node_info* out) { const int fd = okl::unpack(f.h); if (fd < 0 || !info_ok(out)) return kal_err_invalid; okl::kstat st{}; - const okl_long r = okl::sys(okl::nr_fstat, fd, reinterpret_cast(&st)); + const okl_long r = okl::fstat(fd, &st); if (okl::failed(r)) return okl::translate(r); fill_info(st, wanted, out, fd); return kal_ok; @@ -777,8 +772,7 @@ int kal_fs_set_executable_at(kal_dir base, const char* name, kal_uintptr len, if (refused_write(b)) return kal_err_permission; okl::kstat st{}; - okl_long r = okl::sys(okl::nr_newfstatat, b, reinterpret_cast(t.buf), - reinterpret_cast(&st), 0); + okl_long r = okl::fstatat(b, t.buf, &st, 0); if (okl::failed(r)) return okl::translate(r); const int k = kind_of(st.mode); if (k == kal_node_directory) return kal_err_is_directory; @@ -872,7 +866,7 @@ int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen, int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) { const int fd = okl::unpack(d.h); if (fd < 0 || iter == nullptr) return kal_err_invalid; - const okl_long own = okl::sys(okl::nr_openat, fd, reinterpret_cast("."), + const okl_long own = okl::openat( fd, reinterpret_cast("."), okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0); if (okl::failed(own)) return okl::translate(own); auto* s = static_cast(kal_alloc(sizeof(listing), alignof(listing))); @@ -1040,7 +1034,7 @@ int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) { if (fd < 0) return kal_err_invalid; okl::kstatfs sf{}; - const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast(&sf)); + const okl_long r = okl::fstatfs(fd, &sf); if (okl::failed(r)) return okl::translate(r); // In bytes, because that is what the interface says and what a caller of it @@ -1090,8 +1084,7 @@ int kal_fs_hard_link(kal_dir from, const char* a, kal_uintptr alen, if (!ta.ok || !tb.ok) return kal_err_invalid; if (refused_write(t2)) return kal_err_permission; okl::kstat st{}; - const okl_long s = okl::sys(okl::nr_newfstatat, f, reinterpret_cast(ta.buf), - reinterpret_cast(&st), okl::at_symlink_nofollow); + const okl_long s = okl::fstatat(f, ta.buf, &st, okl::at_symlink_nofollow); if (okl::failed(s)) return okl::translate(s); if ((st.mode & okl::s_ifmt) == okl::s_ifdir) return kal_err_is_directory; const okl_long r = okl::sys(okl::nr_linkat, f, reinterpret_cast(ta.buf), @@ -1118,7 +1111,7 @@ kal_intptr kal_fs_resolved_name(kal_dir base, const char* name, kal_uintptr len, asked(b, false, name, len); return -kal_err_not_found; } - const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast(t.buf), + const okl_long fd = okl::openat( b, reinterpret_cast(t.buf), okl::o_path | okl::o_cloexec, 0); if (okl::failed(fd)) { if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len); @@ -1198,8 +1191,7 @@ int kal_fs_list_next_info(kal_dir d, kal_uintptr* iter, char* name_out, kal_uint if ((wanted & ~KAL_INFO_KIND) != 0) { okl::kstat st{}; const int fd = okl::unpack(d.h); - if (!okl::failed(okl::sys(okl::nr_newfstatat, fd, reinterpret_cast(own), - reinterpret_cast(&st), okl::at_symlink_nofollow))) { + if (!okl::failed(okl::fstatat(fd, own, &st, okl::at_symlink_nofollow))) { v.self_size = self; fill_info(st, wanted, &v, fd); v.kind = kind; diff --git a/src/handle.h b/src/handle.h index f8a3fab..2ebc6e7 100644 --- a/src/handle.h +++ b/src/handle.h @@ -12,6 +12,17 @@ namespace okl { inline constexpr int kMaxDescriptor = 65536; +// THE WORD IS AN INDEX AND A GENERATION, AND ITS WIDTH DECIDES THE SPLIT. On a +// sixty-four bit word the index is the low thirty-two bits and the generation +// the high; on i386 the index needs seventeen (a descriptor below 65536, plus +// one) and the generation has the other fifteen, so staleness is detected for +// a number reused fewer than 32768 times between a release and a use --- what +// clause 5.5 of the specification allows a thirty-two bit word. +inline constexpr unsigned kIndexBits = sizeof(okl_uptr) == 8 ? 32u : 17u; +inline constexpr okl_uptr kIndexMask = (okl_uptr{1} << kIndexBits) - 1u; +inline constexpr unsigned kGenerationMask = + sizeof(okl_uptr) == 8 ? ~0u : static_cast((okl_uptr{1} << (32u - kIndexBits)) - 1u); + inline unsigned* generations() { static unsigned g[kMaxDescriptor]; return g; @@ -26,7 +37,7 @@ inline unsigned generation(int fd) { inline okl_uptr pack(int fd) { if (fd < 0 || fd >= kMaxDescriptor) return 0; - return (static_cast(generation(fd)) << 32) + return (static_cast(generation(fd) & kGenerationMask) << kIndexBits) | (static_cast(fd) + 1u); } @@ -44,9 +55,9 @@ inline okl_uptr pack(int fd) { // kal_dir, kal_net_listener, kal_net_conn, kal_datagram --- are the whole of // this function's domain. inline int unpack(okl_uptr h) { - const int fd = static_cast(h & 0xffffffffu) - 1; + const int fd = static_cast(h & kIndexMask) - 1; if (fd < 0 || fd >= kMaxDescriptor) return -1; - if (static_cast(h >> 32) != generation(fd)) return -1; + if (static_cast(h >> kIndexBits) != (generation(fd) & kGenerationMask)) return -1; return fd; } @@ -58,18 +69,21 @@ inline int unpack(okl_uptr h) { // descriptor is never closed (openkal-linux#53). Every release in this // implementation is `release(h)`, which keeps the order in one place. inline bool retire(okl_uptr h) { - const int fd = static_cast(h & 0xffffffffu) - 1; + const int fd = static_cast(h & kIndexMask) - 1; if (fd < 0 || fd >= kMaxDescriptor) return false; // One release of a handle advances its generation; a second release of the // same word, in this context or another, finds it advanced and closes - // nothing, so it cannot close a descriptor opened since. - unsigned held = static_cast(h >> 32); + // nothing, so it cannot close a descriptor opened since. The word carries + // the generation's low bits (all of them on a sixty-four bit word), so the + // counter it is compared with is read first and only that one advanced. + unsigned held = generation(fd); + if ((held & kGenerationMask) != static_cast(h >> kIndexBits)) return false; return __atomic_compare_exchange_n(&generations()[fd], &held, held + 1u, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); } inline void release(okl_uptr h) { - const int fd = static_cast(h & 0xffffffffu) - 1; + const int fd = static_cast(h & kIndexMask) - 1; if (retire(h)) sys(nr_close, fd); } diff --git a/src/map.cpp b/src/map.cpp index 30ce4af..1c5a292 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -27,15 +27,15 @@ int kal_map_file(kal_file f, kal_u64 offset, kal_uintptr length, kal_uintptr how || (how & ~(KAL_MAP_WRITE | KAL_MAP_SHARED)) != 0) return kal_err_invalid; okl::kstat st{}; - const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast(&st)); + const okl_long s = okl::fstat(fd, &st); if (okl::failed(s)) return okl::translate(s); if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported; const kal_u64 size = st.size > 0 ? static_cast(st.size) : 0; if (offset > size || static_cast(length) > size - offset) return kal_err_invalid; const okl_long prot = okl::prot_read | ((how & KAL_MAP_WRITE) ? okl::prot_write : 0); const okl_long flags = (how & KAL_MAP_SHARED) ? okl::map_shared : okl::map_private; - const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast(length), prot, flags, fd, - static_cast(offset)); + const okl_long r = okl::mmap( 0, static_cast(length), prot, flags, fd, + static_cast(offset)); if (okl::failed(r)) { // ENODEV: the file system does not map. The resource, not the // implementation, refuses (map.h). diff --git a/src/memory.cpp b/src/memory.cpp index bb9e0ea..999568d 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -55,7 +55,7 @@ int class_of(okl_uptr n) { okl_uptr size_of(int c) { return kMinBlock << c; } void* map(okl_uptr bytes) { - const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast(bytes), + const okl_long r = okl::mmap( 0, static_cast(bytes), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); if (okl::failed(r)) return nullptr; @@ -98,7 +98,7 @@ void* carve(int c) { // The whole of a small file of the kernel's, into `b'; its length, or 0. okl_uptr read_small(const char* path, char* b, okl_uptr cap) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return 0; okl_uptr n = 0; diff --git a/src/observe.cpp b/src/observe.cpp index a9b0d32..f2c2317 100644 --- a/src/observe.cpp +++ b/src/observe.cpp @@ -18,11 +18,12 @@ // The return from a handler: this kernel requires the restorer of a handler // installed without a C library to be named. #if defined(__x86_64__) -extern "C" void okl_signal_return(); __asm__(".text\n.globl okl_signal_return\n.type okl_signal_return,@function\nokl_signal_return:\n" " mov $15, %eax\n syscall\n hlt\n"); +#elif defined(__i386__) +__asm__(".text\n.globl okl_signal_return\n.type okl_signal_return,@function\nokl_signal_return:\n" + " mov $173, %eax\n int $0x80\n hlt\n"); #elif defined(__aarch64__) -extern "C" void okl_signal_return(); __asm__(".text\n.globl okl_signal_return\n.type okl_signal_return,@function\nokl_signal_return:\n" " mov x8, #139\n svc #0\n"); #endif @@ -83,7 +84,7 @@ okl_uptr parse_hex(const char*& p) { } bool locate(okl_uptr addr, where_t& w) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast("/proc/self/maps"), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/self/maps"), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return false; char buf[4096]; @@ -122,17 +123,29 @@ bool locate(okl_uptr addr, where_t& w) { } // --- the symbol that covers an offset of an ELF file -------------------------- +// The records of this program's own class: a process maps files of its class +// only, ELF32 on i386 (whose records order some fields otherwise) and ELF64 +// elsewhere. The names keep the 64 of the form they were written for. +#if defined(__i386__) +constexpr unsigned char elf_class = 1; +struct ehdr64 { unsigned char ident[16]; okl_u32 tv; okl_u32 version; okl_u32 entry, phoff, shoff; + okl_u32 flags; unsigned short ehsize, phentsize, phnum, shentsize, shnum, shstrndx; }; +struct phdr64 { okl_u32 type, offset, vaddr, paddr, filesz, memsz, flags, align; }; +struct shdr64 { okl_u32 name, type, flags, addr, offset, size, link, info, align, entsize; }; +struct sym64 { okl_u32 name, value, size; unsigned char info, other; unsigned short shndx; }; +#else +constexpr unsigned char elf_class = 2; struct ehdr64 { unsigned char ident[16]; okl_u32 tv; okl_u32 version; okl_u64 entry, phoff, shoff; okl_u32 flags; unsigned short ehsize, phentsize, phnum, shentsize, shnum, shstrndx; }; struct phdr64 { okl_u32 type, flags; okl_u64 offset, vaddr, paddr, filesz, memsz, align; }; struct shdr64 { okl_u32 name, type; okl_u64 flags, addr, offset, size; okl_u32 link, info; okl_u64 align, entsize; }; struct sym64 { okl_u32 name; unsigned char info, other; unsigned short shndx; okl_u64 value, size; }; +#endif bool pread_all(int fd, void* out, okl_uptr n, okl_u64 at) { okl_uptr got = 0; while (got < n) { - const okl_long r = okl::sys(okl::nr_pread64, fd, reinterpret_cast(static_cast(out) + got), - static_cast(n - got), static_cast(at + got)); + const okl_long r = okl::pread(fd, static_cast(out) + got, static_cast(n - got), static_cast(at + got)); if (r == -okl::e_intr) continue; if (r <= 0) return false; got += static_cast(r); @@ -144,14 +157,14 @@ bool pread_all(int fd, void* out, okl_uptr n, okl_u64 at) { // and the distance into it; false where the file has no such symbol. bool symbol_at(const char* file, okl_uptr off, char* name, okl_uptr cap, okl_uptr& len, okl_uptr& delta, okl_uptr& sym_vaddr, okl_uptr& vaddr_out) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(file), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast(file), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return false; bool ok = false; ehdr64 eh{}; do { if (!pread_all(static_cast(fd), &eh, sizeof eh, 0)) break; - if (eh.ident[0] != 0x7f || eh.ident[1] != 'E' || eh.ident[4] != 2) break; + if (eh.ident[0] != 0x7f || eh.ident[1] != 'E' || eh.ident[4] != elf_class) break; // The offset as an address of the file: the loadable segment holding it. okl_uptr vaddr = ~okl_uptr{0}; for (unsigned i = 0; i < eh.phnum; ++i) { @@ -214,6 +227,12 @@ frame_regs from_context(void* uc) { okl::copy(&r.pc, b + 40 + 16 * 8, 8); okl::copy(&r.fp, b + 40 + 10 * 8, 8); okl::copy(&r.sp, b + 40 + 15 * 8, 8); +#elif defined(__i386__) + // struct ucontext: flags, link, stack (12), then the sigcontext's registers + // as words: gs fs es ds edi esi ebp esp ebx edx ecx eax trapno err eip. + okl::copy(&r.pc, b + 20 + 14 * 4, 4); + okl::copy(&r.fp, b + 20 + 6 * 4, 4); + okl::copy(&r.sp, b + 20 + 7 * 4, 4); #elif defined(__aarch64__) // flags, link, stack (24), the signal mask with its reserve (128), then // the sigcontext at a sixteen-byte boundary: fault address, x0..x30, sp, pc. @@ -231,7 +250,7 @@ int walk(frame_regs r, okl_uptr* pcs, int max) { if (r.pc && n < max) pcs[n++] = r.pc; okl_uptr fp = r.fp; for (int i = 0; i < 256 && n < max; ++i) { - if (fp == 0 || (fp & 7) != 0 || fp < r.sp) break; + if (fp == 0 || (fp & (sizeof(okl_uptr) - 1)) != 0 || fp < r.sp) break; okl_uptr pair[2]; if (!peek(fp, pair, sizeof pair)) break; if (pair[1] == 0) break; @@ -282,7 +301,7 @@ void report(int signo, void* info, void* uc) { -struct k_sigaction { void* handler; unsigned long flags; void* restorer; unsigned long mask; }; +using okl::k_sigaction; constexpr unsigned long sa_siginfo = 4, sa_onstack = 0x08000000, sa_restorer = 0x04000000, sa_resethand = 0x80000000, sa_restart = 0x10000000; @@ -347,7 +366,7 @@ bool g_segv_replaced = false; // The protection of the pages of [from, to) from /proc/self/maps: one for all // of them, or -1. int protection_of(okl_uptr from, okl_uptr to) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast("/proc/self/maps"), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/self/maps"), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return -1; char buf[4096], ln[1024]; @@ -509,7 +528,7 @@ int kal_prof_sample_begin(kal_u32 per_second) { // resident once a sample is written to it. constexpr okl_uptr cap = 65536; const okl_uptr bytes = cap * sizeof(kal_prof_sample) + cap * sizeof(okl_uptr); - const okl_long m = okl::sys(okl::nr_mmap, 0, static_cast(bytes), + const okl_long m = okl::mmap( 0, static_cast(bytes), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); if (okl::failed(m)) return kal_err_no_memory; g_ring.slots = reinterpret_cast(m); @@ -565,7 +584,7 @@ int kal_prof_touch_begin(const void* at, kal_uintptr size) { g_touch_page = g; if (!g_touches.pages) { constexpr okl_uptr cap = 65536; - const okl_long m = okl::sys(okl::nr_mmap, 0, static_cast(cap * 2 * sizeof(okl_uptr)), + const okl_long m = okl::mmap( 0, static_cast(cap * 2 * sizeof(okl_uptr)), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); if (okl::failed(m)) return kal_err_no_memory; g_touches.pages = reinterpret_cast(m); @@ -573,7 +592,7 @@ int kal_prof_touch_begin(const void* at, kal_uintptr size) { g_touches.cap = cap; } const okl_uptr bits = (size / g + 7) / 8; - const okl_long b = okl::sys(okl::nr_mmap, 0, static_cast(bits), okl::prot_read | okl::prot_write, + const okl_long b = okl::mmap( 0, static_cast(bits), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); if (okl::failed(b)) return kal_err_no_memory; // The handler resolves the touches, whoever holds SIGSEGV. diff --git a/src/process.cpp b/src/process.cpp index 0ad09b9..122cc1f 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -93,7 +93,7 @@ void close_on_exec_from(okl_long from) { okl::close_range_cloexec); if (!okl::failed(r)) return; - const okl_long dir = okl::sys(okl::nr_openat, okl::at_fdcwd, + const okl_long dir = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/self/fd"), okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0); if (!okl::failed(dir)) { @@ -408,8 +408,37 @@ extern "C" { // first one ended (openkal `kal_process_open_id'). namespace { constexpr unsigned kind_child = 0, kind_self = 1, kind_opened = 2, kind_observed = 3; +okl_long opened_pid(okl_long fd); // below: the number a program's descriptor stands for +#if defined(__i386__) +// A THIRTY-TWO BIT WORD (clause 5.5): the kind in the top two bits, a number +// (pid_max is at most 2^22) in the low twenty-two; an observed program keeps the +// low eight bits of its start between them, so a number reused within 256 +// ticks of a start is not told apart; an opened one carries its descriptor +// only, and its number is read from the descriptor's /proc/self/fdinfo entry. +inline unsigned handle_kind(kal_process h) { return static_cast(h.h >> 30); } +inline okl_long opened_fd(kal_process h) { return static_cast(h.h & 0xffffu) - 1; } +inline okl_long handle_pid(kal_process h) { + return handle_kind(h) == kind_opened ? opened_pid(opened_fd(h)) : static_cast(h.h & 0x3fffffu); +} +inline bool same_start(kal_process h, okl_u64 ticks) { return (ticks & 0xffu) == ((h.h >> 22) & 0xffu); } +inline kal_uintptr make_handle(unsigned kind, okl_long pid, okl_u64 extra) { + const kal_uintptr k = static_cast(kind) << 30; + if (kind == kind_opened) return k | static_cast(extra & 0xffffu); + return k | (static_cast(extra & 0xffu) << 22) | static_cast(pid & 0x3fffff); +} +#else +// The kind in the top byte, which no identifier reaches; the number in the low +// thirty-two bits; between them an opened program's descriptor (plus one) or +// an observed program's start (low twenty-four bits). inline unsigned handle_kind(kal_process h) { return static_cast(h.h >> 56); } +inline okl_long opened_fd(kal_process h) { return static_cast((h.h >> 32) & 0xffffu) - 1; } inline okl_long handle_pid(kal_process h) { return static_cast(h.h & 0xffffffffu); } +inline bool same_start(kal_process h, okl_u64 ticks) { return (ticks & 0xffffffu) == ((h.h >> 32) & 0xffffffu); } +inline kal_uintptr make_handle(unsigned kind, okl_long pid, okl_u64 extra) { + return (static_cast(kind) << 56) | (static_cast(extra & 0xffffffu) << 32) + | static_cast(pid); +} +#endif } // namespace // --- where `execveat' cannot be used (0.18) ---------------------------------- @@ -438,7 +467,7 @@ int g_by_name = -1; // -1 not yet decided, 0 execveat, 1 execve // hides it, a PROOT_ variable. A debugger also traces and is not a translator. bool under_translator() { char st[4096]; - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/self/status"), okl::o_rdonly | okl::o_cloexec, 0); okl_long n = 0; @@ -458,7 +487,7 @@ bool under_translator() { char path[48] = "/proc/"; char* o = put_decimal(path + 6, static_cast(pid)); okl::copy(o, "/comm", 6); - const okl_long cfd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + const okl_long cfd = okl::openat( okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec, 0); if (!okl::failed(cfd)) { char comm[32] = {}; @@ -500,9 +529,8 @@ bool absolute_under(int fd, const char* name, okl_uptr len, char* out, okl_uptr // Whether `name' reaches the file `fd' holds: the same device and inode. bool same_file(okl_long fd, const char* name) { okl::kstat held{}, named{}; - if (okl::failed(okl::sys(okl::nr_fstat, fd, reinterpret_cast(&held)))) return false; - if (okl::failed(okl::sys(okl::nr_newfstatat, okl::at_fdcwd, reinterpret_cast(name), - reinterpret_cast(&named), 0))) + if (okl::failed(okl::fstat(fd, &held))) return false; + if (okl::failed(okl::fstatat(okl::at_fdcwd, name, &named, 0))) return false; return held.dev == named.dev && held.ino == named.ino; } @@ -582,7 +610,7 @@ okl_long confine_to(const okl_u32* rights, kal_uintptr g, int base, const char* if (okl::failed(r)) why = r; } auto by_name = [&](okl_long at, const char* name, okl_u64 allowed) { - const okl_long fd = okl::sys(okl::nr_openat, at, reinterpret_cast(name), + const okl_long fd = okl::openat( at, reinterpret_cast(name), okl::o_path | okl::o_cloexec, 0); if (okl::failed(fd)) return; // absent: nothing to allow add(allowed, fd); @@ -642,7 +670,7 @@ int start_image(void* arg) { // replacement keeps it. for (int signo = 1; signo <= 64; ++signo) { if (signo == 9 || signo == 19) continue; // SIGKILL, SIGSTOP - struct { void* handler; unsigned long flags; void* restorer; unsigned long mask; } act{}; + okl::k_sigaction act{}; if (okl::failed(okl::sys(okl::nr_rt_sigaction, signo, 0, reinterpret_cast(&act), sizeof act.mask))) continue; @@ -683,7 +711,7 @@ int start_image(void* arg) { const kal_uintptr nulls = how->flags & (KAL_SPAWN_NULL_IN | KAL_SPAWN_NULL_OUT | KAL_SPAWN_NULL_ERR); okl_long device = 0; if (nulls != 0) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast("/dev/null"), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/dev/null"), okl::o_rdwr | okl::o_cloexec, 0); if (okl::failed(fd)) { report.say(fd); @@ -746,8 +774,7 @@ int start_image(void* arg) { // SIGPIPE AS THIS PROGRAM WAS STARTED WITH, not as this implementation // set it for its own writes (env.cpp): an ignore would outlive `execve'. if (!okl::g_pipe_started_ignored) { - struct { void* handler; unsigned long flags; void* restorer; unsigned long mask; } - standard{ nullptr /* SIG_DFL */, 0, nullptr, 0 }; + okl::k_sigaction standard{ nullptr /* SIG_DFL */, 0, nullptr, 0 }; okl::sys(okl::nr_rt_sigaction, 13 /* SIGPIPE */, reinterpret_cast(&standard), 0, sizeof standard.mask); } @@ -856,7 +883,7 @@ int start_image(void* arg) { okl::sys(okl::nr_exit_group, 127); for (;;) { } } - okl_long exe = okl::sys(okl::nr_openat, base, reinterpret_cast(p.buf), + okl_long exe = okl::openat( base, reinterpret_cast(p.buf), okl::o_path | okl::o_cloexec, 0); if (okl::failed(exe)) { report.say(exe); @@ -1037,7 +1064,7 @@ int kal_process_spawn(const kal_spawn* how, const start_plan plan{ how, &p, granted, b, w, in, ou, er, &args, &envs, &report, resolved, named, use_name, unit, bind, join, mine, &caller_mask, &carry, confine, grant_rights }; - const okl_long stack = okl::sys(okl::nr_mmap, 0, static_cast(kStartStack), + const okl_long stack = okl::mmap( 0, static_cast(kStartStack), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous, -1, 0); const okl_long child = okl::failed(stack) @@ -1118,7 +1145,7 @@ int kal_process_wait(kal_process h, int* status, int* terminated_by_environment) // peak memory, which the record keeps --- can still be asked about. A // program ended by its unit's limit is told apart by the unit (openkal 0.20). bool ended = false; - return okl::child_outcome(static_cast(h.h & 0xffffffffu), true, status, + return okl::child_outcome(handle_pid(h), true, status, terminated_by_environment, ended); } @@ -1207,7 +1234,7 @@ void kal_process_close(kal_process h) { return; } if (handle_kind(h) == kind_opened) { - const okl_long fd = static_cast((h.h >> 32) & 0xffffu) - 1; + const okl_long fd = opened_fd(h); if (fd >= 0) okl::sys(okl::nr_close, fd); } } @@ -1237,20 +1264,11 @@ namespace { kal_u32 g_stop_word = 0; int g_stop_armed = 0; -#if defined(__x86_64__) -extern "C" void okl_sigreturn_trampoline(void); -// IN `.text', SAID IN SO MANY WORDS (0.18). A top-level `asm' is emitted into -// whatever section the compiler is in at that point of the file, and GCC at -// -O0 was in `.bss': the three instructions were zero bytes in memory that may -// not be executed, and the first request to end a program faulted on its way -// back from the handler (measured: SIGSEGV after SIGTERM, GCC -O0 only). -asm(".pushsection .text,\"ax\",@progbits\n" - ".globl okl_sigreturn_trampoline\n" - "okl_sigreturn_trampoline:\n" - " movq $15, %rax\n" // rt_sigreturn - " syscall\n" - ".popsection\n"); -constexpr unsigned long sa_restorer_flag = 0x04000000u; // SA_RESTORER +// The return from a handler is the one observe.cpp defines (okl_signal_return), +// named on the architectures whose kernel requires it of a handler installed +// without a C library. +#if defined(__x86_64__) || defined(__i386__) +constexpr unsigned long sa_restorer_flag = okl::sa_restorer_flag; #endif // Which request arrived first, for `kal_process_end_as_requested'. @@ -1287,7 +1305,7 @@ void stop_handler(int signo) { // code written for the other kernel, where the trampoline made the failure // mode obvious. bool arm_one(int signo) { - struct { void* handler; unsigned long flags; void* restorer; unsigned long mask; } act {}; + okl::k_sigaction act {}; // A REQUEST THE STARTER ARRANGED NOT TO DELIVER STAYS UNDELIVERED (0.17, // the take-over rule). A program started in the background is started with // these requests ignored, and taking them over would end it when its @@ -1299,9 +1317,9 @@ bool arm_one(int signo) { if (!okl::failed(r) && old.handler == reinterpret_cast(1) /* SIG_IGN */) return true; } act.handler = reinterpret_cast(&stop_handler); -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(__i386__) act.flags = sa_restorer_flag; - act.restorer = reinterpret_cast(&okl_sigreturn_trampoline); + act.restorer = reinterpret_cast(&okl_signal_return); #endif return !okl::failed(okl::sys(okl::nr_rt_sigaction, signo, reinterpret_cast(&act), 0, sizeof act.mask)); @@ -1355,7 +1373,7 @@ okl_long read_stat(okl_long pid, char (&b)[1024]) { if (pid == 0) { okl::copy(o, "self", 4); o += 4; } else o = put_decimal(o, static_cast(pid)); okl::copy(o, "/stat", 6); - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return 0; const okl_long n = okl::sys(okl::nr_read, fd, reinterpret_cast(b), sizeof b - 1); @@ -1363,6 +1381,29 @@ okl_long read_stat(okl_long pid, char (&b)[1024]) { return n > 0 ? n : 0; } +// The number a program's descriptor stands for (`Pid:' in the descriptor's +// /proc/self/fdinfo entry), or 0. Only i386 asks: its handle has no room for +// both (see handle_kind). +okl_long opened_pid(okl_long fd) { + if (fd < 0) return 0; + char path[48] = "/proc/self/fdinfo/"; + okl::copy(put_decimal(path + 18, static_cast(fd)), "", 1); + const okl_long f = okl::openat(okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec); + if (okl::failed(f)) return 0; + char b[512]; + const okl_long n = okl::sys(okl::nr_read, f, reinterpret_cast(b), sizeof b - 1); + okl::sys(okl::nr_close, f); + for (okl_long i = 0; i + 4 < n; ++i) { + if (b[i] != 'P' || b[i + 1] != 'i' || b[i + 2] != 'd' || b[i + 3] != ':' || (i > 0 && b[i - 1] != '\n')) + continue; + okl_long v = 0, j = i + 4; + while (j < n && (b[j] == ' ' || b[j] == '\t')) ++j; + while (j < n && b[j] >= '0' && b[j] <= '9') v = v * 10 + (b[j++] - '0'); + return v; + } + return 0; +} + // The fields after the name, which is in parentheses and may itself contain // them, so the last `)' is where the fields begin: the position of field // `wanted' (the state is 3, the processor times 14 and 15, the start 22), or -1. @@ -1457,13 +1498,13 @@ bool has_ended(kal_process h) { const char state = stat_state(handle_pid(h)); okl_u64 t = 0; return state == 0 || state == 'Z' || state == 'X' || !started_ticks(handle_pid(h), t) - || (t & 0xffffffu) != ((h.h >> 32) & 0xffffffu); + || !same_start(h, t); } if (handle_kind(h) == kind_opened) { - const okl_long fd = static_cast((h.h >> 32) & 0xffffu) - 1; + const okl_long fd = opened_fd(h); if (fd < 0) return false; struct { int fd; short events, revents; } pfd{ static_cast(fd), 1 /* POLLIN */, 0 }; - struct { okl_long sec, nsec; } zero{0, 0}; + okl::ktimespec zero{0, 0}; const okl_long r = okl::sys(okl::nr_ppoll, reinterpret_cast(&pfd), 1, reinterpret_cast(&zero), 0, 0); return r > 0 && (pfd.revents & 1) != 0; @@ -1483,8 +1524,7 @@ bool has_ended(kal_process h) { int kal_process_self(kal_process* out) { if (out == nullptr) return kal_err_invalid; - *out = kal_process{ (static_cast(kind_self) << 56) - | static_cast(okl::sys(okl::nr_getpid)) }; + *out = kal_process{ make_handle(kind_self, okl::sys(okl::nr_getpid), 0) }; return kal_ok; } @@ -1501,8 +1541,7 @@ int kal_process_open_id(kal_u64 id, kal_process* out) { okl_u64 t = 0; if (state == 0 || state == 'Z' || state == 'X' || !started_ticks(static_cast(id), t)) return kal_err_not_found; - *out = kal_process{ (static_cast(kind_observed) << 56) - | (static_cast(t & 0xffffffu) << 32) | static_cast(id) }; + *out = kal_process{ make_handle(kind_observed, static_cast(id), t) }; return kal_ok; }; if (!open_id_available()) { @@ -1514,9 +1553,7 @@ int kal_process_open_id(kal_u64 id, kal_process* out) { if (okl::failed(fd)) return okl::translate(fd); if (fd >= 0xffff) { okl::sys(okl::nr_close, fd); return kal_err_no_memory; } okl::sys(okl::nr_fcntl, fd, okl::f_setfd, okl::fd_cloexec); - *out = kal_process{ (static_cast(kind_opened) << 56) - | (static_cast(fd + 1) << 32) - | static_cast(id) }; + *out = kal_process{ make_handle(kind_opened, static_cast(id), static_cast(fd + 1)) }; return kal_ok; } @@ -1599,18 +1636,18 @@ int kal_process_end_as_requested(void) { if (__atomic_load_n(&g_stop_word, __ATOMIC_ACQUIRE) == 0) return kal_err_invalid; int signo = __atomic_load_n(&g_stop_signal, __ATOMIC_RELAXED); if (signo == 0) signo = 15; - struct { void* handler; unsigned long flags; void* restorer; unsigned long mask; } act {}; -#if defined(__x86_64__) + okl::k_sigaction act {}; +#if defined(__x86_64__) || defined(__i386__) act.flags = sa_restorer_flag; - act.restorer = reinterpret_cast(&okl_sigreturn_trampoline); + act.restorer = reinterpret_cast(&okl_signal_return); #endif okl::sys(okl::nr_rt_sigaction, signo, reinterpret_cast(&act), 0, sizeof act.mask); unsigned long set = 1ul << (signo - 1); okl::sys(okl::nr_rt_sigprocmask, 1 /* SIG_UNBLOCK */, reinterpret_cast(&set), 0, sizeof set); okl::sys(okl::nr_kill, okl::sys(okl::nr_getpid), signo); for (int i = 0; i < 100; ++i) { - struct { okl_long sec, nsec; } ts{0, 1000000}; - okl::sys(okl::nr_nanosleep, reinterpret_cast(&ts), 0); + okl::ktimespec ts{0, 1000000}; + okl::nanosleep(&ts, nullptr); } okl::sys(okl::nr_exit_group, 128 + signo); for (;;) { } @@ -1670,7 +1707,7 @@ kal_uintptr kal_process_props(void) { namespace { okl_long read_small(const char* path, char* b, okl_long cap) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return -1; okl_long n = 0; @@ -1762,7 +1799,7 @@ limited* row_of(okl_long unit) { kal_u32 members_of(okl_long unit, okl_u64& memory) { memory = 0; kal_u32 count = 0; - const okl_long dir = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast("/proc"), + const okl_long dir = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc"), okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0); if (okl::failed(dir)) return 0; alignas(8) char buf[8192]; @@ -1840,7 +1877,7 @@ void supervise(void*) { } const okl_i64 ms = worst < 500 ? 500 : worst < 800 ? 200 : 100; okl::ktimespec ts{ 0, ms * 1000000 }; - okl::sys(okl::nr_nanosleep, reinterpret_cast(&ts), 0); + okl::nanosleep(&ts, nullptr); } } @@ -1975,7 +2012,7 @@ struct unit_identity { bool unit_birth(okl_long pid, kal_u64& value) { okl_u64 ticks = 0; if (!started_ticks(pid,ticks)) return false; value = ticks; return true; } bool unit_origin(char* boot, char* space) { if (self_number() != okl::sys(okl::nr_getpid)) return false; - const auto fd = okl::sys(okl::nr_openat, okl::at_fdcwd, + const auto fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/sys/kernel/random/boot_id"), okl::o_rdonly | okl::o_cloexec, 0); if (okl::failed(fd)) return false; auto n = okl::sys(okl::nr_read, fd, reinterpret_cast(boot), 63); diff --git a/src/region.cpp b/src/region.cpp index 6871900..d537ca0 100644 --- a/src/region.cpp +++ b/src/region.cpp @@ -33,7 +33,7 @@ bool strict() { int v = __atomic_load_n(&g_strict, __ATOMIC_ACQUIRE); if (v >= 0) return v == 1; v = 0; - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast("/proc/sys/vm/overcommit_memory"), okl::o_rdonly | okl::o_cloexec, 0); if (!okl::failed(fd)) { @@ -50,7 +50,7 @@ okl_uptr granularity() { return static_cast(kal_memory_granularity()); bool multiple(okl_uptr v, okl_uptr g) { return (v & (g - 1)) == 0; } okl_long map_at(void* at, okl_uptr len, int prot, int extra) { - return okl::sys(okl::nr_mmap, reinterpret_cast(at), static_cast(len), prot, + return okl::mmap( reinterpret_cast(at), static_cast(len), prot, okl::map_private | okl::map_anonymous | extra, -1, 0); } diff --git a/src/rights.cpp b/src/rights.cpp index 5bd421c..0c9a0a3 100644 --- a/src/rights.cpp +++ b/src/rights.cpp @@ -212,7 +212,7 @@ bool list(root& r, const char* dir, okl_uptr dlen, okl_u64 key) { if (dlen + 1 >= sizeof path) return false; if (dlen == 0) { path[0] = '.'; path[1] = '\0'; } else { okl::copy(path, dir, dlen); path[dlen] = '\0'; } - const okl_long fd = okl::sys(okl::nr_openat, r.base, reinterpret_cast(path), + const okl_long fd = okl::openat( r.base, reinterpret_cast(path), okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0); if (okl::failed(fd)) return false; alignas(8) char buf[16384]; @@ -264,7 +264,7 @@ int attenuate(int fd, okl_u32 add, int record, int* out) { do { digits[n++] = static_cast('0' + v % 10); v /= 10; } while (v); while (n) proc[o++] = digits[--n]; proc[o] = '\0'; - okl_long r = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(proc), + okl_long r = okl::openat( okl::at_fdcwd, reinterpret_cast(proc), okl::o_wronly | okl::o_append | okl::o_cloexec, 0); if (okl::failed(r)) r = okl::sys(okl::nr_fcntl, record, okl::f_dupfd_cloexec, 3); if (okl::failed(r)) { okl::sys(okl::nr_close, nfd); return okl::translate(r); } diff --git a/src/start.cpp b/src/start.cpp index 1e1758d..29acb92 100644 --- a/src/start.cpp +++ b/src/start.cpp @@ -76,6 +76,21 @@ __asm__( " hlt\n" ".size _start,.-_start\n" ); +#elif defined(__i386__) +__asm__( +".text\n" +".globl _start\n" +".type _start,@function\n" +"_start:\n" +" xor %ebp,%ebp\n" // the outermost frame has no caller +" mov %esp,%eax\n" // the kernel left everything here +" and $-16,%esp\n" +" sub $12,%esp\n" // sixteen-byte aligned at the call, after the argument +" push %eax\n" +" call __okl_start_c\n" +" hlt\n" +".size _start,.-_start\n" +); #elif defined(__aarch64__) __asm__( ".text\n" diff --git a/src/sys.h b/src/sys.h index 8ba4b80..346f43b 100644 --- a/src/sys.h +++ b/src/sys.h @@ -35,6 +35,7 @@ using okl_ulong = unsigned long; using okl_u64 = unsigned long long; using okl_i64 = long long; using okl_u32 = unsigned; +using okl_i32 = int; using okl_uptr = __UINTPTR_TYPE__; namespace okl { @@ -244,8 +245,105 @@ enum : okl_long { nr_landlock_restrict_self = 446, nr_sysinfo = 179, }; +#elif defined(__i386__) + +// THE GATE IS `int $0x80', AND THE SIXTH ARGUMENT IS IN THE FRAME POINTER. The +// kernel's i386 convention takes the number in eax and the arguments in ebx, +// ecx, edx, esi, edi and ebp. ebp cannot be named as an operand of an inline +// statement in a function that has a frame, so the sixth is pushed first and +// moved into ebp between saving and restoring it --- the sequence every C +// library for this architecture uses. +inline okl_long sys(okl_long n) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n), "b"(a) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a, okl_long b) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n), "b"(a), "c"(b) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a, okl_long b, okl_long c) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n), "b"(a), "c"(b), "d"(c) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a, okl_long b, okl_long c, okl_long d) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n), "b"(a), "c"(b), "d"(c), "S"(d) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a, okl_long b, okl_long c, okl_long d, okl_long e) { + okl_long r; + __asm__ __volatile__("int $0x80" : "=a"(r) : "a"(n), "b"(a), "c"(b), "d"(c), "S"(d), "D"(e) : "memory"); + return r; +} +inline okl_long sys(okl_long n, okl_long a, okl_long b, okl_long c, okl_long d, okl_long e, okl_long f) { + okl_long r; + __asm__ __volatile__("pushl %7\n\tpush %%ebp\n\tmov 4(%%esp),%%ebp\n\tint $0x80\n\t" + "pop %%ebp\n\tadd $4,%%esp" + : "=a"(r) + : "a"(n), "b"(a), "c"(b), "d"(c), "S"(d), "D"(e), "g"(f) + : "memory"); + return r; +} + +// THE NUMBERS ARE THIS ARCHITECTURE'S OWN TABLE (arch/x86/entry/syscalls/ +// syscall_32.tbl), and where a call has a sixty-four bit time form the name +// below is that form (clause 5.5 of the specification): clock_gettime64, +// clock_getres_time64, ppoll_time64, futex_time64 and utimensat_time64 take the +// same arguments as the names they stand for, with the kernel's sixty-four bit +// timespec, which is `ktimespec'. The calls whose ARGUMENTS differ here --- an +// offset in two words, a record of another layout, another order --- have no +// number of the common name; they are reached through the functions below the +// tables, which every caller uses on every architecture. +enum : okl_long { + nr_read = 3, nr_write = 4, nr_close = 6, nr_fstat = -1, nr_lseek = -1, + nr_mmap = -1, nr_mprotect = 125, nr_munmap = 91, nr_ioctl = 54, nr_msync = 144, + nr_readv = 145, nr_writev = 146, nr_sched_yield = 158, nr_nanosleep = -1, + nr_getpid = 20, nr_clone = 120, nr_execve = 11, nr_exit = 1, nr_wait4 = 114, + nr_kill = 37, nr_ftruncate = -1, nr_getcwd = 183, nr_fsync = 118, + nr_fcntl = 221 /* fcntl64: the record locks' positions are sixty-four bits */, + nr_prctl = 172, nr_sched_getaffinity = 242, nr_getppid = 64, + nr_arch_prctl = -1, nr_gettid = 224, nr_futex = 422, + nr_getdents64 = 220, nr_set_tid_address = 258, nr_clock_gettime = 403, + nr_clock_getres = 406, nr_exit_group = 252, nr_tgkill = 270, + nr_openat = 295, nr_mkdirat = 296, nr_newfstatat = -1, nr_unlinkat = 301, + nr_renameat = 302, nr_readlinkat = 305, nr_dup3 = 330, nr_execveat = 358, + nr_close_range = 436, nr_prlimit64 = 340, + nr_dup2 = 63, nr_utimensat = 412, nr_symlinkat = 304, nr_linkat = 303, nr_fstatfs = -1, + nr_getrandom = 355, nr_sched_setscheduler = 156, + nr_mincore = 218, + nr_fchmodat = 306, + nr_fchdir = 133, nr_setpgid = 57, nr_getpgid = 132, nr_rt_sigaction = 174, + // Each call of a socket has its own number from kernel 4.3; `accept' has + // none here, and `accept4' with no flags is the same call. + nr_socket = 359, nr_connect = 362, nr_accept = -1, nr_sendto = 369, + nr_recvfrom = 371, nr_shutdown = 373, nr_bind = 361, nr_listen = 363, + nr_getsockname = 367, nr_getpeername = 368, nr_accept4 = 364, + nr_setsockopt = 366, nr_pipe2 = 331, + nr_ppoll = 414, + nr_renameat2 = 353, nr_pidfd_open = 434, nr_waitid = 284, + nr_rt_sigprocmask = 175, + nr_pread64 = -1, nr_pwrite64 = -1, nr_madvise = 219, nr_faccessat = 307, + nr_mseal = 462, nr_statx = 383, nr_getrusage = 77, nr_sigaltstack = 186, + nr_setitimer = 104, nr_rt_sigreturn = 173, nr_process_vm_readv = 347, + nr_landlock_create_ruleset = 444, nr_landlock_add_rule = 445, + nr_landlock_restrict_self = 446, nr_sysinfo = 116, + // Only here: the forms the functions below reach, and the thread pointer's + // segment. + nr_llseek = 140, nr_mmap2 = 192, nr_ftruncate64 = 194, nr_pread64_32 = 180, + nr_pwrite64_32 = 181, nr_clock_nanosleep_time64 = 407, nr_fstatfs64 = 269, + nr_set_thread_area = 243, +}; + #else -#error "openkal-linux supports x86_64 and aarch64" +#error "openkal-linux supports x86_64, aarch64 and i386" #endif // --- error values, as the kernel returns them ------------------------------ @@ -333,7 +431,7 @@ enum : okl_long { // "End this context when the one that started it ends", which is what // binding a started program's lifetime is expressed as here. pr_set_pdeathsig = 1, -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(__i386__) o_directory = 0200000, o_nofollow = 0400000, #else o_directory = 040000, o_nofollow = 0100000, @@ -395,7 +493,9 @@ inline bool interrupted(okl_long r) { return r == -e_intr; } struct kflock { short l_type; short l_whence; - int l_pad; +#if !defined(__i386__) + int l_pad; // i386's `struct flock64' aligns the positions to four +#endif okl_i64 l_start; okl_i64 l_len; int l_pid; @@ -421,7 +521,7 @@ struct kstatfs { // A property that varies between the RESOURCES of an interface is answered by // an enquiry taking the resource, and on this kernel the resource's format is // what the enquiry has to consult. -enum : okl_long { +enum : okl_ulong { fs_ext234 = 0xEF53, fs_btrfs = 0x9123683E, fs_xfs = 0x58465342, @@ -440,10 +540,13 @@ enum : okl_long { fs_hfsplus = 0x482B, }; +// On i386 this record is not the kernel's: its `stat64' carries thirty-two bit +// times, and clause 5.5 of the specification asks for the sixty-four bit form, +// so `fstat' and `fstatat' below fill it from `statx'. It has x86_64's order. struct kstat { okl_u64 dev; okl_u64 ino; -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(__i386__) okl_u64 nlink; okl_u32 mode; okl_u32 uid; @@ -498,7 +601,9 @@ struct kstat { // The kernel writes this record; a field read from the wrong offset is a wrong // answer and not a failure, so the offsets are stated here rather than trusted. -#if defined(__x86_64__) +#if defined(__i386__) +// Not the kernel's record: nothing to state. +#elif defined(__x86_64__) static_assert(__builtin_offsetof(kstat, mode) == 24, "x86_64 struct stat"); static_assert(__builtin_offsetof(kstat, size) == 48, "x86_64 struct stat"); static_assert(__builtin_offsetof(kstat, mtime_sec) == 88, "x86_64 struct stat"); @@ -508,7 +613,9 @@ static_assert(__builtin_offsetof(kstat, size) == 48, "asm-generic struct st static_assert(__builtin_offsetof(kstat, mtime_sec) == 88, "asm-generic struct stat"); #endif static_assert(__builtin_offsetof(kstat, ino) == 8, "the inode follows the device"); +#if !defined(__i386__) static_assert(sizeof(kstat) >= 128, "the kernel writes at least this much"); +#endif enum : okl_u32 { s_ifmt = 0170000, s_ifreg = 0100000, s_ifdir = 0040000, s_iflnk = 0120000, @@ -516,6 +623,156 @@ enum : okl_u32 { struct ktimespec { okl_i64 sec; okl_i64 nsec; }; +// The kernel's `struct sigaction', as `rt_sigaction' takes it: the mask is the +// kernel's sixty-four bit set on every architecture this implementation +// supports --- two words on i386, where an `unsigned long' mask was half of it +// and the size the call is told (`sizeof mask') was refused. One declaration for +// every unit that installs a handler. +struct k_sigaction { void* handler; okl_ulong flags; void* restorer; okl_u64 mask; }; +constexpr okl_ulong sa_restorer_flag = 0x04000000u; // SA_RESTORER + +} // namespace okl +// The return from a handler this implementation installs, which a handler +// installed without a C library must name on x86_64 and i386 (observe.cpp). +extern "C" void okl_signal_return(); +namespace okl { + +// --- the calls whose arguments are not the same on every architecture ---------- +// +// EVERY CALLER USES THESE, ON EVERY ARCHITECTURE. On x86_64 and aarch64 each is +// the call itself. On i386 a sixty-four bit position travels in two words +// (`_llseek', `mmap2' in pages, `pread64', `ftruncate64'), the status of a node +// comes from `statx' (the `stat64' record's times are thirty-two bits), and +// `nanosleep' is `clock_nanosleep_time64'. A caller that named the common +// number directly would compile on i386 and pass a truncated position: the +// numbers above are -1 there, so such a call fails rather than misbehaves. +#if defined(__i386__) +struct kstatx_time { okl_i64 sec; okl_u32 nsec; okl_i32 pad; }; +struct kstatx { + okl_u32 mask, blksize; + okl_u64 attributes; + okl_u32 nlink, uid, gid; + unsigned short mode, pad0; + okl_u64 ino, size, blocks, attributes_mask; + kstatx_time atime, btime, ctime, mtime; + okl_u32 rdev_major, rdev_minor, dev_major, dev_minor; + okl_u64 spare[14]; +}; +static_assert(__builtin_offsetof(kstatx, mode) == 28 && __builtin_offsetof(kstatx, mtime) == 112 && + __builtin_offsetof(kstatx, dev_major) == 136 && sizeof(kstatx) == 256, "struct statx"); +inline okl_u64 encode_dev(okl_u32 major, okl_u32 minor) { + return (okl_u64{major & 0xfffff000u} << 32) | (okl_u64{major & 0xfffu} << 8) | + (okl_u64{minor & 0xffffff00u} << 12) | okl_u64{minor & 0xffu}; +} +inline okl_long statx_into(okl_long dirfd, const char* path, okl_long flags, kstat* st) { + kstatx x{}; + const okl_long r = sys(nr_statx, dirfd, reinterpret_cast(path), flags, + 0x7ff /* STATX_BASIC_STATS */, reinterpret_cast(&x)); + if (r < 0) return r; + *st = kstat{}; + st->dev = encode_dev(x.dev_major, x.dev_minor); + st->ino = x.ino; + st->nlink = x.nlink; + st->mode = x.mode; + st->uid = x.uid; + st->gid = x.gid; + st->rdev = encode_dev(x.rdev_major, x.rdev_minor); + st->size = static_cast(x.size); + st->blksize = x.blksize; + st->blocks = static_cast(x.blocks); + st->atime_sec = x.atime.sec; st->atime_nsec = x.atime.nsec; + st->mtime_sec = x.mtime.sec; st->mtime_nsec = x.mtime.nsec; + st->ctime_sec = x.ctime.sec; st->ctime_nsec = x.ctime.nsec; + return 0; +} +#endif + +// A file opened by a thirty-two bit program is limited to two gibibytes unless +// it asks otherwise (EOVERFLOW); a sixty-four bit program is never limited. +inline okl_long openat(okl_long dirfd, okl_long path, okl_long flags, okl_long mode = 0) { +#if defined(__i386__) + flags |= 0100000; // O_LARGEFILE +#endif + return sys(nr_openat, dirfd, path, flags, mode); +} + +inline okl_long fstat(okl_long fd, kstat* st) { +#if defined(__i386__) + return statx_into(fd, "", 0x1000 /* AT_EMPTY_PATH */, st); +#else + return sys(nr_fstat, fd, reinterpret_cast(st)); +#endif +} +inline okl_long fstatat(okl_long dirfd, const char* path, kstat* st, okl_long flags) { +#if defined(__i386__) + return statx_into(dirfd, path, flags, st); +#else + return sys(nr_newfstatat, dirfd, reinterpret_cast(path), reinterpret_cast(st), flags); +#endif +} +inline okl_long mmap(okl_long addr, okl_long len, okl_long prot, okl_long flags, okl_long fd, okl_i64 off) { +#if defined(__i386__) + if (off & 4095) return -22; // EINVAL: mmap2 counts pages + return sys(nr_mmap2, addr, len, prot, flags, fd, static_cast(off >> 12)); +#else + return sys(nr_mmap, addr, len, prot, flags, fd, static_cast(off)); +#endif +} +inline okl_i64 lseek(okl_long fd, okl_i64 off, okl_long whence) { +#if defined(__i386__) + okl_i64 at = 0; + const okl_long r = sys(nr_llseek, fd, static_cast(static_cast(off) >> 32), + static_cast(static_cast(off) & 0xffffffffu), + reinterpret_cast(&at), whence); + return r < 0 ? okl_i64{r} : at; +#else + return sys(nr_lseek, fd, static_cast(off), whence); +#endif +} +inline okl_long pread(okl_long fd, void* buf, okl_uptr n, okl_i64 off) { +#if defined(__i386__) + return sys(nr_pread64_32, fd, reinterpret_cast(buf), static_cast(n), + static_cast(static_cast(off) & 0xffffffffu), + static_cast(static_cast(off) >> 32)); +#else + return sys(nr_pread64, fd, reinterpret_cast(buf), static_cast(n), static_cast(off)); +#endif +} +inline okl_long pwrite(okl_long fd, const void* buf, okl_uptr n, okl_i64 off) { +#if defined(__i386__) + return sys(nr_pwrite64_32, fd, reinterpret_cast(buf), static_cast(n), + static_cast(static_cast(off) & 0xffffffffu), + static_cast(static_cast(off) >> 32)); +#else + return sys(nr_pwrite64, fd, reinterpret_cast(buf), static_cast(n), static_cast(off)); +#endif +} +inline okl_long ftruncate(okl_long fd, okl_i64 size) { +#if defined(__i386__) + return sys(nr_ftruncate64, fd, static_cast(static_cast(size) & 0xffffffffu), + static_cast(static_cast(size) >> 32)); +#else + return sys(nr_ftruncate, fd, static_cast(size)); +#endif +} +inline okl_long fstatfs(okl_long fd, kstatfs* sf) { +#if defined(__i386__) + // `statfs64': this record, with its size stated, since the thirty-two bit + // `statfs' cannot report a volume's blocks past four gibibytes. + return sys(nr_fstatfs64, fd, static_cast(sizeof(kstatfs)), reinterpret_cast(sf)); +#else + return sys(nr_fstatfs, fd, reinterpret_cast(sf)); +#endif +} +inline okl_long nanosleep(const ktimespec* req, ktimespec* rem) { +#if defined(__i386__) + return sys(nr_clock_nanosleep_time64, 1 /* CLOCK_MONOTONIC */, 0, reinterpret_cast(req), + reinterpret_cast(rem)); +#else + return sys(nr_nanosleep, reinterpret_cast(req), reinterpret_cast(rem)); +#endif +} + struct kdirent64 { okl_u64 ino; okl_i64 off; diff --git a/src/task.cpp b/src/task.cpp index d6b1f2e..2d73af5 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -57,6 +57,45 @@ __asm__( "1:ret\n" ".size __okl_clone,.-__okl_clone\n" ); +#elif defined(__i386__) +__asm__( +".text\n" +".globl __okl_clone\n" +".hidden __okl_clone\n" +".type __okl_clone,@function\n" +"__okl_clone:\n" // 4(%esp)=fn stack flags arg ptid tls ctid, cdecl +" push %ebx\n" +" push %esi\n" +" push %edi\n" +" push %ebp\n" // the arguments are now at 20(%esp) +" mov 20(%esp),%ebp\n" // fn: the child inherits every register but eax and esp +" mov 24(%esp),%ecx\n" // stack +" and $-16,%ecx\n" +" sub $16,%ecx\n" +" mov 32(%esp),%eax\n" +" mov %eax,(%ecx)\n" // arg, the child's one argument +" mov 28(%esp),%ebx\n" // flags +" mov 36(%esp),%edx\n" // ptid +" mov 40(%esp),%esi\n" // tls (a user_desc on this architecture) +" mov 44(%esp),%edi\n" // ctid +" mov $120,%eax\n" // SYS_clone +" int $0x80\n" +" test %eax,%eax\n" +" jnz 1f\n" +" mov %ebp,%eax\n" +" xor %ebp,%ebp\n" +" call *%eax\n" +" mov %eax,%ebx\n" +" mov $1,%eax\n" // SYS_exit, this context only +" int $0x80\n" +" hlt\n" +"1:pop %ebp\n" +" pop %edi\n" +" pop %esi\n" +" pop %ebx\n" +" ret\n" +".size __okl_clone,.-__okl_clone\n" +); #elif defined(__aarch64__) __asm__( ".text\n" @@ -463,7 +502,7 @@ int translate_posix(int e) { // another allocation places later can occupy the addresses beneath the stack. bool map_stack(context* c, okl_uptr bytes) { const okl_uptr total = bytes + kGuard; - const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast(total), + const okl_long r = okl::mmap( 0, static_cast(total), okl::prot_read | okl::prot_write, okl::map_private | okl::map_anonymous | okl::map_stack, -1, 0); if (okl::failed(r)) return false; @@ -516,8 +555,14 @@ int start(void (*entry)(void*), void* arg, okl_uptr want, kal_task* out) { | 0x00100000 // CLONE_PARENT_SETTID | 0x00200000; // CLONE_CHILD_CLEARTID auto* top = static_cast(c->stack) + c->stack_bytes; +#if defined(__i386__) + okl::user_desc desc = okl::tls_descriptor(c->tls.tp); + void* const tls_argument = &desc; +#else + void* const tls_argument = c->tls.tp; +#endif const okl_long r = __okl_clone(run, top, flags, c, - const_cast(&c->tid), c->tls.tp, + const_cast(&c->tid), tls_argument, const_cast(&c->tid)); if (okl::failed(r)) { unmap_stack(c); @@ -554,7 +599,7 @@ namespace { // A small number from a file of the kernel's: what /sys answers is one line. bool read_number(const char* path, okl_long& out) { - const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + const okl_long fd = okl::openat( okl::at_fdcwd, reinterpret_cast(path), okl::o_rdonly | okl::o_cloexec); if (okl::failed(fd)) return false; char text[32]; @@ -759,7 +804,7 @@ kal_uintptr kal_task_cores(void) { reinterpret_cast(mask)); if (okl::failed(n) || n <= 0) return 0; constexpr unsigned kMax = 1024; - okl_long seen[kMax]; + okl_i64 seen[kMax]; unsigned count = 0; const unsigned long bits = static_cast(n) * 8; constexpr unsigned long kWord = 8 * sizeof(unsigned long); @@ -767,7 +812,7 @@ kal_uintptr kal_task_cores(void) { if (!((mask[cpu / kWord] >> (cpu % kWord)) & 1ul)) continue; okl_long package = 0, core = 0; if (!cpu_topology(cpu, "physical_package_id", package) || !cpu_topology(cpu, "core_id", core)) return 0; - const okl_long key = (package << 32) | core; + const okl_i64 key = (static_cast(package) << 32) | core; bool known = false; for (unsigned i = 0; i < count && !known; ++i) known = seen[i] == key; if (known) continue; diff --git a/src/time.cpp b/src/time.cpp index aa19a9c..54210a5 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -54,6 +54,12 @@ okl_u64 gnu_symbol_count(const unsigned* h) { } vdso_clock find_vdso_clock() { +#if defined(__i386__) + // This architecture's object is ELF32 and its clock entry takes the + // thirty-two bit record; the call below is the sixty-four bit form + // (clock_gettime64), which clause 5.5 asks for. + return nullptr; +#endif const okl_ulong base = okl::auxval(33 /* AT_SYSINFO_EHDR */); if (base == 0) return nullptr; const auto* eh = reinterpret_cast(base); @@ -133,8 +139,7 @@ void kal_time_sleep(kal_duration ns) { // writes what is left into the same structure, which is why it is not // const. for (;;) { - const okl_long r = okl::sys(okl::nr_nanosleep, reinterpret_cast(&req), - reinterpret_cast(&req)); + const okl_long r = okl::nanosleep(&req, &req); if (!okl::interrupted(r)) return; } } diff --git a/src/timeout.cpp b/src/timeout.cpp index e21aba3..cc86709 100644 --- a/src/timeout.cpp +++ b/src/timeout.cpp @@ -163,7 +163,7 @@ int kal_timeout_wait_process(kal_process p, kal_u64 ns, int* status, int* termin continue; } okl::ktimespec ts{ 0, static_cast(interval_ns) }; - okl::sys(okl::nr_nanosleep, reinterpret_cast(&ts), 0); + okl::nanosleep(&ts, nullptr); waited += interval_ns; } } diff --git a/src/tls.h b/src/tls.h index 6ed9e87..792e7fa 100644 --- a/src/tls.h +++ b/src/tls.h @@ -39,10 +39,18 @@ inline okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - // loader has already applied any bias, so the addresses are the ones the // program will use. inline void describe_tls(okl_ulong phdr, okl_ulong phent, okl_ulong phnum) { + // The program headers of this program's own class: ELF32 on i386 orders + // the fields otherwise and makes each a word. +#if defined(__i386__) + struct elf_phdr { + okl_u32 type, offset, vaddr, paddr, filesz, memsz, flags, align; + }; +#else struct elf_phdr { okl_u32 type, flags; okl_u64 offset, vaddr, paddr, filesz, memsz, align; }; +#endif constexpr okl_u32 pt_tls = 7; auto& im = image(); im.known = true; @@ -140,7 +148,7 @@ inline tls_block make_tls(void* (*alloc)(okl_uptr, okl_uptr)) { // program cannot see. The caller of `kal_task_start' is told instead. if (im.memsz == 0) return b; -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(__i386__) // Variant II: the storage lies below the thread pointer, and the word the // thread pointer addresses holds the thread pointer itself, which is how a // program obtains it without an instruction that reads the register. @@ -174,8 +182,34 @@ inline tls_block make_tls(void* (*alloc)(okl_uptr, okl_uptr)) { return b; } +#if defined(__i386__) +// THE THREAD POINTER IS A SEGMENT ON i386: a descriptor in the global table +// whose base is the pointer, selected by %gs. The kernel gives the process one +// entry the first time (`entry_number' -1) and the same entry is then filled +// for each context, at its start by `clone' (CLONE_SETTLS takes this record +// here, not the pointer). +struct user_desc { + unsigned entry_number, base_addr, limit; + unsigned flags; // seg_32bit 1, contents 0, read_exec_only 0, limit_in_pages 1, useable 1 +}; +inline unsigned& tls_entry() { + static unsigned entry = static_cast(-1); + return entry; +} +inline user_desc tls_descriptor(void* tp) { + return user_desc{ tls_entry(), static_cast(reinterpret_cast(tp)), 0xfffffu, + 0x1u | 0x10u | 0x40u }; +} +#endif + inline void set_thread_pointer(void* tp) { -#if defined(__x86_64__) +#if defined(__i386__) + user_desc d = tls_descriptor(tp); + if (failed(sys(nr_set_thread_area, reinterpret_cast(&d)))) return; + tls_entry() = d.entry_number; + const unsigned short selector = static_cast(d.entry_number * 8 + 3); + __asm__ __volatile__("movw %w0, %%gs" :: "q"(selector)); +#elif defined(__x86_64__) sys(nr_arch_prctl, 0x1002 /* ARCH_SET_FS */, reinterpret_cast(tp)); #elif defined(__aarch64__) __asm__ __volatile__("msr tpidr_el0, %0" :: "r"(tp)); diff --git a/tests/word32/runs.cpp b/tests/word32/runs.cpp new file mode 100644 index 0000000..4564e91 --- /dev/null +++ b/tests/word32/runs.cpp @@ -0,0 +1,74 @@ +// A thirty-two bit build of this implementation runs (clause 5.5 of the specification): built +// standalone for i386 by tools/word32-check.sh and run on the machine that built it. A file past +// four gibibytes, a task with its own thread-local storage, a sleep through the sixty-four bit +// clock. The program supplies the C library facilities the compiler calls. +#include +#include +#include +#include +#include + +static int failures = 0; +static void say(const char* s) { kal_uintptr n = 0; while (s[n]) ++n; kal_stream_write(kal_stdout(), s, n); } +static void check(bool ok, const char* what) { say(ok ? " ok " : " FAIL "); say(what); say("\n"); if (!ok) ++failures; } + +thread_local int tlv = 7; +static volatile int shared = 0; +static void task_body(void*) { tlv = 99; shared = tlv + 1; } + +extern "C" int main(int, char**, char**) { + say("openkal-linux on i386\n"); + check(sizeof(void*) == 4, "a pointer is four bytes"); + kal_dir work{}; + char name[256]; kal_uintptr name_len = 0; + const kal_uintptr n = kal_fs_preopen_count(); + bool found = false; + for (kal_uintptr i = 0; i < n && !found; ++i) + if (kal_fs_preopen(i, &work, name, sizeof name, &name_len) == kal_ok) { + found = true; // the first is the working directory + } + check(found, "a directory was supplied"); + kal_file f{}; + const char fname[] = "okl-i386-big"; + int rc = kal_fs_open(work, fname, sizeof fname - 1, KAL_OPEN_READ | KAL_OPEN_WRITE | KAL_OPEN_CREATE | KAL_OPEN_TRUNCATE, &f); + check(rc == kal_ok, "a file is created"); + const kal_u64 far = (kal_u64{5} << 30); + check(kal_fs_write_at(f, "0123456789", 10, far) == 10, "ten bytes are written at 5 GiB"); + char back[10] = {}; + check(kal_fs_read_at(f, back, 10, far) == 10 && back[0] == '0' && back[9] == '9', "they are read back at 5 GiB"); + kal_node_info info{}; info.self_size = sizeof info; + rc = kal_fs_file_info(f, KAL_INFO_SIZE, &info); + check(rc == kal_ok && info.size == far + 10, "the file's size is past 4 GiB"); + kal_u64 at = 0; + rc = kal_fs_seek(f, 0, KAL_SEEK_END, &at); + check(rc == kal_ok && at == far + 10, "a seek to the end answers a position past 4 GiB"); + kal_fs_close_file(f); + check(kal_fs_remove(work, fname, sizeof fname - 1) == kal_ok, "the file is removed"); + kal_task t{}; + check(kal_task_start(task_body, nullptr, &t) == kal_ok, "a task starts"); + check(kal_task_join(t) == kal_ok, "it is joined"); + check(shared == 100, "the task saw its own thread-local value"); + check(tlv == 7, "this context's thread-local value is its own"); + const kal_duration before = kal_time_monotonic(); + kal_time_sleep(20u * 1000u * 1000u); + check(kal_time_monotonic() - before >= 20u * 1000u * 1000u, "a sleep of 20 ms lasts at least that"); + say(failures ? "FAILED\n" : "all held\n"); + return failures; +} + +// What the compiler calls and a C library would supply. +extern "C" { +void* memcpy(void* d, const void* s, __SIZE_TYPE__ n) { auto* o = static_cast(d); auto* i = static_cast(s); while (n--) *o++ = *i++; return d; } +void* memset(void* d, int c, __SIZE_TYPE__ n) { auto* o = static_cast(d); while (n--) *o++ = static_cast(c); return d; } +unsigned long long __udivmoddi4(unsigned long long a, unsigned long long b, unsigned long long* rem) { + unsigned long long q = 0, r = 0; + for (int i = 63; i >= 0; --i) { + r = (r << 1) | ((a >> i) & 1u); + if (r >= b) { r -= b; q |= 1ull << i; } + } + if (rem) *rem = r; + return q; +} +unsigned long long __udivdi3(unsigned long long a, unsigned long long b) { return __udivmoddi4(a, b, nullptr); } +unsigned long long __umoddi3(unsigned long long a, unsigned long long b) { unsigned long long r; __udivmoddi4(a, b, &r); return r; } +} diff --git a/tools/word32-check.sh b/tools/word32-check.sh new file mode 100755 index 0000000..c19d59c --- /dev/null +++ b/tools/word32-check.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Builds this implementation standalone for i386 and runs tests/word32/runs.cpp on the machine that +# built it (an x86_64 kernel runs a static i386 program). Clause 5.5 of the specification: a +# thirty-two bit word, sixty-four bit positions and times. The full conformance suite runs for +# i686 where the C library and the C++ runtime of this family are built for it (openkal-platform). +# +# tools/word32-check.sh [LLVM_BIN] # default: the llvm payload mcpp installed +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +spec="${OPENKAL_INCLUDE:-}" +llvm="${1:-$(ls -d "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-llvm/*/bin 2>/dev/null | sort -V | tail -1)}" +[ -x "$llvm/clang++" ] || { echo "no clang++ in '$llvm'" >&2; exit 2; } +if [ -z "$spec" ]; then + spec="$(ls -d "${MCPP_HOME:-$HOME/.mcpp}"/xpkgs/mcpplibs-x-openkal/*/openkal-*/include 2>/dev/null | sort -V | tail -1)" +fi +[ -d "$spec/openkal" ] || { echo "no openkal include directory (set OPENKAL_INCLUDE)" >&2; exit 2; } +out="$(mktemp -d)" +trap 'rm -rf "$out"' EXIT +flags=(--target=i686-linux-musl -march=i686 -std=c++23 -O1 -ffreestanding -fno-exceptions -fno-rtti + -nostdlibinc -fPIC -DOKL_STANDALONE -D__OPENKAL__ -I"$spec" -Werror) +for f in "$here"/src/*.cpp "$here"/tests/word32/runs.cpp; do + "$llvm/clang++" "${flags[@]}" -c "$f" -o "$out/$(basename "$f" .cpp).o" +done +"$llvm/ld.lld" -m elf_i386 -static -e _start -o "$out/runs" "$out"/*.o +( cd "$out" && ./runs ) From 5a0b2a511bf600e9c1845e1329c0b20fbf9c0212 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 13 Nov 2026 13:13:42 +0900 Subject: [PATCH 2/3] i386: siginfo_t and the signal set at the word's layout; SA_SIGINFO for the shared rt_sigreturn --- README.md | 9 ++++++++- src/observe.cpp | 5 ++++- src/process.cpp | 32 ++++++++++++++++++++++---------- src/sys.h | 17 ++++++++++++++++- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6ad32af..980fa9f 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,16 @@ caller uses on every architecture. The thread pointer is a segment set by `set_thread_area` and given to a new context by `clone`. A handle is a thirty-two bit word there: seventeen bits of descriptor and fifteen of generation; a program's handle carries its number and its kind, and an opened program's descriptor. +The kernel's records whose layout follows the word are read at the word's offsets: +`waitid`'s `siginfo_t` (its union at twelve, not sixteen), the signal set +(sixty-four bits, `rt_sigprocmask` refused four bytes), and a handler is installed +with `SA_SIGINFO` so the kernel builds the frame `rt_sigreturn` leaves. `tools/word32-check.sh` builds the implementation standalone for i386 and runs `tests/word32/runs.cpp`: a file past four gibibytes, a task with its own -thread-local storage, a sleep through the sixty-four bit clock. +thread-local storage, a sleep through the sixty-four bit clock. Measured locally +with the family built for `i686-linux-musl` (openkal-musl and openkal-llvm-runtime +for i386): the specification's conformance suite, `full,optional`, 408 held and +none did not, the five not observed being x86_64's five. Also: one record of `rt_sigaction` (the mask is the kernel's sixty-four bit set; it was declared three times with an `unsigned long` mask) and one return from a diff --git a/src/observe.cpp b/src/observe.cpp index f2c2317..cc5d816 100644 --- a/src/observe.cpp +++ b/src/observe.cpp @@ -282,7 +282,10 @@ const char* fault_name(int signo, int code) { } } -struct siginfo_head { int signo, errnum, code, pad; okl_uptr addr; }; +// signal, error, code, then the union at a pointer's alignment (okl::si_fields_at): a +// fault's address first --- offset 16 on a sixty-four bit word, 12 on i386. +struct siginfo_head { int signo, errnum, code; alignas(sizeof(void*)) okl_uptr addr; }; +static_assert(__builtin_offsetof(siginfo_head, addr) == okl::si_fields_at, "siginfo_t"); void report(int signo, void* info, void* uc) { const auto* si = static_cast(info); diff --git a/src/process.cpp b/src/process.cpp index 122cc1f..cb36e75 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -640,7 +640,7 @@ struct start_plan { const char* resolved; bool named, use_name, unit, bind; okl_long join, mine; - const unsigned long* mask; + const okl::ksigset* mask; const conveyance* carry; // 0.23: the records placed after the grants bool confine; // 0.23: held to the grants by Landlock const okl_u32* grant_rights; @@ -1058,7 +1058,7 @@ int kal_process_spawn(const kal_spawn* how, // program run in the image would run in this program's memory; so the image // sets every handler to the default first, and only then takes the mask the // caller had (start_image). - unsigned long every = ~0ul, caller_mask = 0; + okl::ksigset every = ~okl::ksigset{0}, caller_mask = 0; okl::sys(okl::nr_rt_sigprocmask, 2 /* SIG_SETMASK */, reinterpret_cast(&every), reinterpret_cast(&caller_mask), sizeof every); const start_plan plan{ how, &p, granted, b, w, in, ou, er, &args, &envs, &report, resolved, @@ -1318,7 +1318,13 @@ bool arm_one(int signo) { } act.handler = reinterpret_cast(&stop_handler); #if defined(__x86_64__) || defined(__i386__) - act.flags = sa_restorer_flag; + // On i386 the kernel builds the frame `rt_sigreturn' leaves only for a + // handler installed with SA_SIGINFO, and the legacy frame otherwise, whose + // return is another call: the shared restorer is `rt_sigreturn', so the + // flag is stated there (measured: a request to end never reached the + // program that had taken requests over). A handler of one `int' ignores the + // other two arguments under this convention. + act.flags = sa_restorer_flag | (sizeof(void*) == 4 ? 4u /* SA_SIGINFO */ : 0u); act.restorer = reinterpret_cast(&okl_signal_return); #endif return !okl::failed(okl::sys(okl::nr_rt_sigaction, signo, @@ -1516,7 +1522,7 @@ bool has_ended(kal_process h) { if (r == -okl::e_child) return true; if (okl::failed(r)) return false; int si_pid = 0; - okl::copy(reinterpret_cast(&si_pid), reinterpret_cast(info + 16), sizeof si_pid); + okl::copy(reinterpret_cast(&si_pid), reinterpret_cast(info + okl::si_pid_at), sizeof si_pid); return si_pid != 0; } @@ -1638,11 +1644,17 @@ int kal_process_end_as_requested(void) { if (signo == 0) signo = 15; okl::k_sigaction act {}; #if defined(__x86_64__) || defined(__i386__) - act.flags = sa_restorer_flag; + // On i386 the kernel builds the frame `rt_sigreturn' leaves only for a + // handler installed with SA_SIGINFO, and the legacy frame otherwise, whose + // return is another call: the shared restorer is `rt_sigreturn', so the + // flag is stated there (measured: a request to end never reached the + // program that had taken requests over). A handler of one `int' ignores the + // other two arguments under this convention. + act.flags = sa_restorer_flag | (sizeof(void*) == 4 ? 4u /* SA_SIGINFO */ : 0u); act.restorer = reinterpret_cast(&okl_signal_return); #endif okl::sys(okl::nr_rt_sigaction, signo, reinterpret_cast(&act), 0, sizeof act.mask); - unsigned long set = 1ul << (signo - 1); + okl::ksigset set = okl::ksigset{1} << (signo - 1); okl::sys(okl::nr_rt_sigprocmask, 1 /* SIG_UNBLOCK */, reinterpret_cast(&set), 0, sizeof set); okl::sys(okl::nr_kill, okl::sys(okl::nr_getpid), signo); for (int i = 0; i < 100; ++i) { @@ -1770,7 +1782,7 @@ bool ended_usage(okl_long pid, k_rusage& ru) { reinterpret_cast(&ru)); if (okl::failed(r)) return false; int si_pid = 0; - okl::copy(&si_pid, info + 16, sizeof si_pid); + okl::copy(&si_pid, info + okl::si_pid_at, sizeof si_pid); return si_pid != 0; } @@ -1944,9 +1956,9 @@ int child_outcome(okl_long pid, bool block, int* status, int* terminated, bool& ended = false; if (okl::failed(r)) return okl::translate(r); int si_pid = 0, si_code = 0, si_status = 0; - okl::copy(&si_code, info + 8, sizeof si_code); - okl::copy(&si_pid, info + 16, sizeof si_pid); - okl::copy(&si_status, info + 24, sizeof si_status); + okl::copy(&si_code, info + okl::si_code_at, sizeof si_code); + okl::copy(&si_pid, info + okl::si_pid_at, sizeof si_pid); + okl::copy(&si_status, info + okl::si_status_at, sizeof si_status); if (si_pid == 0) return kal_ok; // not yet ended = true; if (si_code == 1 /* CLD_EXITED */) { diff --git a/src/sys.h b/src/sys.h index 346f43b..ede7a63 100644 --- a/src/sys.h +++ b/src/sys.h @@ -628,7 +628,22 @@ struct ktimespec { okl_i64 sec; okl_i64 nsec; }; // supports --- two words on i386, where an `unsigned long' mask was half of it // and the size the call is told (`sizeof mask') was refused. One declaration for // every unit that installs a handler. -struct k_sigaction { void* handler; okl_ulong flags; void* restorer; okl_u64 mask; }; +// The kernel's signal set: sixty-four bits on every architecture this +// implementation supports, two words on i386 --- where an `unsigned long' set +// was half of it and the size a call is told was refused (measured: +// `rt_sigprocmask(..., 4) = -EINVAL', and no program could be started). +using ksigset = okl_u64; + +// THE KERNEL'S `siginfo_t' AS `waitid' WRITES IT: three ints (signal, error, +// code), then the union, aligned to a pointer --- at sixteen on a sixty-four bit +// word and twelve on i386, where reading at sixteen took the user for the +// identifier and an exit status of 33 was reported as 0 (measured). For a child: +// the identifier, the user, then the status. +constexpr okl_uptr si_code_at = 8; +constexpr okl_uptr si_fields_at = sizeof(void*) == 8 ? 16 : 12; +constexpr okl_uptr si_pid_at = si_fields_at; +constexpr okl_uptr si_status_at = si_fields_at + 8; +struct k_sigaction { void* handler; okl_ulong flags; void* restorer; ksigset mask; }; constexpr okl_ulong sa_restorer_flag = 0x04000000u; // SA_RESTORER } // namespace okl From 777ca7fc27b2b19a1f5c02060061501c35a0e9cd Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 13 Nov 2026 13:30:42 +0900 Subject: [PATCH 3/3] the return from a handler is reached through okl::signal_return, so no unit names another's symbol outside the namespace --- src/observe.cpp | 4 ++++ src/process.cpp | 4 ++-- src/sys.h | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/observe.cpp b/src/observe.cpp index cc5d816..936a0f4 100644 --- a/src/observe.cpp +++ b/src/observe.cpp @@ -27,6 +27,10 @@ __asm__(".text\n.globl okl_signal_return\n.type okl_signal_return,@function\nokl __asm__(".text\n.globl okl_signal_return\n.type okl_signal_return,@function\nokl_signal_return:\n" " mov x8, #139\n svc #0\n"); #endif +#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) +extern "C" void okl_signal_return(); +void* okl::signal_return() { return reinterpret_cast(&okl_signal_return); } +#endif extern "C" void okl_fault_handler(int signo, void* info, void* uc); extern "C" void okl_sample_handler(int signo, void* info, void* uc); diff --git a/src/process.cpp b/src/process.cpp index cb36e75..bcc9b82 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -1325,7 +1325,7 @@ bool arm_one(int signo) { // program that had taken requests over). A handler of one `int' ignores the // other two arguments under this convention. act.flags = sa_restorer_flag | (sizeof(void*) == 4 ? 4u /* SA_SIGINFO */ : 0u); - act.restorer = reinterpret_cast(&okl_signal_return); + act.restorer = okl::signal_return(); #endif return !okl::failed(okl::sys(okl::nr_rt_sigaction, signo, reinterpret_cast(&act), 0, sizeof act.mask)); @@ -1651,7 +1651,7 @@ int kal_process_end_as_requested(void) { // program that had taken requests over). A handler of one `int' ignores the // other two arguments under this convention. act.flags = sa_restorer_flag | (sizeof(void*) == 4 ? 4u /* SA_SIGINFO */ : 0u); - act.restorer = reinterpret_cast(&okl_signal_return); + act.restorer = okl::signal_return(); #endif okl::sys(okl::nr_rt_sigaction, signo, reinterpret_cast(&act), 0, sizeof act.mask); okl::ksigset set = okl::ksigset{1} << (signo - 1); diff --git a/src/sys.h b/src/sys.h index ede7a63..412723a 100644 --- a/src/sys.h +++ b/src/sys.h @@ -646,11 +646,11 @@ constexpr okl_uptr si_status_at = si_fields_at + 8; struct k_sigaction { void* handler; okl_ulong flags; void* restorer; ksigset mask; }; constexpr okl_ulong sa_restorer_flag = 0x04000000u; // SA_RESTORER -} // namespace okl // The return from a handler this implementation installs, which a handler -// installed without a C library must name on x86_64 and i386 (observe.cpp). -extern "C" void okl_signal_return(); -namespace okl { +// installed without a C library must name on x86_64 and i386: one sequence +// (observe.cpp), reached through this function so that no unit names another +// unit's symbol outside this namespace (the independence check). +void* signal_return(); // --- the calls whose arguments are not the same on every architecture ---------- //