diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..272712e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +name: CI + +# What this workflow asserts about openkal-dos (technical experiment only). +# +# stub stub/stub.S assembles to the bytes the seal writes (tools/src/stub_bytes.h is what +# stub/build.sh generates from it, byte for byte). +# okdos the seal builds on each system of a building machine and refuses a file that is no +# ELF32 i386 executable. +# programs the examples and tests, built for i686-dos-musl through openkal-platform with this +# repository's openkal-dos, run under DOSBox Staging with CWSDPMI beside them (dirun), +# unoptimised and optimised: hello's arguments, exception, environment and status 7; +# files' FAT operations and status 9; a page fault's status 139. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +env: + XLINGS_VERSION: v2026.10.26.1 + XLINGS_NON_INTERACTIVE: '1' + MCPP_VERSION: 2026.11.13.1 + LLVM: llvm@23.1.3 + +jobs: + stub: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - name: Install xlings and mcpp + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - name: mcpp and LLVM + run: | + xlings update + xlings install "mcpp@$MCPP_VERSION" -y -g + mcpp self config --mirror GLOBAL + mcpp toolchain install llvm 23.1.3 + - name: The stub's bytes are the committed ones + run: | + LLVM="$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm/23.1.3/bin" sh stub/build.sh + git diff --exit-code tools/src/stub_bytes.h + + okdos: + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - name: Install xlings (Linux, macOS) + if: runner.os != 'Windows' + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - name: Install xlings (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + irm https://d2learn.org/xlings-install.ps1.txt | iex + "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: mcpp + run: | + xlings update + xlings install "mcpp@$MCPP_VERSION" -y -g + mcpp self config --mirror GLOBAL + - name: okdos builds and refuses what is no executable of the row + run: | + (cd tools && mcpp build) + okdos=$(find tools/target -path '*bin*' \( -name okdos -o -name okdos.exe \) -type f | head -1) + test -n "$okdos" + printf 'not an executable' > plain.bin + if "$okdos" seal plain.bin; then echo "::error::okdos sealed a file that is no ELF32 executable"; exit 1; fi + if "$okdos" check plain.bin; then echo "::error::okdos check accepted a plain file"; exit 1; fi + + programs: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Install xlings and mcpp + run: | + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - name: mcpp and LLVM + run: | + xlings update + xlings install "mcpp@$MCPP_VERSION" -y -g + mcpp self config --mirror GLOBAL + mcpp toolchain install llvm 23.1.3 + # Each program is built with this repository's openkal-dos in place of the indexed one. + - name: This repository's openkal-dos + run: | + for p in examples/hello examples/files tests/fault; do + printf '\n[target.i686-dos-musl.dependencies]\nmcpplibs.openkal-dos = { path = "%s", override = true }\n' \ + "$GITHUB_WORKSPACE" >> "$p/mcpp.toml" + done + - name: hello (status 7), unoptimised and optimised + working-directory: examples/hello + run: | + for profile in "" --release; do + set +e; mcpp run --target i686-dos-musl $profile -- zeta alpha > out.txt; rc=$?; set -e + cat out.txt + [ "$rc" = 7 ] || { echo "::error::hello ($profile) ended with $rc, not 7"; exit 1; } + tr -d '\r' < out.txt | grep -qx ' alpha' + tr -d '\r' < out.txt | grep -qx 'caught: an exception crossed a frame' + tr -d '\r' < out.txt | grep -q '^PATH=.' + done + - name: files (status 9) + working-directory: examples/files + run: | + set +e; mcpp run --target i686-dos-musl > out.txt; rc=$?; set -e + cat out.txt + [ "$rc" = 9 ] || { echo "::error::files ended with $rc, not 9"; exit 1; } + tr -d '\r' < out.txt | grep -qx 'read 3: appended' + tr -d '\r' < out.txt | grep -qx 'resized to 40' + tr -d '\r' < out.txt | grep -qx 'removed: true' + - name: a page fault (status 139) + working-directory: tests/fault + run: | + set +e; mcpp run --target i686-dos-musl > out.txt; rc=$?; set -e + cat out.txt + [ "$rc" = 139 ] || { echo "::error::the faulting program ended with $rc, not 139"; exit 1; } + tr -d '\r' < out.txt | grep -qx 'before the fault' + ! tr -d '\r' < out.txt | grep -q 'after the fault' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a8e8e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +*.okdos diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a30fe7 --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# openkal-dos + +An implementation of [openkal][kal] on DOS, through a DPMI host. + +**TECHNICAL EXPERIMENT ONLY.** This package exists to test that openkal's +interfaces, its C library and its C++ run-time reach a system as distant from +the others as DOS. It carries no stability guarantee and no support; its +interfaces, its hand-over and its stub may change in any release. + +A program names the target and nothing else. The row `i686-dos-musl`, its +compile and link conventions, its seal and its runner are the openkal +platform's ([openkal-platform][plat] 0.28.0): the same C library and C++ +run-time as on every other system, openkal-musl and openkal-llvm-runtime, with +this implementation beneath them, so a DOS program is written in C++23 with +`import std`: + +```toml +[target.i686-dos-musl] +platform = "openkal-platform@0.28.0" +``` + +```sh +mcpp build --target i686-dos-musl # hello.exe: the stub and an ELF32 i386 executable +mcpp run --target i686-dos-musl # run by dirun under DOSBox Staging, with CWSDPMI beside it +``` + +`examples/hello` sorts its arguments, throws and catches an exception across a +frame, reads `PATH` and returns 7. + +## The program + +A DOS program of this arrangement is two parts in one file: + +| part | what it is | +|---|---| +| the stub (`stub/stub.S`, 2544 bytes) | an MZ program in 16-bit code | +| the executable | ELF32, i386, linked at its address (`ET_EXEC`, base 0x1000), at most 16 program headers | + +The stub finds a 32-bit DPMI host (int 2Fh, 1687h), and where there is none it +runs `CWSDPMI.EXE` from the program's own directory and asks again. It enters +protected mode as a 32-bit client, allocates one block for the executable's +loadable segments, its first stack (64 KiB) and the hand-over record, makes a +code and a data descriptor whose base is the block and whose limit is 4 GiB, +reads each loadable segment from its own file through a 16 KiB transfer buffer +in conventional memory, and enters the executable at `e_entry` with EBX naming +the record. An address of the program is therefore an offset from the block; +any linear address L is reached at L minus the block's base. + +`okdos seal ` (package `openkal-dos-tools`, the platform's tool) writes the +stub before an executable, in place; `okdos check ` reports whether a file +is such a program. The stub applies no relocation, which is why the executable +is linked at its address. + +## The hand-over + +The record (`src/dos.h`, version 1, 168 bytes) states the block's linear base, +its size and DPMI handle, the segment prefix's selector and segment, the +environment's segment, the transfer buffer's segment, size and selector, the +code and data selectors, the program's file (left open), and the name DOS +started the program by. `okdos_entry` (`src/start.S`) aligns the stack and calls +`okdos_start`, which refuses a record of another version with status 127. + +## Two arrangements + +| | entry | initialisers | `main` | +|---|---|---|---| +| beneath a C library (`standalone`) | `okdos_entry` records the hand-over and the arguments | the C library's, through `__libc_start_main` | called by the C library | +| alone (no C library) | `okdos_entry` | `__init_array`, run here | called here | + +openkal-musl selects `standalone` on `cfg(os = "dos")`. A program that wants no +C library states `c-abi = false` for the row and calls openkal directly. + +## What a program receives + +| openkal | DOS | +|---|---| +| `stream` | DOS handles: 0, 1 and 2, and a file's (int 21h, 3Fh and 40h) through the transfer buffer; a handle's properties from IOCTL 4400h | +| `memory` | DPMI blocks (0501h, 0502h), 4 KiB granular; the free memory from 0500h | +| `env` | the arguments from the command tail (offset 80h of the segment prefix), the variables from the environment block, the program's name, the system name `dos` | +| `time` | the BIOS tick count at 0040:006C (54.925494 ms, made monotonic across midnight); the wall clock from int 21h 2Ah and 2Ch, read as UTC; a sleep by int 15h, 86h | +| `fs` | the current directory and each volume as supplied directories (`C:\WORK`, `C:`, `Z:`), a directory held as its name; open, create (5Bh refuses a name that exists), truncate, seek, transfers at an offset, find first and next for enquiry and listing, make and remove a directory, remove, rename, a file's time (5700h, 5701h) and a volume's capacity (36h) | +| `terminal` | the console's mode by bit 5 of its device word (cooked: line assembly and echo; raw: every byte as typed), ANSI.SYS's sequences where it answers int 2Fh 1A00h, the size from the BIOS data area | +| `exec` | a DPMI block, zeroed: the code descriptor spans 4 GiB, so every block is executable through it; publishing makes no request | +| `abort` | int 21h, 4Ch with the status; an abort writes its message to standard error and ends with 134 | +| (faults) | a processor fault (division by zero, an instruction the processor lacks, the stack, general protection, a page) is reported on standard error with the instruction's address, and the program ends with 128 and the corresponding signal's number (136, 132, 139), through DPMI's exception handlers (int 31h, 0203h); CWSDPMI's own report is a register dump on the screen and status 1 (measured) | + +DOS is reached through DPMI's real-mode simulation (int 31h, 0300h), with every +buffer in the transfer buffer. + +A volume is FAT's: names are eight characters, a dot and three, at most 64 +bytes with the volume, and no case is distinguished; a node has a modification +time to two seconds and no identity; no link is made; DOS's rename refuses a +name that exists, which is the rename that does not replace +(`KAL_FS_PROP_RENAME_NO_REPLACE`), and the rename that replaces removes the name +first and is not atomic. A name that holds DOS's `\` or a volume's `:` is +refused, as a name that ascends is. + +Absent (openkal clause 6.2): contexts, processes as openkal states them, the +network, and regions --- DOS cannot reserve an address apart from backing it: the +DPMI host CWSDPMI is DPMI 0.90 and refuses 0504h (measured), which is the case +`openkal/region.h` names for leaving the interface out. DOSBox Staging's console +accepts the request for raw mode and stays cooked (measured); +`kal_terminal_get_mode` reports what is in effect. + +An i386 ok image (`i686-openkal`) runs on DOS as on firmware: the platform's +runner `hosts` include `dos-i686`, so `mcpp pack --pkg-format single-file +--systems dos-i686` builds openkal-run's core runner as a program of this row, +with the image in its `.oke` section, and the runner places the image through +`exec`. Measured under DOSBox Staging: an image built for `i686-openkal` +(C++23, `import std`, an exception across a frame) sorted its arguments, read +`PATH` and returned 7. + +`examples/files` writes, appends and reads a file, renames it into a directory, +lists, resizes (the extension reads as zero) and removes them, and returns 9 +(measured under DOSBox Staging). + +## The row's conventions + +The row compiles for `i386-unknown-none-elf` at `-march=i486`: DOSBox Staging +0.83 executes RDTSC and not CMPXCHG8B, under every `cputype` (measured), so a +sixty-four bit atomic is compiler-rt's call (`atomic.c`) rather than that +instruction. The row also uses emulated thread-local storage +(there is no thread register the toolchain can address: openkal-llvm-runtime +builds compiler-rt's `emutls.c` for the row) and unwind tables for every unit, +C included (clang's default for a triple with no system is none, and the +unwinder's own frames need them), and a stack aligned to sixteen bytes at a +call, as an i386 ok image's is. The link is `ld.lld -static +--image-base=0x1000 -z norelro --gc-sections --eh-frame-hdr`, entered at +`okdos_entry`. + +## Checks + +CI (`.github/workflows/ci.yml`) asserts that `stub/stub.S` assembles to the +committed `tools/src/stub_bytes.h`; that `okdos` builds on Linux, macOS and +Windows and refuses a file that is no ELF32 i386 executable; and that the +examples and `tests/fault`, built for `i686-dos-musl` with this repository's +openkal-dos and run under DOSBox Staging, unoptimised and optimised, print what +they state and end with 7, 9 and 139. + +## CWSDPMI + +CWSDPMI is Copyright (C) Charles W. Sandmann. It is not part of this package +and this package contains none of it: the stub runs `CWSDPMI.EXE` when it finds +it beside the program. CWSDPMI is distributed under its own license +(`cwsdpmi.doc` in csdpmi7b.zip, www.delorie.com/pub/djgpp/current/v2misc/), +which permits free redistribution of the unmodified binary with its notice. The +xim package `dosbox-staging`, which the row's runner uses, carries it under +those terms. A program distributed with `CWSDPMI.EXE` beside it carries that +notice too. + +## License + +Apache-2.0 (LICENSE). + +[kal]: https://github.com/mcpplibs/openkal +[plat]: https://github.com/mcpplibs/openkal-platform diff --git a/examples/files/mcpp.toml b/examples/files/mcpp.toml new file mode 100644 index 0000000..6857402 --- /dev/null +++ b/examples/files/mcpp.toml @@ -0,0 +1,13 @@ +# Files on DOS through openkal.fs (openkal-dos; technical experiment only): writes, appends and +# reads a file, renames it into a directory, lists, resizes and removes them, and returns 9. +# mcpp run --target i686-dos-musl +[package] +name = "files" +version = "0.1.0" + +[targets.files] +kind = "bin" +main = "src/main.cpp" + +[target.i686-dos-musl] +platform = "openkal-platform@0.28.0" diff --git a/examples/files/src/main.cpp b/examples/files/src/main.cpp new file mode 100644 index 0000000..ed71194 --- /dev/null +++ b/examples/files/src/main.cpp @@ -0,0 +1,23 @@ +import std; +namespace fs = std::filesystem; + +int main() { + std::println("cwd {}", fs::current_path().string()); + { std::ofstream("NOTE.TXT") << "first line\nsecond line\n"; } + { std::ofstream("NOTE.TXT", std::ios::app) << "appended\n"; } + std::ifstream in("NOTE.TXT"); + int n = 0; + for (std::string line; std::getline(in, line);) std::println("read {}: {}", ++n, line); + in.close(); + std::println("size {}", fs::file_size("NOTE.TXT")); + fs::create_directory("SUB"); + fs::rename("NOTE.TXT", "SUB/MOVED.TXT"); + std::vector names; + for (auto& e : fs::directory_iterator("SUB")) names.push_back(e.path().filename().string()); + std::println("SUB holds {}", names); + fs::resize_file("SUB/MOVED.TXT", 40); + std::println("resized to {}", fs::file_size("SUB/MOVED.TXT")); + fs::remove_all("SUB"); + std::println("removed: {}", !fs::exists("SUB")); + return 9; +} diff --git a/examples/hello/mcpp.toml b/examples/hello/mcpp.toml new file mode 100644 index 0000000..18b5c60 --- /dev/null +++ b/examples/hello/mcpp.toml @@ -0,0 +1,13 @@ +# A DOS program in C++23 over openkal-dos (technical experiment only): sorts its arguments, throws +# and catches an exception across a frame, reads PATH and returns 7. +# mcpp run --target i686-dos-musl -- b a +[package] +name = "hello" +version = "0.1.0" + +[targets.hello] +kind = "bin" +main = "src/main.cpp" + +[target.i686-dos-musl] +platform = "openkal-platform@0.28.0" diff --git a/examples/hello/src/main.cpp b/examples/hello/src/main.cpp new file mode 100644 index 0000000..3a6d616 --- /dev/null +++ b/examples/hello/src/main.cpp @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include +int main(int argc, char** argv) { + std::vector v(argv + 1, argv + argc); + std::sort(v.begin(), v.end()); + std::printf("hello from openkal-dos: musl and libc++ over DPMI, %d args\n", argc); + for (auto& s : v) std::printf(" %s\n", s.c_str()); + try { throw std::runtime_error("an exception crossed a frame"); } + catch (const std::exception& e) { std::printf("caught: %s\n", e.what()); } + const char* p = std::getenv("PATH"); + std::printf("PATH=%s\n", p ? p : "(none)"); + return 7; +} diff --git a/mcpp.toml b/mcpp.toml new file mode 100644 index 0000000..12deaa6 --- /dev/null +++ b/mcpp.toml @@ -0,0 +1,39 @@ +# openkal on DOS, through DPMI. TECHNICAL EXPERIMENT ONLY (README): no stability, no support. +# +# A DOS program of this arrangement is a stub (stub/stub.S, an MZ program) followed by an ELF32 +# i386 executable: the stub enters 32-bit protected mode through a DPMI host --- CWSDPMI where +# the machine has none --- and loads the executable into one block of memory; this package +# receives it there (src/start.S) and answers openkal's interfaces through DOS (int 21h), which +# DPMI's real-mode simulation (int 31h, 0300h) reaches with buffers in conventional memory. +[package] +namespace = "mcpplibs" +name = "openkal-dos" +version = "0.1.0" +description = "An implementation of openkal on DOS through DPMI (technical experiment only)" +license = "Apache-2.0" +provides = ["mcpp:kernel-abi=openkal"] +authors = ["mcpplibs"] +repo = "https://github.com/mcpplibs/openkal-dos" + +# The interfaces DOS has a counterpart for. Absent (openkal clause 6.2): contexts, processes as +# openkal states them, the network, and regions --- the DPMI host reserves no address apart from +# backing it (CWSDPMI is DPMI 0.90 and refuses 0504h, measured). +[kernel-abi] +provides-interfaces = ["openkal.abort", "openkal.env", "openkal.exec", "openkal.fs", "openkal.memory", + "openkal.stream", "openkal.terminal", "openkal.time"] + +[dependencies] +openkal = "^0.25.0" + +# With no C library this package runs the initialisers and calls `main'; `standalone' is the +# arrangement beneath a C library written above openkal (openkal-musl names it so on +# `cfg(os = "dos")'), whose `__libc_start_main' receives the program. +[features] +standalone = { defines = ["OKDOS_STANDALONE"] } + +[build] +sources = ["src/*.cpp", "src/*.S"] +flags = [ + { glob = "src/**", cxxflags = ["-fno-exceptions", "-fno-rtti", "-fno-stack-protector", + "-fno-asynchronous-unwind-tables"] }, +] diff --git a/src/dos.h b/src/dos.h new file mode 100644 index 0000000..bb71311 --- /dev/null +++ b/src/dos.h @@ -0,0 +1,79 @@ +// What the stub hands over, and the two ways this implementation reaches DOS. +// +// THE HAND-OVER (stub/stub.S writes it, version 1). The program is entered in 32-bit protected +// mode with CS, DS, ES and SS descriptors whose base is the block the stub allocated and whose +// limit spans 4 GiB, so an address of the program is an offset from the block's base and any +// linear address L is reached at L - base. EBX holds the record's offset. +#pragma once + +namespace okdos { + +using u8 = unsigned char; using u16 = unsigned short; using u32 = unsigned int; + +struct handoff { + u32 magic; // "OKDS" + u32 version; // 1 + u32 base; // the block's linear address + u32 size; // its size + u32 block; // its DPMI handle + u16 psp_selector; + u16 psp_segment; + u16 env_segment; // the environment block, in conventional memory + u16 tb_segment; // the transfer buffer: conventional memory DOS reads and writes + u32 tb_size; + u16 code_selector, data_selector, tb_selector; + u16 exe_handle; // the program's own file, opened by the stub + char name[128]; // the program's name as DOS gave it +}; +static_assert(sizeof(handoff) == 168); +constexpr u32 kMagic = 0x53444b4fu; + +// DPMI's real-mode call structure (int 31h, 0300h). +struct rm_regs { + u32 edi, esi, ebp, reserved, ebx, edx, ecx, eax; + u16 flags, es, ds, fs, gs, ip, cs, sp, ss; +} __attribute__((packed)); +static_assert(sizeof(rm_regs) == 50); + +struct state { + const handoff* h = nullptr; + int argc = 0; + char** argv = nullptr; + u32 ticks_base = 0, ticks_last = 0, ticks_days = 0; // the BIOS tick count, made monotonic +}; +state& self(); + +// A real-mode interrupt with these registers (DPMI 0300h); its carry is the result's. +// Interrupts are enabled during it: DOS may wait on them. +bool real_int(u8 vector, rm_regs& r); +// int 21h with registers only, no buffers; false on carry. +bool dos(rm_regs& r); + +// A linear address of conventional memory, as this program reaches it. +inline u8* low(u32 linear) { return reinterpret_cast(static_cast(linear - self().h->base)); } +inline u8* transfer() { return low(static_cast(self().h->tb_segment) * 16u); } +inline u32 transfer_size() { return self().h->tb_size; } + +void copy(void* to, const void* from, u32 n); + +// A handle's transfers through the transfer buffer (int 21h, 3Fh and 40h): a write of the whole +// buffer a piece at a time, a read of at most the transfer buffer's size. +long write_handle(u16 handle, const u8* p, unsigned long n); +long read_handle(u16 handle, u8* p, unsigned long n); +// Whether a file's handle was opened for appending (fs.cpp): each write is placed at the end. +bool append_handle(u16 handle); +// The handlers of the processor's faults (fault.S, fault.cpp): a fault is reported on standard +// error and ends the program with 128 and the signal's number. +void install_faults(); + +// Days from 1970-01-01 to a civil date (H. Hinnant's algorithm). +inline long long days_from_civil(long long y, unsigned m, unsigned d) { + y -= m <= 2; + const long long era = (y >= 0 ? y : y - 399) / 400; + const unsigned yoe = static_cast(y - era * 400); + const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + static_cast(doe) - 719468; +} + +} // namespace okdos diff --git a/src/dpmi.cpp b/src/dpmi.cpp new file mode 100644 index 0000000..4ceca51 --- /dev/null +++ b/src/dpmi.cpp @@ -0,0 +1,50 @@ +// DOS through DPMI: a real-mode interrupt simulated by the host (int 31h, 0300h), with buffers in +// the transfer buffer the stub allocated in conventional memory. +#include "dos.h" + +namespace okdos { + +state& self() { + static state s{}; + return s; +} + +void copy(void* to, const void* from, u32 n) { + auto* d = static_cast(to); + const auto* s = static_cast(from); + while (n--) *d++ = *s++; +} + +bool real_int(u8 vector, rm_regs& r) { + r.flags = 0x0202; // interrupts enabled + r.ss = 0; // the host's stack + r.sp = 0; + int carry; + __asm__ volatile("int $0x31\n\tsbb %0, %0" + : "=r"(carry) + : "a"(0x0300), "b"(static_cast(vector)), "c"(0), "D"(&r) + : "memory", "cc"); + return carry == 0 && (r.flags & 1) == 0; +} + +bool dos(rm_regs& r) { + if (r.ds == 0) r.ds = self().h->tb_segment; + if (r.es == 0) r.es = self().h->tb_segment; + return real_int(0x21, r); +} + +} // namespace okdos + +// A compiler zeroes and copies a structure through these two whether or not a C library is +// linked; without one they are these, and a C library's own replace them (weak). +extern "C" { +__attribute__((weak)) void* memset(void* p, int c, unsigned long n) { + auto* d = static_cast(p); + while (n--) *d++ = static_cast(c); + return p; +} +__attribute__((weak)) void* memcpy(void* to, const void* from, unsigned long n) { + okdos::copy(to, from, static_cast(n)); + return to; +} +} diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 0000000..41aea21 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,81 @@ +// openkal.env on DOS: the arguments are the command tail of the program's segment prefix (PSP), +// split at blanks; position zero is the name DOS started the program by; the variables are the +// environment block's NAME=VALUE strings. +#include + +#include "dos.h" + +namespace { + +using namespace okdos; + +kal_uintptr length(const char* s) { kal_uintptr n = 0; while (s[n]) ++n; return n; } + +kal_intptr give(const char* s, kal_uintptr n, char* out, kal_uintptr cap) { + if (out && cap) { + const kal_uintptr k = n < cap ? n : cap; + for (kal_uintptr i = 0; i < k; ++i) out[i] = s[i]; + } + return static_cast(n); +} + +const char* environment() { return reinterpret_cast(low(static_cast(self().h->env_segment) * 16u)); } + +// The I-th NAME=VALUE string, or null. +const char* variable(kal_uintptr index) { + const char* p = environment(); + for (kal_uintptr i = 0; *p; ++i) { + if (i == index) return p; + p += length(p) + 1; + } + return nullptr; +} + +} // namespace + +extern "C" { + +kal_uintptr kal_env_arg_count(void) { return static_cast(self().argc); } + +kal_intptr kal_env_arg(kal_uintptr index, char* out, kal_uintptr cap) { + const state& s = self(); + if (index >= static_cast(s.argc)) return -kal_err_not_found; + return give(s.argv[index], length(s.argv[index]), out, cap); +} + +kal_uintptr kal_env_var_count(void) { + kal_uintptr n = 0; + while (variable(n)) ++n; + return n; +} + +kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) { + const char* v = variable(index); + if (!v) return -kal_err_not_found; + kal_uintptr n = 0; + while (v[n] && v[n] != '=') ++n; + return give(v, n, out, cap); +} + +kal_intptr kal_env_var(const char* name, kal_uintptr name_len, char* out, kal_uintptr cap) { + for (kal_uintptr i = 0;; ++i) { + const char* v = variable(i); + if (!v) return -kal_err_not_found; + kal_uintptr k = 0; + while (k < name_len && v[k] && v[k] == name[k]) ++k; + if (k == name_len && v[k] == '=') return give(v + k + 1, length(v + k + 1), out, cap); + } +} + +kal_intptr kal_env_image(char* out, kal_uintptr cap) { + const char* n = self().h->name; + return give(n, length(n), out, cap); +} + +kal_intptr kal_env_loader(char*, kal_uintptr) { return -kal_err_not_found; } + +kal_intptr kal_env_system(char* out, kal_uintptr cap) { return give("dos", 3, out, cap); } + +int kal_env_resources(void) { return KAL_RESOURCES_ECONOMY; } + +} // extern "C" diff --git a/src/exec.cpp b/src/exec.cpp new file mode 100644 index 0000000..df13b3e --- /dev/null +++ b/src/exec.cpp @@ -0,0 +1,68 @@ +// openkal.exec on DOS through DPMI: memory a program may execute, which a loader of images needs +// (the ok-image runner, carried as a DOS program, places an image's code here). +// +// The stub's code descriptor has the block's base and a limit of 4 GiB, so every address of the +// program, a DPMI block allocated since among them, is executable through it: a region is a DPMI +// block, zeroed, at the page granularity. DPMI 0.9 has no protection a client may change on its +// own pages, so publishing and sealing a part make no request; they are statements the caller +// makes, as the interface allows an environment without such a protection. +// +// A file is not mapped (DOS has no mapping): kal_exec_map_part answers kal_err_not_supported, and +// the properties claim neither a file's segments nor a placement. +#include +#include +#include + +namespace { + +constexpr kal_uintptr kPage = 4096; + +kal_uintptr pages(kal_uintptr size) { return (size + kPage - 1) / kPage * kPage; } + +bool part(void* region, kal_uintptr offset, kal_uintptr size) { + return region != nullptr && size != 0 && offset % kPage == 0 && size % kPage == 0; +} + +} // namespace + +extern "C" { + +void* kal_exec_alloc(kal_uintptr size) { + if (size == 0) return nullptr; + const kal_uintptr n = pages(size); + if (n < size) return nullptr; + auto* p = static_cast(kal_alloc(n, kPage)); + if (p) + for (kal_uintptr i = 0; i < n; ++i) p[i] = 0; + return p; +} + +// Placement at a chosen address is not offered: DPMI 0.9 gives a block where the host places it. +void* kal_exec_alloc_at(void*, kal_uintptr) { return nullptr; } + +int kal_exec_publish(void* p, kal_uintptr size) { + return p != nullptr && size != 0 ? kal_ok : kal_err_invalid; +} + +int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) { + return part(region, offset, size) ? kal_ok : kal_err_invalid; +} + +int kal_exec_seal_part(void* region, kal_uintptr offset, kal_uintptr size, kal_uintptr flags) { + if (flags & ~KAL_EXEC_SEAL_FOREVER) return kal_err_invalid; + return part(region, offset, size) ? kal_ok : kal_err_invalid; +} + +kal_uintptr kal_exec_granularity(void) { return kPage; } + +int kal_exec_map_part(void*, kal_uintptr, kal_uintptr, struct kal_file, kal_u64, kal_uintptr) { + return kal_err_not_supported; +} + +void kal_exec_free(void* p, kal_uintptr size) { + if (p) kal_free(p, pages(size), kPage); +} + +kal_uintptr kal_exec_props(void) { return KAL_EXEC_PROP_AVAILABLE; } + +} // extern "C" diff --git a/src/fault.S b/src/fault.S new file mode 100644 index 0000000..71170ea --- /dev/null +++ b/src/fault.S @@ -0,0 +1,65 @@ +// A fault of the program, reported (openkal-dos 0.1.0). The DPMI host calls a processor-exception +// handler (int 31h, 0203h) on its own stack, with this frame (DPMI 0.9, a 32-bit client): +// +// [esp+00h] the host's return EIP [esp+04h] its CS [esp+08h] the error code +// [esp+0Ch] the faulting EIP [esp+10h] its CS [esp+14h] EFLAGS +// [esp+18h] the faulting ESP [esp+1Ch] its SS +// +// The handler records the vector, the error code and the faulting EIP, and returns to the host with +// the frame changed so that the program resumes at okdos_fault_entry on a stack of its own: the +// report runs as ordinary code, whatever the fault did to the stack it had. DS at the fault is not +// trusted; the data selector is read through CS, whose descriptor has the same base and is +// readable. + .set OKDOS_FAULT_STACK, 16384 + .text + + .macro okdos_vector n + .globl okdos_fault_\n + .type okdos_fault_\n, @function +okdos_fault_\n: + pushl $\n + jmp okdos_fault_common + .endm + + okdos_vector 0 + okdos_vector 6 + okdos_vector 12 + okdos_vector 13 + okdos_vector 14 + +okdos_fault_common: + pushl %eax + pushl %ds + movw %cs:okdos_fault_ds, %ax + movw %ax, %ds + // [esp] DS, +04h EAX, +08h the vector, +0Ch the host's EIP, +10h its CS, +14h the error code, + // +18h the faulting EIP, +1Ch its CS, +20h EFLAGS, +24h ESP, +28h SS + movl 0x08(%esp), %eax + movl %eax, okdos_fault_vector + movl 0x14(%esp), %eax + movl %eax, okdos_fault_error + movl 0x18(%esp), %eax + movl %eax, okdos_fault_eip + movl $okdos_fault_entry, 0x18(%esp) + movl $okdos_fault_stack + OKDOS_FAULT_STACK, 0x24(%esp) + movzwl okdos_fault_ds, %eax + movl %eax, 0x28(%esp) + popl %ds + popl %eax + addl $4, %esp + lret + +okdos_fault_entry: + movw %cs:okdos_fault_ds, %ax + movw %ax, %ds + movw %ax, %es + andl $-16, %esp + call okdos_fault + ud2 + + .bss + .p2align 4 +okdos_fault_stack: + .space OKDOS_FAULT_STACK + + .section .note.GNU-stack, "", @progbits diff --git a/src/fault.cpp b/src/fault.cpp new file mode 100644 index 0000000..864d600 --- /dev/null +++ b/src/fault.cpp @@ -0,0 +1,89 @@ +// A fault of the program, reported as the other implementations of openkal report one: a sentence +// on standard error naming the fault and the instruction's address, and the status a process ended +// by the corresponding signal reports on the systems that have signals (128 and the signal's +// number). Without it, CWSDPMI writes a register dump to the screen and ends the program with +// status 1, which a program's output and status do not distinguish from `exit(1)` (measured). +// +// fault.S holds the handlers the DPMI host calls (int 31h, 0203h); they resume the program at +// okdos_fault on a stack of its own. +#include "dos.h" + +extern "C" { +okdos::u16 okdos_fault_ds; +okdos::u32 okdos_fault_vector, okdos_fault_eip, okdos_fault_error; +void okdos_fault_0(); +void okdos_fault_6(); +void okdos_fault_12(); +void okdos_fault_13(); +void okdos_fault_14(); +} + +namespace okdos { +namespace { + +struct kind { + u32 vector; + void (*handler)(); + const char* what; + int status; // 128 and the signal's number +}; + +const kind kKinds[] = { + {0, okdos_fault_0, "a division by zero (#DE)", 128 + 8}, + {6, okdos_fault_6, "an instruction the processor does not have (#UD)", 128 + 4}, + {12, okdos_fault_12, "a fault of the stack (#SS)", 128 + 11}, + {13, okdos_fault_13, "a general protection fault (#GP)", 128 + 11}, + {14, okdos_fault_14, "an access to memory that is not there (#PF)", 128 + 11}, +}; + +unsigned put(char* out, const char* s) { + unsigned n = 0; + while (s[n]) { out[n] = s[n]; ++n; } + return n; +} + +unsigned hex(char* out, u32 v) { + const char digits[] = "0123456789abcdef"; + out[0] = '0'; out[1] = 'x'; + for (int i = 0; i < 8; ++i) out[2 + i] = digits[(v >> (28 - 4 * i)) & 15]; + return 10; +} + +} // namespace + +void install_faults() { + u16 cs = 0, ds = 0; + __asm__ volatile("movw %%cs, %0\n\tmovw %%ds, %1" : "=r"(cs), "=r"(ds)); + okdos_fault_ds = ds; + for (const kind& k : kKinds) { + u32 ax = 0x0203; + __asm__ volatile("int $0x31" + : "+a"(ax) + : "b"(k.vector), "c"(static_cast(cs)), + "d"(reinterpret_cast(k.handler)) + : "memory", "cc"); + } +} + +} // namespace okdos + +extern "C" [[noreturn]] void okdos_fault() { + using namespace okdos; + const kind* k = &kKinds[0]; + for (const kind& c : kKinds) + if (c.vector == okdos_fault_vector) k = &c; + char text[160]; + unsigned n = put(text, "openkal-dos: fault: "); + n += put(text + n, k->what); + n += put(text + n, " at "); + n += hex(text + n, okdos_fault_eip); + if (okdos_fault_vector >= 12) { + n += put(text + n, " (error "); + n += hex(text + n, okdos_fault_error); + n += put(text + n, ")"); + } + n += put(text + n, "\r\n"); + write_handle(2, reinterpret_cast(text), n); + __asm__ volatile("int $0x21" ::"a"(0x4c00 | (k->status & 0xff))); + for (;;) {} +} diff --git a/src/fs.cpp b/src/fs.cpp new file mode 100644 index 0000000..f1f5a13 --- /dev/null +++ b/src/fs.cpp @@ -0,0 +1,619 @@ +// openkal.fs on DOS (int 21h), through the transfer buffer. +// +// A DIRECTORY IS A NAME. DOS has no handle for a directory, so a kal_dir is a slot holding the +// directory's whole name (`C:\WORK'), and every operation joins it with the name it is given, +// each `/' written as DOS's `\'. The supplied directories are those of a Windows environment: +// the current directory first (`C:\WORK'), the one the program was started in, then each volume +// DOS has (`C:', `Z:'). A C library above openkal finds the longest supplied name that is a prefix +// of an absolute name, and a volume's name compares as DOS compares it. +// +// THE VOLUME'S PROPERTIES ARE FAT'S: no case is distinguished, no link is made, a node has a +// modification time (to two seconds, DOS's local time read as UTC, as openkal.time reads the +// clock) and no identity, and a rename refuses a name that exists, which is the rename that does +// not replace; the rename that replaces removes the name first, and is not atomic. A volume's +// capacity is int 21h, 36h's. A listing gives each entry's size and time (find first and next). +// +// The names are DOS's: eight characters, a dot and three, at most 64 bytes in all with the +// volume; a longer whole name is refused by DOS and reported as not found. +#include +#include +#include + +#include "dos.h" + +namespace { + +using namespace okdos; + +constexpr int kDirs = 32; +constexpr u32 kPath = 128; +constexpr kal_uintptr kMaxName = 64; + +// THE TABLES ARE ZERO BEFORE THE PROGRAM STARTS (no initialiser runs for them): a C library above +// reads the supplied directories during its own start, before the program's initialisers run, and +// an initialiser here would then empty the table it had filled. +struct listing { + bool active; + bool first; + u8 dta[43]; +}; + +struct directory { + bool used; + char path[kPath]; // `C:\', `C:\WORK' + listing list; +}; + +directory g_dirs[kDirs]; +int g_preopens = -1; +int g_preopen_slot[kDirs]; +bool g_append[256]; +bool g_writable[256]; + +u32 length(const char* s) { u32 n = 0; while (s[n]) ++n; return n; } + +directory* dir_of(kal_dir d) { + if (d.h == 0 || d.h > kDirs || !g_dirs[d.h - 1].used) return nullptr; + return &g_dirs[d.h - 1]; +} + +int new_dir(const char* path) { + for (int i = 0; i < kDirs; ++i) + if (!g_dirs[i].used) { + g_dirs[i].used = true; + g_dirs[i].list.active = false; + const u32 n = length(path); + copy(g_dirs[i].path, path, n + 1); + return i; + } + return -1; +} + +int error_of(const rm_regs& r) { + switch (r.eax & 0xffff) { + case 2: case 3: case 0x0f: case 0x12: return kal_err_not_found; + case 4: case 8: return kal_err_no_memory; + case 5: return kal_err_permission; + case 6: return kal_err_invalid; + case 0x10: return kal_err_permission; // the current directory is not removed + case 0x11: return kal_err_invalid; // not the same volume + case 0x50: return kal_err_exists; + default: return kal_err_io; + } +} + +// The whole name of `name' beneath `base', in `out'; `.' is the directory itself. A name that +// ascends or starts at a root is refused, as the interface states. +int join(kal_dir base, const char* name, kal_uintptr len, char* out) { + const directory* d = dir_of(base); + if (!d) return kal_err_invalid; + if (!name || len == 0 || name[0] == '/') return kal_err_invalid; + if (len > kMaxName) return kal_err_invalid; + u32 o = length(d->path); + copy(out, d->path, o); + if (len == 1 && name[0] == '.') { out[o] = 0; return kal_ok; } + if (o && out[o - 1] != '\\') out[o++] = '\\'; + for (kal_uintptr i = 0; i < len;) { + kal_uintptr j = i; + while (j < len && name[j] != '/') ++j; + const kal_uintptr n = j - i; + if (n == 2 && name[i] == '.' && name[i + 1] == '.') return kal_err_invalid; + if (!(n == 1 && name[i] == '.') && n) { + if (o + n + 2 >= kPath) return kal_err_invalid; + if (o && out[o - 1] != '\\') out[o++] = '\\'; + for (kal_uintptr k = 0; k < n; ++k) { + // DOS's own separator and a volume's colon would leave the directory. + if (name[i + k] == '\\' || name[i + k] == ':') return kal_err_invalid; + out[o++] = name[i + k]; + } + } + i = j + 1; + } + if (o > 3 && out[o - 1] == '\\') --o; + out[o] = 0; + return kal_ok; +} + +// A name in the transfer buffer at `at', for DS:DX (or ES:DI). +void put_name(const char* path, u32 at) { + copy(transfer() + at, path, length(path) + 1); +} + +// A volume's root: `C:\'. +bool is_root(const char* path) { return path[1] == ':' && path[2] == '\\' && path[3] == 0; } + +// int 21h, 4300h: the attributes of a name; -1 when it does not exist. +int attributes(const char* path) { + if (is_root(path)) return 0x10; + put_name(path, 0); + rm_regs r{}; + r.eax = 0x4300; r.edx = 0; + if (!dos(r)) return -1; + return static_cast(r.ecx & 0xffff); +} + +kal_u64 dos_time_ns(u32 date, u32 time) { + const long long y = 1980 + ((date >> 9) & 0x7f); + const unsigned m = (date >> 5) & 0x0f, d = date & 0x1f; + if (m == 0 || d == 0) return 0; + const long long s = days_from_civil(y, m, d) * 86400 + ((time >> 11) & 0x1f) * 3600 + + ((time >> 5) & 0x3f) * 60 + (time & 0x1f) * 2; + return s < 0 ? 0 : static_cast(s) * 1000000000ull; +} + +// DOS's date and time words of a wall time; false before 1980 or after 2107. +bool ns_dos_time(kal_u64 ns, u32& date, u32& time) { + long long s = static_cast(ns / 1000000000ull); + long long days = s / 86400; + long long rem = s % 86400; + // civil_from_days (H. Hinnant) + days += 719468; + const long long era = days / 146097; + const unsigned doe = static_cast(days - era * 146097); + const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + long long y = static_cast(yoe) + era * 400; + const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + const unsigned mp = (5 * doy + 2) / 153; + const unsigned d = doy - (153 * mp + 2) / 5 + 1; + const unsigned m = mp < 10 ? mp + 3 : mp - 9; + y += m <= 2; + if (y < 1980 || y > 2107) return false; + date = static_cast(((y - 1980) << 9) | (m << 5) | d); + time = static_cast(((rem / 3600) << 11) | (((rem % 3600) / 60) << 5) | ((rem % 60) / 2)); + return true; +} + +void fill(kal_node_info* out, int kind, kal_u32 wanted, bool sized, kal_u64 size, bool timed, kal_u64 ns, + int attr) { + const kal_u32 cap = out->self_size; + kal_node_info v{}; + v.self_size = cap; + v.kind = kind; + v.present = KAL_INFO_KIND; + if (sized && (wanted & KAL_INFO_SIZE)) { v.size = size; v.present |= KAL_INFO_SIZE; } + if (timed && (wanted & KAL_INFO_MODIFIED)) { v.modified_ns = ns; v.present |= KAL_INFO_MODIFIED; } + if (attr >= 0 && (wanted & KAL_INFO_WRITABLE)) { v.writable = (attr & 1) == 0; v.present |= KAL_INFO_WRITABLE; } + const u32 n = cap < sizeof v ? cap : static_cast(sizeof v); + copy(out, &v, n); + out->self_size = cap; +} + +// Find first (4Eh) for `path' into the transfer buffer's DTA at offset 512; true when found. +bool find_first(const char* path, u16 attrs) { + rm_regs set{}; + set.eax = 0x1a00; set.edx = 512; + dos(set); + put_name(path, 0); + rm_regs r{}; + r.eax = 0x4e00; r.ecx = attrs; r.edx = 0; + return dos(r); +} + +u32 dta_u16(const u8* dta, u32 at) { return dta[at] | (dta[at + 1] << 8); } +u32 dta_u32(const u8* dta, u32 at) { return dta_u16(dta, at) | (dta_u16(dta, at + 2) << 16); } + +void preopen_table() { + if (g_preopens >= 0) return; + g_preopens = 0; + rm_regs drive{}; + drive.eax = 0x1900; + dos(drive); + const char letter = static_cast('A' + (drive.eax & 0xff)); + // The current directory: 47h writes it without the volume and the first separator. + rm_regs cwd{}; + cwd.eax = 0x4700; cwd.edx = 0; cwd.esi = 0; + char path[kPath] = {letter, ':', '\\', 0}; + if (dos(cwd)) { + const char* rest = reinterpret_cast(transfer()); + u32 n = 0; + while (rest[n] && n < 64) { path[3 + n] = rest[n]; ++n; } + path[3 + n] = 0; + } + const int s = new_dir(path); + if (s >= 0) g_preopen_slot[g_preopens++] = s; + // The volumes: IOCTL 4409h answers for a drive that exists. + for (int v = 0; v < 26 && g_preopens < kDirs / 2; ++v) { + rm_regs r{}; + r.eax = 0x4409; r.ebx = static_cast(v + 1); + if (!dos(r)) continue; + const char root[4] = {static_cast('A' + v), ':', '\\', 0}; + const int slot = new_dir(root); + if (slot >= 0) g_preopen_slot[g_preopens++] = slot; + } +} + +} // namespace + +namespace okdos { +bool append_handle(u16 handle) { return handle < 256 && g_append[handle]; } +} + +extern "C" { + +kal_uintptr kal_fs_preopen_count(void) { + preopen_table(); + return static_cast(g_preopens); +} + +int kal_fs_preopen(kal_uintptr index, kal_dir* out, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len) { + preopen_table(); + if (!out || index >= static_cast(g_preopens)) return kal_err_invalid; + const int slot = g_preopen_slot[index]; + const char* path = g_dirs[slot].path; + // A volume is named without its separator (`C:'), so the rest of a name beneath it does not + // begin with one; the current directory by its whole name. + u32 n = length(path); + if (index > 0 && is_root(path)) n = 2; + if (name_len) *name_len = n; + if (name_out && name_cap) { + const u32 k = n < name_cap ? n : static_cast(name_cap); + copy(name_out, path, k); + } + out->h = static_cast(slot + 1); + return kal_ok; +} + +kal_uintptr kal_fs_props(kal_dir d) { + if (!dir_of(d)) return 0; + return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CAPACITY | KAL_FS_PROP_RENAME_NO_REPLACE + | KAL_FS_PROP_LIST_INFO; +} + +kal_uintptr kal_fs_max_name(void) { return kMaxName; } + +int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* out) { + if (!out) return kal_err_invalid; + char path[kPath]; + if (int e = join(base, name, len, path)) return e; + const int a = attributes(path); + if (a < 0) return kal_err_not_found; + if (!(a & 0x10)) return kal_err_not_directory; + const int slot = new_dir(path); + if (slot < 0) return kal_err_no_memory; + out->h = static_cast(slot + 1); + return kal_ok; +} + +int kal_fs_open(kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, kal_file* out) { + if (!out) return kal_err_invalid; + if (flags & ~(KAL_OPEN_READ | KAL_OPEN_WRITE | KAL_OPEN_CREATE | KAL_OPEN_EXCLUSIVE + | KAL_OPEN_TRUNCATE | KAL_OPEN_APPEND)) + return kal_err_invalid; + const bool write = flags & (KAL_OPEN_WRITE | KAL_OPEN_APPEND); + const bool read = flags & KAL_OPEN_READ; + if (!read && !write) return kal_err_invalid; + char path[kPath]; + if (int e = join(base, name, len, path)) return e; + const int a = attributes(path); + if (a >= 0 && (a & 0x10)) return kal_err_is_directory; + rm_regs r{}; + r.edx = 0; + put_name(path, 0); + if ((flags & KAL_OPEN_CREATE) && (flags & KAL_OPEN_EXCLUSIVE)) { + r.eax = 0x5b00; r.ecx = 0; // create a new file: refused where one exists + } else if (a < 0 && (flags & KAL_OPEN_CREATE)) { + r.eax = 0x5b00; r.ecx = 0; + } else if (a < 0) { + return kal_err_not_found; + } else if ((flags & KAL_OPEN_TRUNCATE) && write) { + r.eax = 0x3c00; r.ecx = 0; // create or truncate + } else { + r.eax = 0x3d00u | (read && write ? 2u : write ? 1u : 0u); + } + if (!dos(r)) return error_of(r); + u16 handle = static_cast(r.eax & 0xffff); + // 3Ch and 5Bh open for reading and writing whatever was asked; a file meant for reading only + // is opened again so. + if ((r.eax & 0xffff) && (flags & KAL_OPEN_CREATE) && !write) { + rm_regs c{}; + c.eax = 0x3e00; c.ebx = handle; + dos(c); + put_name(path, 0); + rm_regs o{}; + o.eax = 0x3d00; o.edx = 0; + if (!dos(o)) return error_of(o); + handle = static_cast(o.eax & 0xffff); + } + if (handle >= 256) { + rm_regs c{}; + c.eax = 0x3e00; c.ebx = handle; + dos(c); + return kal_err_no_memory; + } + g_append[handle] = flags & KAL_OPEN_APPEND; + g_writable[handle] = write; + out->h = static_cast(handle); + return kal_ok; +} + +void kal_fs_close_dir(kal_dir d) { + if (directory* p = dir_of(d)) { + for (int i = 0; i < g_preopens; ++i) + if (g_preopen_slot[i] == static_cast(d.h - 1)) return; // supplied: kept + p->used = false; + p->list.active = false; + } +} + +void kal_fs_close_file(kal_file f) { + if (f.h < 5 || f.h >= 256) return; + rm_regs r{}; + r.eax = 0x3e00; r.ebx = static_cast(f.h); + dos(r); + g_append[f.h] = false; + g_writable[f.h] = false; +} + +kal_stream kal_fs_stream(kal_file f) { return kal_stream{f.h}; } + +int kal_fs_seek(kal_file f, kal_i64 offset, int whence, kal_u64* result) { + if (whence < 0 || whence > 2 || f.h < 5) return kal_err_invalid; + if (offset > 0x7fffffffll || offset < -0x80000000ll) return kal_err_invalid; + const u32 o = static_cast(offset); + rm_regs r{}; + r.eax = 0x4200u | static_cast(whence); r.ebx = static_cast(f.h); + r.ecx = o >> 16; r.edx = o & 0xffff; + if (!dos(r)) return error_of(r); + if (result) *result = ((r.edx & 0xffff) << 16) | (r.eax & 0xffff); + return kal_ok; +} + +int kal_fs_truncate(kal_file f, kal_u64 size) { + if (size > 0x7fffffffull) return kal_err_invalid; + kal_u64 here = 0, end = 0; + if (int e = kal_fs_seek(f, 0, KAL_SEEK_CURRENT, &here)) return e; + if (int e = kal_fs_seek(f, 0, KAL_SEEK_END, &end)) return e; + int result = kal_ok; + if (size > end) { + // DOS extends a file with whatever its clusters held; openkal extends it with zero. + u8 zero[512]; + for (auto& b : zero) b = 0; + for (kal_u64 left = size - end; left;) { + const kal_uintptr k = left > sizeof zero ? sizeof zero : static_cast(left); + const kal_intptr w = write_handle(static_cast(f.h), zero, k); + if (w <= 0) { result = kal_err_no_space; break; } + left -= static_cast(w); + } + } else { + kal_fs_seek(f, static_cast(size), KAL_SEEK_SET, nullptr); + rm_regs r{}; + r.eax = 0x4000; r.ebx = static_cast(f.h); r.ecx = 0; r.edx = 0; // zero bytes: the end + if (!dos(r)) result = error_of(r); + } + kal_fs_seek(f, static_cast(here), KAL_SEEK_SET, nullptr); + return result; +} + +kal_intptr kal_fs_read_at(kal_file f, void* buf, kal_uintptr len, kal_u64 offset) { + kal_u64 here = 0; + if (int e = kal_fs_seek(f, 0, KAL_SEEK_CURRENT, &here)) return -e; + if (offset > 0x7fffffffull) return -kal_err_invalid; + if (int e = kal_fs_seek(f, static_cast(offset), KAL_SEEK_SET, nullptr)) return -e; + kal_uintptr done = 0; + while (done < len) { + const kal_intptr n = read_handle(static_cast(f.h), static_cast(buf) + done, len - done); + if (n < 0) { if (!done) done = static_cast(n); break; } + if (n == 0) break; + done += static_cast(n); + } + kal_fs_seek(f, static_cast(here), KAL_SEEK_SET, nullptr); + return static_cast(done); +} + +kal_intptr kal_fs_write_at(kal_file f, const void* buf, kal_uintptr len, kal_u64 offset) { + if (f.h >= 256 || !g_writable[f.h]) return -kal_err_permission; + kal_u64 here = 0; + if (int e = kal_fs_seek(f, 0, KAL_SEEK_CURRENT, &here)) return -e; + if (offset > 0x7fffffffull) return -kal_err_invalid; + if (int e = kal_fs_seek(f, static_cast(offset), KAL_SEEK_SET, nullptr)) return -e; + const kal_intptr n = write_handle(static_cast(f.h), static_cast(buf), len); + kal_fs_seek(f, static_cast(here), KAL_SEEK_SET, nullptr); + return n; +} + +int kal_fs_info(kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, kal_u32 wanted, + kal_node_info* out) { + if (!out || out->self_size < 8) return kal_err_invalid; + if (flags & ~KAL_FS_NO_RESOLVE) return kal_err_invalid; + char path[kPath]; + if (int e = join(base, name, len, path)) return e; + if (is_root(path)) { + fill(out, kal_node_directory, wanted, false, 0, false, 0, -1); + return kal_ok; + } + // Find first with every attribute: the entry's own attributes, size, date and time. + if (!find_first(path, 0x37)) { + fill(out, kal_node_absent, wanted, false, 0, false, 0, -1); + return kal_ok; + } + const u8* dta = transfer() + 512; + const int attr = dta[21]; + const bool dir = attr & 0x10; + fill(out, dir ? kal_node_directory : kal_node_file, wanted, !dir, dta_u32(dta, 26), true, + dos_time_ns(dta_u16(dta, 24), dta_u16(dta, 22)), attr); + return kal_ok; +} + +int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) { + char path[kPath]; + if (int e = join(base, name, len, path)) return e; + if (attributes(path) >= 0) return kal_err_exists; + put_name(path, 0); + rm_regs r{}; + r.eax = 0x3900; r.edx = 0; + return dos(r) ? kal_ok : error_of(r); +} + +int kal_fs_remove(kal_dir base, const char* name, kal_uintptr len) { + char path[kPath]; + if (int e = join(base, name, len, path)) return e; + const int a = attributes(path); + if (a < 0) return kal_err_not_found; + put_name(path, 0); + rm_regs r{}; + r.eax = (a & 0x10) ? 0x3a00 : 0x4100; r.edx = 0; + if (dos(r)) return kal_ok; + // A directory that holds entries is refused as access denied, which is what it means here. + if ((a & 0x10) && (r.eax & 0xffff) == 5) return kal_err_not_empty; + return error_of(r); +} + +int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen, kal_dir to, const char* b, + kal_uintptr blen, kal_uintptr flags) { + if (flags & ~KAL_RENAME_NO_REPLACE) return kal_err_invalid; + char pa[kPath], pb[kPath]; + if (int e = join(from, a, alen, pa)) return e; + if (int e = join(to, b, blen, pb)) return e; + const int ab = attributes(pb); + if (ab >= 0) { + if (flags & KAL_RENAME_NO_REPLACE) return kal_err_exists; + // The rename that replaces: DOS refuses a name that exists, so it is removed first. + if (ab & 0x10) return kal_err_is_directory; + put_name(pb, 0); + rm_regs del{}; + del.eax = 0x4100; del.edx = 0; + if (!dos(del)) return error_of(del); + } + put_name(pa, 0); + put_name(pb, 256); + rm_regs r{}; + r.eax = 0x5600; r.edx = 0; r.edi = 256; + return dos(r) ? kal_ok : error_of(r); +} + +int kal_fs_rename(kal_dir from, const char* a, kal_uintptr alen, kal_dir to, const char* b, + kal_uintptr blen) { + return kal_fs_rename_flags(from, a, alen, to, b, blen, 0); +} + +int kal_fs_file_info(kal_file f, kal_u32 wanted, kal_node_info* out) { + if (!out || out->self_size < 8 || f.h < 5) return kal_err_invalid; + kal_u64 here = 0, end = 0; + if (int e = kal_fs_seek(f, 0, KAL_SEEK_CURRENT, &here)) return e; + kal_fs_seek(f, 0, KAL_SEEK_END, &end); + kal_fs_seek(f, static_cast(here), KAL_SEEK_SET, nullptr); + rm_regs t{}; + t.eax = 0x5700; t.ebx = static_cast(f.h); + const bool timed = dos(t); + fill(out, kal_node_file, wanted, true, end, timed, timed ? dos_time_ns(t.edx & 0xffff, t.ecx & 0xffff) : 0, -1); + return kal_ok; +} + +int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) { + u32 date = 0, time = 0; + if (!ns_dos_time(modified_ns, date, time)) return kal_err_invalid; + rm_regs r{}; + r.eax = 0x5701; r.ebx = static_cast(f.h); r.ecx = time; r.edx = date; + return dos(r) ? kal_ok : error_of(r); +} + +int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len, kal_u64 modified_ns) { + kal_file f{}; + if (int e = kal_fs_open(base, name, len, KAL_OPEN_READ, &f)) return e; + const int r = kal_fs_set_modified(f, modified_ns); + kal_fs_close_file(f); + return r; +} + +int kal_fs_set_executable_at(kal_dir, const char*, kal_uintptr, int) { return kal_err_not_supported; } +int kal_fs_lock(kal_file, kal_u64, kal_u64, kal_uintptr) { return kal_err_not_supported; } +int kal_fs_unlock(kal_file, kal_u64, kal_u64) { return kal_err_not_supported; } + +int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) { + const directory* p = dir_of(d); + if (!p) return kal_err_invalid; + char letter = p->path[0]; + if (letter >= 'a' && letter <= 'z') letter = static_cast(letter - 'a' + 'A'); + rm_regs r{}; + r.eax = 0x3600; r.edx = static_cast(letter - 'A' + 1); + if (!dos(r) || (r.eax & 0xffff) == 0xffff) return kal_err_not_found; + const kal_u64 cluster = static_cast(r.eax & 0xffff) * (r.ecx & 0xffff); + if (total) *total = cluster * (r.edx & 0xffff); + if (available) *available = cluster * (r.ebx & 0xffff); + return kal_ok; +} + +int kal_fs_link_create(kal_dir, const char*, kal_uintptr, const char*, kal_uintptr, kal_uintptr) { + return kal_err_not_supported; +} +kal_intptr kal_fs_link_read(kal_dir, const char*, kal_uintptr, char*, kal_uintptr) { + return -kal_err_not_supported; +} +int kal_fs_hard_link(kal_dir, const char*, kal_uintptr, kal_dir, const char*, kal_uintptr) { + return kal_err_not_supported; +} +kal_intptr kal_fs_resolved_name(kal_dir, const char*, kal_uintptr, char*, kal_uintptr) { + return -kal_err_not_supported; +} +int kal_fs_dir_attenuate(kal_dir, const kal_dir_rights*, kal_dir*) { return kal_err_not_supported; } + +int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) { + directory* p = dir_of(d); + if (!p || !iter) return kal_err_invalid; + p->list.active = true; + p->list.first = true; + *iter = 1; + return kal_ok; +} + +int kal_fs_list_next_info(kal_dir d, kal_uintptr* iter, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len, kal_u32 wanted, kal_node_info* info) { + directory* p = dir_of(d); + if (!p || !iter) return kal_err_invalid; + if (*iter == 0 || !p->list.active) { + *iter = 0; + if (name_len) *name_len = 0; + return kal_ok; + } + for (;;) { + bool found; + if (p->list.first) { + char pattern[kPath]; + u32 n = length(p->path); + copy(pattern, p->path, n); + if (n && pattern[n - 1] != '\\') pattern[n++] = '\\'; + copy(pattern + n, "*.*", 4); + found = find_first(pattern, 0x37); + p->list.first = false; + } else { + rm_regs set{}; + set.eax = 0x1a00; set.edx = 512; + dos(set); + copy(transfer() + 512, p->list.dta, sizeof p->list.dta); + rm_regs r{}; + r.eax = 0x4f00; + found = dos(r); + } + if (!found) { + p->list.active = false; + *iter = 0; + if (name_len) *name_len = 0; + return kal_ok; + } + copy(p->list.dta, transfer() + 512, sizeof p->list.dta); + const u8* dta = p->list.dta; + const char* name = reinterpret_cast(dta + 30); + const u32 n = length(name); + if ((n == 1 && name[0] == '.') || (n == 2 && name[0] == '.' && name[1] == '.')) continue; + if (dta[21] & 0x08) continue; // a volume label is not an entry of the directory + if (name_len) *name_len = n; + if (name_out && name_cap) copy(name_out, name, n < name_cap ? n : static_cast(name_cap)); + const bool dir = dta[21] & 0x10; + if (info) + fill(info, dir ? kal_node_directory : kal_node_file, wanted, !dir, dta_u32(dta, 26), true, + dos_time_ns(dta_u16(dta, 24), dta_u16(dta, 22)), dta[21]); + return kal_ok; + } +} + +int kal_fs_list_next(kal_dir d, kal_uintptr* iter, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len, int* kind) { + kal_node_info info{}; + info.self_size = sizeof info; + const int r = kal_fs_list_next_info(d, iter, name_out, name_cap, name_len, KAL_INFO_KIND, &info); + if (r == kal_ok && kind) *kind = *iter ? info.kind : kal_node_absent; + return r; +} + +} // extern "C" diff --git a/src/kal.cpp b/src/kal.cpp new file mode 100644 index 0000000..58c0bdc --- /dev/null +++ b/src/kal.cpp @@ -0,0 +1,177 @@ +// openkal's core interfaces on DOS, through DPMI (openkal-dos; technical experiment only). +// +// DOS divides its services as openkal does for three interfaces: handles 0 to 2 are the standard +// streams (int 21h, 3Fh and 40h), the program ends with a status (4Ch), and memory beyond the +// first megabyte is the DPMI host's (int 31h, 0501h). The interfaces DOS has no counterpart for +// --- contexts, processes as openkal states them, the network --- are absent rather than present +// and failing (openkal clause 6.2). +#include +#include +#include +#include + +#include "dos.h" + +namespace okdos { + +// Writes through the transfer buffer, a piece at a time. +long write_handle(u16 handle, const u8* p, unsigned long n) { + unsigned long done = 0; + while (done < n) { + u32 k = static_cast(n - done); + if (k > transfer_size()) k = transfer_size(); + copy(transfer(), p + done, k); + rm_regs r{}; + r.eax = 0x4000; r.ebx = handle; r.ecx = k; r.edx = 0; + if (!dos(r)) return done ? static_cast(done) : -kal_err_io; + const u32 wrote = r.eax & 0xffff; + done += wrote; + if (wrote < k) break; // a full disk, or a device that takes no more + } + return static_cast(done); +} + +long read_handle(u16 handle, u8* p, unsigned long n) { + u32 k = static_cast(n); + if (k > transfer_size()) k = transfer_size(); + rm_regs r{}; + r.eax = 0x3f00; r.ebx = handle; r.ecx = k; r.edx = 0; + if (!dos(r)) return (r.eax & 0xffff) == 5 ? -kal_err_permission : -kal_err_io; + const u32 got = r.eax & 0xffff; + copy(p, transfer(), got); + return static_cast(got); +} + +} // namespace okdos + +namespace { + +using namespace okdos; + +constexpr kal_uintptr kStdin = 0, kStdout = 1, kStderr = 2; + +// IOCTL 4400h: bit 7 of the device word is "a device, not a file"; bit 0 or 1 the console. +bool is_console(u16 handle) { + rm_regs r{}; + r.eax = 0x4400; r.ebx = handle; + return dos(r) && (r.edx & 0x80) && (r.edx & 0x03); +} + +} // namespace + +extern "C" { + +KAL_NORETURN void kal_exit(int code) { + __asm__ volatile("int $0x21" ::"a"(0x4c00 | (code & 0xff))); + for (;;) {} +} + +KAL_NORETURN void kal_abort(const char* msg, kal_uintptr len) { + if (self().h) { + if (msg && len) write_handle(2, reinterpret_cast(msg), len); + write_handle(2, reinterpret_cast("\r\n"), 2); + } + // 134, as a process ended by SIGABRT reports itself on the systems that have the signal. + kal_exit(134); +} + +// ── openkal.stream ────────────────────────────────────────────────────────── +kal_stream kal_stdin(void) { return kal_stream{kStdin}; } +kal_stream kal_stdout(void) { return kal_stream{kStdout}; } +kal_stream kal_stderr(void) { return kal_stream{kStderr}; } + +// A stream is a DOS handle: the three standard ones, or a file's (openkal.fs), which DOS numbers +// from 5. A file opened for appending writes at its end each time (fs.cpp). +kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { + if (s.h == kStdin || s.h == 3 || s.h == 4 || s.h >= 256) return -kal_err_invalid; + if (append_handle(static_cast(s.h))) { + rm_regs r{}; + r.eax = 0x4202; r.ebx = static_cast(s.h); r.ecx = 0; r.edx = 0; + dos(r); + } + return write_handle(static_cast(s.h), static_cast(buf), n); +} + +kal_intptr kal_stream_read(kal_stream s, void* buf, kal_uintptr n) { + if (s.h == kStdout || s.h == kStderr || s.h == 3 || s.h == 4 || s.h >= 256) return -kal_err_invalid; + return read_handle(static_cast(s.h), static_cast(buf), n); +} + +// DOS writes a standard handle's bytes when the call returns; a file's are committed (68h). +int kal_stream_flush(kal_stream s) { + if (s.h <= kStderr) return kal_ok; + if (s.h < 5 || s.h >= 256) return kal_err_invalid; + rm_regs r{}; + r.eax = 0x6800; r.ebx = static_cast(s.h); + dos(r); + return kal_ok; +} + +kal_uintptr kal_stream_props(kal_stream s) { + if (s.h > kStderr) return 0; + return is_console(static_cast(s.h)) ? KAL_STREAM_PROP_INTERACTIVE : 0; +} + +// ── openkal.memory ────────────────────────────────────────────────────────── +// +// Each region is a block of the DPMI host (0501h), large enough to place the region at its +// alignment with a record of the block before it: its handle and its linear address. The host +// allocates in pages of four kilobytes, which is the granularity stated. +kal_uintptr kal_memory_granularity(void) { return 4096; } + +void* kal_alloc(kal_uintptr size, kal_uintptr align) { + if (!self().h) return nullptr; + if (size == 0) size = 1; + if (align < 16) align = 16; + if ((align & (align - 1)) != 0) return nullptr; + const u32 want = static_cast(size + align + 16); + if (want < size) return nullptr; + u32 bx = want >> 16, cx = want & 0xffff, si, di, carry; + __asm__ volatile("int $0x31\n\tsbb %4, %4" + : "+b"(bx), "+c"(cx), "=S"(si), "=D"(di), "=r"(carry) + : "a"(0x0501) + : "memory", "cc"); + if (carry) return nullptr; + const u32 linear = (bx << 16) | (cx & 0xffff); + const u32 handle = (si << 16) | (di & 0xffff); + const u32 base = self().h->base; + u32 at = (linear - base + 16 + static_cast(align) - 1) & ~(static_cast(align) - 1); + auto* record = reinterpret_cast(static_cast(at - 8)); + record[0] = handle; + record[1] = linear; + return reinterpret_cast(static_cast(at)); +} + +void kal_free(void* p, kal_uintptr, kal_uintptr) { + if (!p) return; + const auto* record = reinterpret_cast(static_cast(p) - 8); + const u32 handle = record[0]; + u32 si = handle >> 16, di = handle & 0xffff; + __asm__ volatile("int $0x31" : "+S"(si), "+D"(di) : "a"(0x0502) : "memory", "cc"); +} + +// DPMI 0500h: the largest free block, and, where the host states them, the pages in total and free. +int kal_memory_info(kal_memory_status* out) { + if (out == nullptr || out->self_size < 2 * sizeof(kal_u32)) return kal_err_invalid; + u32 info[12]; + for (auto& w : info) w = ~0u; + int carry; + __asm__ volatile("push %%es\n\tpush %%ds\n\tpop %%es\n\tint $0x31\n\tpop %%es\n\tsbb %0, %0" + : "=r"(carry) : "a"(0x0500), "D"(info) : "memory", "cc"); + out->present = 0; + if (carry) return kal_ok; + if (out->self_size >= sizeof(kal_memory_status)) { + if (info[6] != ~0u) { out->total_bytes = static_cast(info[6]) * 4096; out->present |= KAL_MSTATUS_TOTAL; } + if (info[0] != ~0u) { out->available_bytes = info[0]; out->present |= KAL_MSTATUS_AVAILABLE; } + } + return kal_ok; +} + +// ── version ───────────────────────────────────────────────────────────────── +kal_u64 kal_version(void) { return KAL_VERSION; } + +kal_u64 kal_interfaces(void) { + return KAL_IFACE_ABORT | KAL_IFACE_STREAM | KAL_IFACE_MEMORY | KAL_IFACE_ENV | KAL_IFACE_TIME; +} + +} // extern "C" diff --git a/src/start.S b/src/start.S new file mode 100644 index 0000000..901837c --- /dev/null +++ b/src/start.S @@ -0,0 +1,14 @@ +// The executable's entry: the stub (stub/stub.S) jumps here in 32-bit protected mode with EBX the +// offset of its hand-over record, and a first stack at the end of its block. + .section .text.okdos_entry, "ax" + .globl okdos_entry + .type okdos_entry, @function +okdos_entry: + xorl %ebp, %ebp + andl $-16, %esp + subl $12, %esp + pushl %ebx + call okdos_start + ud2 + .size okdos_entry, . - okdos_entry + .section .note.GNU-stack, "", @progbits diff --git a/src/start.cpp b/src/start.cpp new file mode 100644 index 0000000..3b96bcb --- /dev/null +++ b/src/start.cpp @@ -0,0 +1,74 @@ +// What the stub enters, and what a program receives (openkal clause 7.13). +// +// okdos_entry (start.S) calls okdos_start with the stub's hand-over record. The arguments are the +// command tail of the program's segment prefix, at offset 80h: a count, then the text, ended by +// a carriage return; position zero is the program's name. +#include + +#include "dos.h" + +extern "C" int main(int, char**, char**); + +#ifdef OKDOS_STANDALONE +// THE STANDALONE ARRANGEMENT: a C library above this implementation (openkal-musl names it so +// on `cfg(os = "dos")') receives the program at the name every C library's hand-over has; it +// obtains the arguments through openkal.env and runs the initialisers itself. +extern "C" int __libc_start_main(int (*)(int, char**, char**), int, char**, + void (*)(), void (*)(), void (*)()); +#else +// Without a C library, the initialisers the linker gathered run here, in order. +extern "C" { +using initialiser = void (*)(); +extern initialiser __init_array_start[] __attribute__((weak)); +extern initialiser __init_array_end[] __attribute__((weak)); +} +#endif + +namespace { + +using namespace okdos; + +// Room for the command tail's words: at most 127 bytes, so at most 64 words. +char tail[128]; +char* words[66]; + +void record_arguments(const handoff* h) { + state& s = self(); + const u8* psp = low(static_cast(h->psp_segment) * 16u); + u32 n = psp[0x80]; + if (n > 127) n = 127; + copy(tail, psp + 0x81, n); + tail[n] = 0; + int argc = 0; + words[argc++] = const_cast(h->name); + for (u32 i = 0; i < n;) { + while (i < n && (tail[i] == ' ' || tail[i] == '\t' || tail[i] == '\r')) tail[i++] = 0; + if (i >= n) break; + words[argc++] = &tail[i]; + while (i < n && tail[i] != ' ' && tail[i] != '\t' && tail[i] != '\r') ++i; + } + words[argc] = nullptr; + s.argc = argc; + s.argv = words; +} + +} // namespace + +extern "C" [[noreturn]] void okdos_start(const handoff* h) { + if (h->magic != kMagic || h->version != 1) { + // A stub of another version: nothing here can be trusted to say so but the status. + kal_exit(127); + } + self().h = h; + install_faults(); + record_arguments(h); +#ifdef OKDOS_STANDALONE + __libc_start_main(main, 0, nullptr, nullptr, nullptr, nullptr); + kal_exit(127); +#else + if (__init_array_start) + for (initialiser* f = __init_array_start; f < __init_array_end; ++f) (*f)(); + static char* no_environment[] = {nullptr}; + kal_exit(main(self().argc, self().argv, no_environment)); +#endif +} diff --git a/src/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000..7556c95 --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,81 @@ +// openkal.terminal on DOS: the console device's mode (IOCTL 4400h and 4401h) and the display's +// size from the BIOS. +// +// DOS distinguishes one mode of the console, by bit 5 of its device word: cooked, where DOS +// assembles a line, echoes it and reserves Ctrl+C and Ctrl+S, and raw (binary), where each byte +// typed reaches the program as typed and nothing is reserved. The four input positions of openkal +// are therefore one switch here: cooked is KAL_TERM_LINE_EDIT | KAL_TERM_ECHO, raw is +// KAL_TERM_PASS_CONTROL | KAL_TERM_PASS_INPUT, and a requested mode is raw when it asks for no line +// assembly. KAL_TERM_SEQUENCES is reported where ANSI.SYS answers its installation check (int 2Fh, +// 1A00h). The size is the BIOS's: the columns at 0040:004A and the rows less one at 0040:0084. +#include + +#include "dos.h" + +namespace { + +using namespace okdos; + +constexpr u32 kRaw = 0x20; + +// The console's device word, or false for a handle that is not the console. +bool console_word(kal_stream s, u32& word) { + if (s.h >= 256) return false; + rm_regs r{}; + r.eax = 0x4400; r.ebx = static_cast(s.h); + if (!dos(r)) return false; + word = r.edx & 0xffff; + return (word & 0x80) && (word & 0x03); +} + +bool ansi_installed() { + rm_regs r{}; + r.eax = 0x1a00; + real_int(0x2f, r); + return (r.eax & 0xff) == 0xff; +} + +} // namespace + +extern "C" { + +int kal_terminal_get_mode(kal_stream s, kal_uintptr* mode) { + u32 word = 0; + if (!console_word(s, word)) return kal_err_not_supported; + if (!mode) return kal_err_invalid; + kal_uintptr m = (word & kRaw) ? (KAL_TERM_PASS_CONTROL | KAL_TERM_PASS_INPUT) + : (KAL_TERM_LINE_EDIT | KAL_TERM_ECHO); + if (ansi_installed()) m |= KAL_TERM_SEQUENCES; + *mode = m; + return kal_ok; +} + +int kal_terminal_set_mode(kal_stream s, kal_uintptr mode) { + u32 word = 0; + if (!console_word(s, word)) return kal_err_not_supported; + const bool raw = !(mode & KAL_TERM_LINE_EDIT); + if (raw == ((word & kRaw) != 0)) return kal_ok; // in effect already: not written again + rm_regs r{}; + r.eax = 0x4401; r.ebx = static_cast(s.h); + r.edx = (word & 0xff & ~kRaw) | (raw ? kRaw : 0); + return dos(r) ? kal_ok : kal_err_io; +} + +int kal_terminal_size(kal_stream s, kal_uintptr* cols, kal_uintptr* rows) { + u32 word = 0; + if (!console_word(s, word)) return kal_err_not_supported; + const u32 c = *reinterpret_cast(low(0x44a)); + const u32 r = *reinterpret_cast(low(0x484)); + if (c == 0) return kal_err_not_supported; + if (cols) *cols = c; + if (rows) *rows = r ? r + 1 : 25; + return kal_ok; +} + +kal_uintptr kal_terminal_props(kal_stream s) { + u32 word = 0; + if (!console_word(s, word)) return 0; + return KAL_TERM_PROP_MODE | KAL_TERM_PROP_SIZE; +} + +} // extern "C" diff --git a/src/time.cpp b/src/time.cpp new file mode 100644 index 0000000..a650784 --- /dev/null +++ b/src/time.cpp @@ -0,0 +1,66 @@ +// openkal.time on DOS. +// +// MONOTONIC: the BIOS's tick count (linear 0x46C), which the timer interrupt advances 1,573,040 +// times a day (about 18.2 a second) and sets back to zero at midnight; each reading that is below +// the last one counts a day. Its granularity is one tick, 54,925,494 nanoseconds. +// WALL: DOS's date and time (int 21h, 2Ah and 2Ch), to the hundredth of a second. DOS keeps local +// time and states no zone; it is reported as UTC, which it is where the machine's clock was set so. +// SLEEP: the BIOS's wait (int 15h, 86h), in microseconds. +#include + +#include "dos.h" + +namespace { + +using namespace okdos; + +constexpr kal_u64 kTicksPerDay = 0x1800B0; +constexpr kal_u64 kTickNs = 54925494; + +} // namespace + +extern "C" { + +kal_duration kal_time_monotonic(void) { + state& s = self(); + const u32 now = *reinterpret_cast(low(0x46c)); + if (now < s.ticks_last) ++s.ticks_days; + s.ticks_last = now; + const kal_u64 ticks = static_cast(s.ticks_days) * kTicksPerDay + now; + return static_cast(ticks * kTickNs); +} + +kal_duration kal_time_monotonic_granularity(void) { return kTickNs; } + +kal_duration kal_time_wall(void) { + rm_regs d{}; + d.eax = 0x2a00; + if (!dos(d)) return 0; + rm_regs t{}; + t.eax = 0x2c00; + if (!dos(t)) return 0; + const long long year = d.ecx & 0xffff; + const unsigned month = (d.edx >> 8) & 0xff, day = d.edx & 0xff; + const unsigned hour = (t.ecx >> 8) & 0xff, minute = t.ecx & 0xff; + const unsigned second = (t.edx >> 8) & 0xff, hundredths = t.edx & 0xff; + const long long secs = days_from_civil(year, month, day) * 86400 + hour * 3600 + minute * 60 + second; + if (secs < 0) return 0; + return static_cast(secs) * 1000000000ull + static_cast(hundredths) * 10000000ull; +} + +void kal_time_sleep(kal_duration ns) { + kal_duration us = (ns + 999) / 1000; + while (us) { + const kal_duration step = us > 1000000 ? 1000000 : us; + rm_regs r{}; + r.eax = 0x8600; + r.ecx = static_cast(step >> 16); + r.edx = static_cast(step & 0xffff); + real_int(0x15, r); + us -= step; + } +} + +kal_uintptr kal_time_props(void) { return KAL_TIME_PROP_WALL_AVAILABLE; } + +} // extern "C" diff --git a/stub/build.sh b/stub/build.sh new file mode 100755 index 0000000..ba6f362 --- /dev/null +++ b/stub/build.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Assembles the stub and writes tools/src/stub_bytes.h, the bytes the seal writes before an +# executable. Run after changing stub/stub.S; the result is committed, so that the seal builds +# on a machine whose assembler has no 16-bit x86 (an aarch64 host's). +# LLVM= stub/build.sh +set -eu +here=$(cd "$(dirname "$0")" && pwd) +LLVM=${LLVM:-$(dirname "$(command -v clang)")} +work=$(mktemp -d) +trap 'rm -f "$work/stub.o" "$work/stub.bin"; rmdir "$work"' EXIT +"$LLVM/clang" --target=i386-unknown-none-elf -c "$here/stub.S" -o "$work/stub.o" +"$LLVM/ld.lld" -m elf_i386 --oformat binary --image-base=0 -Ttext=0 -e _okdos_stub "$work/stub.o" -o "$work/stub.bin" +python3 - "$work/stub.bin" "$here/../tools/src/stub_bytes.h" <<'PY' +import sys, hashlib +b = open(sys.argv[1], 'rb').read() +lines = ['// Generated by stub/build.sh from stub/stub.S; do not edit.', + f'// {len(b)} bytes, sha256 {hashlib.sha256(b).hexdigest()}', + '#pragma once', 'inline constexpr unsigned char kOkdosStub[] = {'] +for i in range(0, len(b), 16): + lines.append(' ' + ', '.join(f'0x{x:02x}' for x in b[i:i+16]) + ',') +lines.append('};') +open(sys.argv[2], 'w').write('\n'.join(lines) + '\n') +PY +echo "stub: $(wc -c < "$work/stub.bin") bytes" diff --git a/stub/stub.S b/stub/stub.S new file mode 100644 index 0000000..ab8ddb7 --- /dev/null +++ b/stub/stub.S @@ -0,0 +1,579 @@ +// okdos stub: a DOS program that runs the ELF32 i386 executable appended to it in 32-bit +// protected mode through DPMI (openkal-dos, technical experiment only). +// +// THE FILE is this stub (an MZ program) followed by the executable. The stub's offsets are the +// file's: the header states the code segment and the stack segment two paragraphs below the load +// module, so CS:0 is the file's first byte and an offset here is an offset there. +// +// WHAT IT DOES, in order: frees the memory DOS gave it beyond its own; finds the program's name +// at the end of its environment and opens it; reads the executable's ELF header and program +// headers; asks for a DPMI host (int 2Fh, 1687h), and where there is none runs CWSDPMI.EXE from +// the program's directory and asks again; enters protected mode as a 32-bit client; allocates +// one block of memory for the executable's segments, its first stack and the hand-over record; +// makes a code and a data descriptor whose base is the block and whose limit spans 4 GiB; reads +// each loadable segment into the block through a transfer buffer in conventional memory (int 21h +// simulated by DPMI 0300h), zeroes what the file does not hold, and enters the executable at its +// ELF entry with EBX the hand-over record's offset (src/dos.h, okdos::handoff). +// +// Built by stub/build.sh into tools/src/stub_bytes.h, which the seal (tools/) writes before the +// executable. Apache-2.0, as the rest of openkal-dos; nothing of DJGPP's stub is used. + .code16 + .text + .globl _okdos_stub +_okdos_stub: + // ── MZ header (32 bytes) ───────────────────────────────────────────── + .ascii "MZ" + .word (stub_end - _okdos_stub) % 512 + .word (stub_end - _okdos_stub + 511) / 512 + .word 0 // relocations + .word 2 // header paragraphs + .word (STACK + 15) / 16 // extra paragraphs needed: the stack + .word 0xffff // extra paragraphs wanted + .word 0xfffe // SS: two paragraphs below the load module + .word stub_end + STACK // SP + .word 0 // checksum + .word start // IP + .word 0xfffe // CS: two paragraphs below the load module + .word 0x1c // relocation table + .word 0 // overlay + .word 0, 0 + + .set STACK, 0x800 + .set PHMAX, 16 + .set TB_PARAS, 0x400 // the transfer buffer: 16 KiB + .set FIRST_STACK, 0x10000 // the executable's first stack: 64 KiB + +start: + movw %es, %ax + movw %cs, %bx + movw %bx, %ds + movw %ax, psp_seg + // Keep the stub and its stack; give the rest back so a DPMI host can be loaded. + movw %cs, %bx + subw %ax, %bx // paragraphs from the PSP to CS + addw $((stub_end - _okdos_stub + STACK + 15) / 16), %bx + movb $0x4a, %ah + int $0x21 + jc fail_memory + + // The program's name: after the environment's strings, a word, then the name. + movw psp_seg, %es + movw %es:0x2c, %ax + movw %ax, env_seg + movw %ax, %es + xorw %di, %di +1: cmpb $0, %es:(%di) + je 2f +3: incw %di + cmpb $0, %es:(%di) + jne 3b + incw %di + jmp 1b +2: addw $3, %di // the empty string, then the count word + movw %di, name_off + // Copy it, for CWSDPMI's name beside it. + movw $path, %si + xorw %bx, %bx // the last separator's position in path +4: movb %es:(%di), %al + movb %al, (%si) + cmpb $'\\', %al + jne 5f + movw %si, %bx +5: incw %di + incw %si + cmpb $0, %al + jne 4b + movw %bx, path_sep + + // Open the program (read only) and read the executable's headers. + pushw %ds + movw env_seg, %ds + movw %cs:name_off, %dx + movw $0x3d00, %ax + int $0x21 + popw %ds + jc fail_open + movw %ax, handle + movw $stub_end, %dx + xorw %cx, %cx + call seek + movw $ehdr, %dx + movw $52, %cx + call read + jc fail_read + cmpl $0x464c457f, ehdr // \x7fELF + jne fail_format + cmpw $0x0101, ehdr + 4 // 32-bit, little-endian + jne fail_format + cmpw $3, ehdr + 18 // EM_386 + jne fail_format + movw ehdr + 44, %cx // e_phnum + cmpw $PHMAX, %cx + ja fail_format + movw %cx, phnum + shlw $5, %cx // 32 bytes each + movl ehdr + 28, %edx // e_phoff + addl $stub_end, %edx + pushw %cx + movl %edx, %eax + movw %ax, %dx + shrl $16, %eax + movw %ax, %cx + call seek + popw %cx + movw $phdrs, %dx + call read + jc fail_read + + // The span: the highest end of a loadable segment. + xorl %eax, %eax + movw phnum, %cx + movw $phdrs, %si +6: jcxz 7f + cmpl $1, (%si) // PT_LOAD + jne 8f + movl 8(%si), %edx // p_vaddr + addl 20(%si), %edx // p_memsz + cmpl %eax, %edx + jbe 8f + movl %edx, %eax +8: addw $32, %si + decw %cx + jmp 6b +7: addl $0xfff, %eax + andl $0xfffff000, %eax + movl %eax, span + addl $(FIRST_STACK + 0x1000), %eax + movl %eax, block_size + + // A DPMI host, or CWSDPMI from the program's directory. + call ask_dpmi + jnc have_dpmi + call load_cwsdpmi + call ask_dpmi + jc fail_dpmi +have_dpmi: + // The host's private data, in conventional memory. + movw host_paras, %bx + testw %bx, %bx + jz 9f + movb $0x48, %ah + int $0x21 + jc fail_memory + movw %ax, host_seg +9: movw host_seg, %es + movw $1, %ax // a 32-bit client + lcall *switch_entry + jc fail_switch + + // ── Protected mode (16-bit code, selectors for the real-mode segments) ───────────── + movw %es, psp_sel + // The block: its linear address in BX:CX, its handle in SI:DI. + movl block_size, %eax + movw %ax, %cx + shrl $16, %eax + movw %ax, %bx + movw $0x0501, %ax + int $0x31 + jc fail_block + movw %bx, base + 2 + movw %cx, base + movw %si, block_handle + 2 + movw %di, block_handle + // Two descriptors: code, then data. + xorw %ax, %ax + movw $2, %cx + int $0x31 + jc fail_desc + movw %ax, code_sel + movw %ax, %bx + movw $3, %ax + int $0x31 + addw %bx, %ax + movw %ax, data_sel + // Base and limit of each; access: present, the client's privilege, 32-bit, 4 KiB granular. + movw %cs, %ax + andw $3, %ax + shlw $5, %ax + movw %ax, dpl + movw code_sel, %bx + call frame_descriptor + movw dpl, %cx + orw $0xc09a, %cx // code: execute/read + movw $9, %ax + int $0x31 + jc fail_desc + movw data_sel, %bx + call frame_descriptor + movw dpl, %cx + orw $0xc092, %cx // data: read/write + movw $9, %ax + int $0x31 + jc fail_desc + // The transfer buffer. + movw $0x0100, %ax + movw $TB_PARAS, %bx + int $0x31 + jc fail_memory_pm + movw %ax, tb_seg + movw %dx, tb_sel + + // Each loadable segment, read into the block, the rest of it zero. + movw phnum, %cx + movw $phdrs, %si +10: jcxz 11f + pushw %cx + pushw %si + cmpl $1, (%si) + jne 12f + call load_segment + jc fail_read_pm +12: popw %si + popw %cx + addw $32, %si + decw %cx + jmp 10b +11: + + // The hand-over record, at the end of the executable's span. + movw data_sel, %es + movl span, %edi + movl $0x53444b4f, %es:(%edi) // "OKDS" + movl $1, %es:4(%edi) // version + movl base, %eax + movl %eax, %es:8(%edi) + movl block_size, %eax + movl %eax, %es:12(%edi) + movl block_handle, %eax + movl %eax, %es:16(%edi) + movw psp_sel, %ax + movw %ax, %es:20(%edi) + movw psp_seg, %ax + movw %ax, %es:22(%edi) + movw env_seg, %ax + movw %ax, %es:24(%edi) + movw tb_seg, %ax + movw %ax, %es:26(%edi) + movl $(TB_PARAS * 16), %es:28(%edi) + movw code_sel, %ax + movw %ax, %es:32(%edi) + movw data_sel, %ax + movw %ax, %es:34(%edi) + movw tb_sel, %ax + movw %ax, %es:36(%edi) + movw handle, %ax // the program's file, left open: closing it here hangs + movw %ax, %es:38(%edi) // under DOSBox Staging, and DOS closes it at the end + // The name the program was started by, as DOS gave it. + leal 40(%edi), %edi + movw $path, %si +13: movb (%si), %al + movb %al, %es:(%edi) + incw %si + incl %edi + testb %al, %al + jnz 13b + + // Enter: DS, ES and SS the data descriptor, the first stack at the block's end. + movl span, %ebx + movl ehdr + 24, %edx // e_entry + movw code_sel, %cx + movw data_sel, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movl %cs:block_size, %esp + subl $16, %esp + pushl %ecx + pushl %edx + lretl + +// ── Real mode helpers ────────────────────────────────────────────────────── +// seek to CX:DX from the file's start +seek: + movw handle, %bx + movw $0x4200, %ax + int $0x21 + ret +// read CX bytes into DS:DX; carry on error or a short read +read: + pushw %cx + movw handle, %bx + movb $0x3f, %ah + int $0x21 + popw %cx + jc 1f + cmpw %cx, %ax + jne 2f + clc + ret +2: stc +1: ret +// int 2Fh 1687h: carry when there is no 32-bit host +ask_dpmi: + movw $0x1687, %ax + int $0x2f + testw %ax, %ax + jnz 1f + testw $1, %bx + jz 1f + movw %si, host_paras + movw %di, switch_entry + movw %es, switch_entry + 2 + clc + ret +1: stc + ret +// CWSDPMI.EXE beside the program, run as a program; it stays resident for this one. +load_cwsdpmi: + movw path_sep, %di + incw %di + movw $cwsdpmi_name, %si +1: movb (%si), %al + movb %al, (%di) + incw %si + incw %di + testb %al, %al + jnz 1b + movw %cs, %ax + movw %ax, exec_tail + 2 + movw %ax, exec_fcb1 + 2 + movw %ax, exec_fcb2 + 2 + movw %ax, %es + movw $exec_block, %bx + movw $path, %dx + movw %sp, saved_sp + movw $0x4b00, %ax + int $0x21 + movw %cs, %ax // DOS 2 kept nothing but CS and IP across EXEC + movw %ax, %ds + cli + movw %ax, %ss + movw saved_sp, %sp + sti + // The program's own name again, for what follows. + movw env_seg, %es + movw name_off, %si + movw $path, %di +2: movb %es:(%si), %al + movb %al, (%di) + incw %si + incw %di + testb %al, %al + jnz 2b + ret + +// ── Protected mode helpers ───────────────────────────────────────────────── +// BX a descriptor: its base the block, its limit 4 GiB +frame_descriptor: + movw base + 2, %cx + movw base, %dx + movw $7, %ax + int $0x31 + movw $0xffff, %cx + movw $0xffff, %dx + movw $8, %ax + int $0x31 + ret +// int 21h in real mode with the registers as they are (no pointers): DPMI 0300h +dos_rm: + pushl %edi + movl %eax, rm_eax + movl %ebx, rm_ebx + movl %ecx, rm_ecx + movl %edx, rm_edx + movw tb_seg, %ax + movw %ax, rm_ds + movw $0x0202, rm_flags // interrupts enabled: DOS may wait on them + movw $0, rm_ss + movw $0, rm_sp + pushw %es + movw %ds, %ax + movw %ax, %es + movl $rm_regs, %edi // a 32-bit client's structure is at ES:EDI, all of it + movw $0x0300, %ax + movw $0x21, %bx + xorw %cx, %cx + int $0x31 + popw %es + popl %edi + jc 1f + testw $1, rm_flags + jnz 1f + movl rm_eax, %eax + clc + ret +1: stc + ret +// one PT_LOAD at SI: its file bytes, then zero to its size in memory +load_segment: + movl 4(%si), %eax // p_offset + addl $stub_end, %eax + movl %eax, %edx + shrl $16, %eax + movw %ax, %cx + movw handle, %bx + movw $0x4200, %ax + call dos_rm + jc 9f + movl 8(%si), %edi // p_vaddr: the destination in the block + movl 16(%si), %ebp // p_filesz +1: testl %ebp, %ebp + jz 3f + movl $(TB_PARAS * 16), %ecx + cmpl %ecx, %ebp + jae 2f + movl %ebp, %ecx +2: pushl %ecx + movw handle, %bx + xorl %edx, %edx + movb $0x3f, %ah + call dos_rm + popl %ecx + jc 9f + cmpl %ecx, %eax + jne 8f + // copy ECX bytes from the transfer buffer to the block at EDI + pushw %ds + pushw %es + pushw %si + movw data_sel, %ax + movw tb_sel, %dx + movw %ax, %es + movw %dx, %ds + xorl %esi, %esi + cld + subl %ecx, %ebp + .byte 0x67, 0xf3, 0xa4 // rep movsb, 32-bit addresses + popw %si + popw %es + popw %ds + jmp 1b +3: movl 20(%si), %ecx // p_memsz - p_filesz, zero + subl 16(%si), %ecx + jz 4f + pushw %es + movw data_sel, %ax + movw %ax, %es + xorb %al, %al + cld + .byte 0x67, 0xf3, 0xaa // rep stosb, 32-bit addresses + popw %es +4: clc + ret +8: stc +9: ret + +// ── Failures: a sentence on standard error, then status 255 ──────────────── +fail_memory: movw $m_memory, %dx; jmp fail_rm +fail_open: movw $m_open, %dx; jmp fail_rm +fail_read: movw $m_read, %dx; jmp fail_rm +fail_format: movw $m_format, %dx; jmp fail_rm +fail_dpmi: movw $m_dpmi, %dx; jmp fail_rm +fail_switch: movw $m_switch, %dx; jmp fail_rm +fail_rm: + movw %cs, %ax + movw %ax, %ds + movw %dx, %si + xorw %cx, %cx +1: cmpb $0, (%si) + je 2f + incw %si + incw %cx + jmp 1b +2: movw $2, %bx + movb $0x40, %ah + int $0x21 + movw $0x4cff, %ax + int $0x21 +fail_block: movw $m_block, %dx; jmp fail_pm +fail_desc: movw $m_desc, %dx; jmp fail_pm +fail_memory_pm: movw $m_memory, %dx; jmp fail_pm +fail_read_pm: movw $m_read, %dx; jmp fail_pm +fail_pm: + // In protected mode int 21h AH=40h takes DS:DX as it is only where the host translates it; + // the message is copied to the transfer buffer and written from real mode instead, where + // the buffer exists; without one, the status alone says it. + movw tb_sel, %ax + testw %ax, %ax + jz 3f + movw %ax, %es + movw %dx, %si + xorw %di, %di + xorw %cx, %cx +1: movb (%si), %al + testb %al, %al + jz 2f + movb %al, %es:(%di) + incw %si + incw %di + incw %cx + jmp 1b +2: movw $2, %bx + xorw %dx, %dx + movb $0x40, %ah + call dos_rm +3: movw $0x4cff, %ax + int $0x21 + +m_memory: .asciz "okdos: not enough memory\r\n" +m_open: .asciz "okdos: the program cannot open itself\r\n" +m_read: .asciz "okdos: the program's executable cannot be read\r\n" +m_format: .asciz "okdos: the appended executable is not ELF32 i386 with at most 16 program headers\r\n" +m_dpmi: .asciz "okdos: no 32-bit DPMI host, and none from CWSDPMI.EXE beside the program\r\n" +m_switch: .asciz "okdos: the DPMI host refused protected mode\r\n" +m_block: .asciz "okdos: the DPMI host has no block of the executable's size\r\n" +m_desc: .asciz "okdos: the DPMI host refused the program's descriptors\r\n" +cwsdpmi_name: .asciz "CWSDPMI.EXE" + + .balign 4 +psp_seg: .word 0 +psp_sel: .word 0 +env_seg: .word 0 +name_off: .word 0 +path_sep: .word 0 +handle: .word 0 +saved_sp: .word 0 +phnum: .word 0 +host_paras: .word 0 +host_seg: .word 0 +switch_entry: .word 0, 0 +code_sel: .word 0 +data_sel: .word 0 +dpl: .word 0 +tb_seg: .word 0 +tb_sel: .word 0 + .balign 4 +span: .long 0 +block_size: .long 0 +base: .long 0 +block_handle: .long 0 +exec_block: .word 0 +exec_tail: .word exec_empty, 0 +exec_fcb1: .word exec_fcb, 0 +exec_fcb2: .word exec_fcb, 0 +exec_empty: .byte 0, 0x0d +exec_fcb: .space 16, 0 +rm_regs: +rm_edi: .long 0 +rm_esi: .long 0 +rm_ebp: .long 0 + .long 0 +rm_ebx: .long 0 +rm_edx: .long 0 +rm_ecx: .long 0 +rm_eax: .long 0 +rm_flags: .word 0 +rm_es: .word 0 +rm_ds: .word 0 +rm_fs: .word 0 +rm_gs: .word 0 +rm_ip: .word 0 +rm_cs: .word 0 +rm_sp: .word 0 +rm_ss: .word 0 +ehdr: .space 52, 0 +path: .space 128, 0 + .balign 4 +phdrs: .space 32 * PHMAX, 0 + .balign 16 +stub_end: diff --git a/tests/fault/mcpp.toml b/tests/fault/mcpp.toml new file mode 100644 index 0000000..b82f15b --- /dev/null +++ b/tests/fault/mcpp.toml @@ -0,0 +1,12 @@ +# A program that writes where no memory is: openkal-dos reports the page fault on standard error and +# the program ends with 139 (128 and SIGSEGV's number), where CWSDPMI alone ends it with 1. +[package] +name = "fault" +version = "0.1.0" + +[targets.fault] +kind = "bin" +main = "src/main.cpp" + +[target.i686-dos-musl] +platform = "openkal-platform@0.28.0" diff --git a/tests/fault/src/main.cpp b/tests/fault/src/main.cpp new file mode 100644 index 0000000..c9fd2a8 --- /dev/null +++ b/tests/fault/src/main.cpp @@ -0,0 +1,10 @@ +#include + +int main() { + std::printf("before the fault\n"); + std::fflush(stdout); + volatile int* nowhere = reinterpret_cast(0xfffffff0u); + *nowhere = 1; + std::printf("after the fault\n"); + return 3; +} diff --git a/tools/mcpp.toml b/tools/mcpp.toml new file mode 100644 index 0000000..a90ece7 --- /dev/null +++ b/tools/mcpp.toml @@ -0,0 +1,15 @@ +# okdos: the seal of a DOS program --- the stub of openkal-dos written before an ELF32 i386 +# executable. A tool of the building machine, which a platform names in `[platform.tools]` and +# runs as its link's seal. Technical experiment only (README). +[package] +namespace = "mcpplibs" +name = "openkal-dos-tools" +version = "0.1.0" +description = "okdos: seals an ELF32 i386 executable as a DOS program behind openkal-dos's DPMI stub (technical experiment only)" +license = "Apache-2.0" +authors = ["mcpplibs"] +repo = "https://github.com/mcpplibs/openkal-dos" + +[targets.okdos] +kind = "bin" +main = "src/okdos.cpp" diff --git a/tools/src/okdos.cpp b/tools/src/okdos.cpp new file mode 100644 index 0000000..23284d5 --- /dev/null +++ b/tools/src/okdos.cpp @@ -0,0 +1,109 @@ +// okdos: the seal of a DOS program (openkal-dos; technical experiment only). +// +// okdos seal the ELF32 i386 executable becomes a DOS program: the stub +// (stub/stub.S) followed by the executable, written in its place +// okdos check whether is a sealed program, and of which stub +// +// The stub loads the executable's loadable segments from its own file into DPMI memory and +// enters it (src/dos.h states the hand-over). Sealing a sealed program is refused, naming it. +#include "stub_bytes.h" +import std; + +namespace { + +int usage() { + std::println(std::cerr, "usage: okdos seal \n okdos check "); + return 2; +} + +std::optional read(const std::filesystem::path& p) { + std::ifstream in(p, std::ios::binary); + if (!in) return std::nullopt; + return std::string(std::istreambuf_iterator(in), {}); +} + +std::uint32_t u32(const std::string& b, std::size_t at) { + std::uint32_t v = 0; + for (int i = 3; i >= 0; --i) v = (v << 8) | static_cast(b[at + i]); + return v; +} +std::uint16_t u16(const std::string& b, std::size_t at) { + return static_cast(static_cast(b[at]) | static_cast(b[at + 1]) << 8); +} + +bool sealed(const std::string& b) { + return b.size() >= sizeof kOkdosStub + && std::equal(std::begin(kOkdosStub), std::end(kOkdosStub), b.begin(), + [](unsigned char s, char c) { return s == static_cast(c); }); +} + +// What the stub requires of the executable: ELF32, little-endian, i386, an executable (not +// position-independent: the stub applies no relocation), at most 16 program headers. +std::optional refuse(const std::string& b) { + if (b.size() < 52 || b.compare(0, 4, "\x7f" "ELF") != 0) return "not ELF"; + if (b[4] != 1 || b[5] != 1) return "not 32-bit little-endian ELF"; + if (u16(b, 18) != 3) return "not i386 (EM_386)"; + if (u16(b, 16) != 2) return "not an executable linked at its address (ET_EXEC): the stub applies no relocation"; + if (u16(b, 44) > 16) return "more than 16 program headers"; + const std::size_t phoff = u32(b, 28); + for (std::size_t i = 0; i < u16(b, 44); ++i) { + const std::size_t at = phoff + i * 32; + if (at + 32 > b.size()) return "program headers beyond the file"; + if (u32(b, at) == 1 && u32(b, at + 4) + static_cast(u32(b, at + 16)) > b.size()) + return "a loadable segment beyond the file"; + } + return std::nullopt; +} + +} // namespace + +int main(int argc, char** argv) { + if (argc != 3) return usage(); + const std::string_view verb = argv[1]; + const std::filesystem::path file = argv[2]; + const auto bytes = read(file); + if (!bytes) { + std::println(std::cerr, "okdos: {} cannot be read", file.string()); + return 1; + } + if (verb == "check") { + if (!sealed(*bytes)) { + std::println(std::cerr, "okdos: {} is not sealed by this stub", file.string()); + return 1; + } + const std::string elf = bytes->substr(sizeof kOkdosStub); + if (auto why = refuse(elf)) { + std::println(std::cerr, "okdos: {}: the executable after the stub is {}", file.string(), *why); + return 1; + } + std::println("{}: a DOS program: the okdos stub ({} bytes) and an ELF32 i386 executable ({} bytes)", + file.string(), sizeof kOkdosStub, elf.size()); + return 0; + } + if (verb != "seal") return usage(); + if (sealed(*bytes)) { + std::println(std::cerr, "okdos: {} is sealed already", file.string()); + return 1; + } + if (auto why = refuse(*bytes)) { + std::println(std::cerr, "okdos: {} cannot be sealed: {}", file.string(), *why); + return 1; + } + const auto temp = std::filesystem::path(file).concat(".okdos"); + { + std::ofstream out(temp, std::ios::binary | std::ios::trunc); + out.write(reinterpret_cast(kOkdosStub), sizeof kOkdosStub); + out.write(bytes->data(), static_cast(bytes->size())); + if (!out) { + std::println(std::cerr, "okdos: {} cannot be written", temp.string()); + return 1; + } + } + std::error_code ec; + std::filesystem::rename(temp, file, ec); + if (ec) { + std::println(std::cerr, "okdos: {} cannot replace {}: {}", temp.string(), file.string(), ec.message()); + return 1; + } + return 0; +} diff --git a/tools/src/stub_bytes.h b/tools/src/stub_bytes.h new file mode 100644 index 0000000..4e869ac --- /dev/null +++ b/tools/src/stub_bytes.h @@ -0,0 +1,164 @@ +// Generated by stub/build.sh from stub/stub.S; do not edit. +// 2544 bytes, sha256 5c53d70f4175825926c74a1403381cbeb4c9164af30ea1b402fd0103bcc6f01b +#pragma once +inline constexpr unsigned char kOkdosStub[] = { + 0x4d, 0x5a, 0xf0, 0x01, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x00, 0xff, 0xff, 0xfe, 0xff, + 0xf0, 0x11, 0x00, 0x00, 0x20, 0x00, 0xfe, 0xff, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8c, 0xc0, 0x8c, 0xcb, 0x8e, 0xdb, 0xa3, 0xa8, 0x06, 0x8c, 0xcb, 0x29, 0xc3, 0x81, 0xc3, 0x1f, + 0x01, 0xb4, 0x4a, 0xcd, 0x21, 0x0f, 0x82, 0x30, 0x04, 0x8e, 0x06, 0xa8, 0x06, 0x26, 0xa1, 0x2c, + 0x00, 0xa3, 0xac, 0x06, 0x8e, 0xc0, 0x31, 0xff, 0x26, 0x80, 0x3d, 0x00, 0x74, 0x0a, 0x47, 0x26, + 0x80, 0x3d, 0x00, 0x75, 0xf9, 0x47, 0xeb, 0xf0, 0x83, 0xc7, 0x03, 0x89, 0x3e, 0xae, 0x06, 0xbe, + 0x62, 0x07, 0x31, 0xdb, 0x26, 0x8a, 0x05, 0x88, 0x04, 0x3c, 0x5c, 0x75, 0x02, 0x89, 0xf3, 0x47, + 0x46, 0x3c, 0x00, 0x75, 0xef, 0x89, 0x1e, 0xb0, 0x06, 0x1e, 0x8e, 0x1e, 0xac, 0x06, 0x2e, 0x8b, + 0x16, 0xae, 0x06, 0xb8, 0x00, 0x3d, 0xcd, 0x21, 0x1f, 0x0f, 0x82, 0xe1, 0x03, 0xa3, 0xb2, 0x06, + 0xba, 0xf0, 0x09, 0x31, 0xc9, 0xe8, 0x49, 0x02, 0xba, 0x2e, 0x07, 0xb9, 0x34, 0x00, 0xe8, 0x4a, + 0x02, 0x0f, 0x82, 0xce, 0x03, 0x66, 0x81, 0x3e, 0x2e, 0x07, 0x7f, 0x45, 0x4c, 0x46, 0x0f, 0x85, + 0xc6, 0x03, 0x81, 0x3e, 0x32, 0x07, 0x01, 0x01, 0x0f, 0x85, 0xbc, 0x03, 0x83, 0x3e, 0x40, 0x07, + 0x03, 0x0f, 0x85, 0xb3, 0x03, 0x8b, 0x0e, 0x5a, 0x07, 0x83, 0xf9, 0x10, 0x0f, 0x87, 0xa8, 0x03, + 0x89, 0x0e, 0xb6, 0x06, 0xc1, 0xe1, 0x05, 0x66, 0x8b, 0x16, 0x4a, 0x07, 0x66, 0x81, 0xc2, 0xf0, + 0x09, 0x00, 0x00, 0x51, 0x66, 0x89, 0xd0, 0x89, 0xc2, 0x66, 0xc1, 0xe8, 0x10, 0x89, 0xc1, 0xe8, + 0xef, 0x01, 0x59, 0xba, 0xe4, 0x07, 0xe8, 0xf2, 0x01, 0x0f, 0x82, 0x76, 0x03, 0x66, 0x31, 0xc0, + 0x8b, 0x0e, 0xb6, 0x06, 0xbe, 0xe4, 0x07, 0xe3, 0x1c, 0x66, 0x83, 0x3c, 0x01, 0x75, 0x10, 0x66, + 0x8b, 0x54, 0x08, 0x66, 0x03, 0x54, 0x14, 0x66, 0x39, 0xc2, 0x76, 0x03, 0x66, 0x89, 0xd0, 0x83, + 0xc6, 0x20, 0x49, 0xeb, 0xe2, 0x66, 0x05, 0xff, 0x0f, 0x00, 0x00, 0x66, 0x25, 0x00, 0xf0, 0xff, + 0xff, 0x66, 0xa3, 0xcc, 0x06, 0x66, 0x05, 0x00, 0x10, 0x01, 0x00, 0x66, 0xa3, 0xd0, 0x06, 0xe8, + 0xbd, 0x01, 0x73, 0x0a, 0xe8, 0xd7, 0x01, 0xe8, 0xb5, 0x01, 0x0f, 0x82, 0x2f, 0x03, 0x8b, 0x1e, + 0xb8, 0x06, 0x85, 0xdb, 0x74, 0x0b, 0xb4, 0x48, 0xcd, 0x21, 0x0f, 0x82, 0x0b, 0x03, 0xa3, 0xba, + 0x06, 0x8e, 0x06, 0xba, 0x06, 0xb8, 0x01, 0x00, 0xff, 0x1e, 0xbc, 0x06, 0x0f, 0x82, 0x12, 0x03, + 0x8c, 0x06, 0xaa, 0x06, 0x66, 0xa1, 0xd0, 0x06, 0x89, 0xc1, 0x66, 0xc1, 0xe8, 0x10, 0x89, 0xc3, + 0xb8, 0x01, 0x05, 0xcd, 0x31, 0x0f, 0x82, 0x1b, 0x03, 0x89, 0x1e, 0xd6, 0x06, 0x89, 0x0e, 0xd4, + 0x06, 0x89, 0x36, 0xda, 0x06, 0x89, 0x3e, 0xd8, 0x06, 0x31, 0xc0, 0xb9, 0x02, 0x00, 0xcd, 0x31, + 0x0f, 0x82, 0x05, 0x03, 0xa3, 0xc0, 0x06, 0x89, 0xc3, 0xb8, 0x03, 0x00, 0xcd, 0x31, 0x01, 0xd8, + 0xa3, 0xc2, 0x06, 0x8c, 0xc8, 0x83, 0xe0, 0x03, 0xc1, 0xe0, 0x05, 0xa3, 0xc4, 0x06, 0x8b, 0x1e, + 0xc0, 0x06, 0xe8, 0xaa, 0x01, 0x8b, 0x0e, 0xc4, 0x06, 0x81, 0xc9, 0x9a, 0xc0, 0xb8, 0x09, 0x00, + 0xcd, 0x31, 0x0f, 0x82, 0xd3, 0x02, 0x8b, 0x1e, 0xc2, 0x06, 0xe8, 0x92, 0x01, 0x8b, 0x0e, 0xc4, + 0x06, 0x81, 0xc9, 0x92, 0xc0, 0xb8, 0x09, 0x00, 0xcd, 0x31, 0x0f, 0x82, 0xbb, 0x02, 0xb8, 0x00, + 0x01, 0xbb, 0x00, 0x04, 0xcd, 0x31, 0x0f, 0x82, 0xb4, 0x02, 0xa3, 0xc6, 0x06, 0x89, 0x16, 0xc8, + 0x06, 0x8b, 0x0e, 0xb6, 0x06, 0xbe, 0xe4, 0x07, 0xe3, 0x17, 0x51, 0x56, 0x66, 0x83, 0x3c, 0x01, + 0x75, 0x07, 0xe8, 0xca, 0x01, 0x0f, 0x82, 0x9a, 0x02, 0x5e, 0x59, 0x83, 0xc6, 0x20, 0x49, 0xeb, + 0xe7, 0x8e, 0x06, 0xc2, 0x06, 0x66, 0x8b, 0x3e, 0xcc, 0x06, 0x26, 0x67, 0x66, 0xc7, 0x07, 0x4f, + 0x4b, 0x44, 0x53, 0x26, 0x67, 0x66, 0xc7, 0x47, 0x04, 0x01, 0x00, 0x00, 0x00, 0x66, 0xa1, 0xd4, + 0x06, 0x26, 0x67, 0x66, 0x89, 0x47, 0x08, 0x66, 0xa1, 0xd0, 0x06, 0x26, 0x67, 0x66, 0x89, 0x47, + 0x0c, 0x66, 0xa1, 0xd8, 0x06, 0x26, 0x67, 0x66, 0x89, 0x47, 0x10, 0xa1, 0xaa, 0x06, 0x26, 0x67, + 0x89, 0x47, 0x14, 0xa1, 0xa8, 0x06, 0x26, 0x67, 0x89, 0x47, 0x16, 0xa1, 0xac, 0x06, 0x26, 0x67, + 0x89, 0x47, 0x18, 0xa1, 0xc6, 0x06, 0x26, 0x67, 0x89, 0x47, 0x1a, 0x26, 0x67, 0x66, 0xc7, 0x47, + 0x1c, 0x00, 0x40, 0x00, 0x00, 0xa1, 0xc0, 0x06, 0x26, 0x67, 0x89, 0x47, 0x20, 0xa1, 0xc2, 0x06, + 0x26, 0x67, 0x89, 0x47, 0x22, 0xa1, 0xc8, 0x06, 0x26, 0x67, 0x89, 0x47, 0x24, 0xa1, 0xb2, 0x06, + 0x26, 0x67, 0x89, 0x47, 0x26, 0x67, 0x66, 0x8d, 0x7f, 0x28, 0xbe, 0x62, 0x07, 0x8a, 0x04, 0x26, + 0x67, 0x88, 0x07, 0x46, 0x66, 0x47, 0x84, 0xc0, 0x75, 0xf3, 0x66, 0x8b, 0x1e, 0xcc, 0x06, 0x66, + 0x8b, 0x16, 0x46, 0x07, 0x8b, 0x0e, 0xc0, 0x06, 0xa1, 0xc2, 0x06, 0x8e, 0xd8, 0x8e, 0xc0, 0x8e, + 0xd0, 0x2e, 0x66, 0x8b, 0x26, 0xd0, 0x06, 0x66, 0x83, 0xec, 0x10, 0x66, 0x51, 0x66, 0x52, 0x66, + 0xcb, 0x8b, 0x1e, 0xb2, 0x06, 0xb8, 0x00, 0x42, 0xcd, 0x21, 0xc3, 0x51, 0x8b, 0x1e, 0xb2, 0x06, + 0xb4, 0x3f, 0xcd, 0x21, 0x59, 0x72, 0x07, 0x39, 0xc8, 0x75, 0x02, 0xf8, 0xc3, 0xf9, 0xc3, 0xb8, + 0x87, 0x16, 0xcd, 0x2f, 0x85, 0xc0, 0x75, 0x14, 0xf7, 0xc3, 0x01, 0x00, 0x74, 0x0e, 0x89, 0x36, + 0xb8, 0x06, 0x89, 0x3e, 0xbc, 0x06, 0x8c, 0x06, 0xbe, 0x06, 0xf8, 0xc3, 0xf9, 0xc3, 0x8b, 0x3e, + 0xb0, 0x06, 0x47, 0xbe, 0x9a, 0x06, 0x8a, 0x04, 0x88, 0x05, 0x46, 0x47, 0x84, 0xc0, 0x75, 0xf6, + 0x8c, 0xc8, 0xa3, 0xe0, 0x06, 0xa3, 0xe4, 0x06, 0xa3, 0xe8, 0x06, 0x8e, 0xc0, 0xbb, 0xdc, 0x06, + 0xba, 0x62, 0x07, 0x89, 0x26, 0xb4, 0x06, 0xb8, 0x00, 0x4b, 0xcd, 0x21, 0x8c, 0xc8, 0x8e, 0xd8, + 0xfa, 0x8e, 0xd0, 0x8b, 0x26, 0xb4, 0x06, 0xfb, 0x8e, 0x06, 0xac, 0x06, 0x8b, 0x36, 0xae, 0x06, + 0xbf, 0x62, 0x07, 0x26, 0x8a, 0x04, 0x88, 0x05, 0x46, 0x47, 0x84, 0xc0, 0x75, 0xf5, 0xc3, 0x8b, + 0x0e, 0xd6, 0x06, 0x8b, 0x16, 0xd4, 0x06, 0xb8, 0x07, 0x00, 0xcd, 0x31, 0xb9, 0xff, 0xff, 0xba, + 0xff, 0xff, 0xb8, 0x08, 0x00, 0xcd, 0x31, 0xc3, 0x66, 0x57, 0x66, 0xa3, 0x18, 0x07, 0x66, 0x89, + 0x1e, 0x0c, 0x07, 0x66, 0x89, 0x0e, 0x14, 0x07, 0x66, 0x89, 0x16, 0x10, 0x07, 0xa1, 0xc6, 0x06, + 0xa3, 0x20, 0x07, 0xc7, 0x06, 0x1c, 0x07, 0x02, 0x02, 0xc7, 0x06, 0x2c, 0x07, 0x00, 0x00, 0xc7, + 0x06, 0x2a, 0x07, 0x00, 0x00, 0x06, 0x8c, 0xd8, 0x8e, 0xc0, 0x66, 0xbf, 0xfc, 0x06, 0x00, 0x00, + 0xb8, 0x00, 0x03, 0xbb, 0x21, 0x00, 0x31, 0xc9, 0xcd, 0x31, 0x07, 0x66, 0x5f, 0x72, 0x0e, 0xf7, + 0x06, 0x1c, 0x07, 0x01, 0x00, 0x75, 0x06, 0x66, 0xa1, 0x18, 0x07, 0xf8, 0xc3, 0xf9, 0xc3, 0x66, + 0x8b, 0x44, 0x04, 0x66, 0x81, 0xc0, 0xf0, 0x09, 0x00, 0x00, 0x66, 0x89, 0xc2, 0x66, 0xc1, 0xe8, + 0x10, 0x89, 0xc1, 0x8b, 0x1e, 0xb2, 0x06, 0xb8, 0x00, 0x42, 0xe8, 0x8b, 0xff, 0x72, 0x69, 0x66, + 0x8b, 0x7c, 0x08, 0x66, 0x8b, 0x6c, 0x10, 0x66, 0x85, 0xed, 0x74, 0x42, 0x66, 0xb9, 0x00, 0x40, + 0x00, 0x00, 0x66, 0x39, 0xcd, 0x73, 0x03, 0x66, 0x89, 0xe9, 0x66, 0x51, 0x8b, 0x1e, 0xb2, 0x06, + 0x66, 0x31, 0xd2, 0xb4, 0x3f, 0xe8, 0x60, 0xff, 0x66, 0x59, 0x72, 0x3c, 0x66, 0x39, 0xc8, 0x75, + 0x36, 0x1e, 0x06, 0x56, 0xa1, 0xc2, 0x06, 0x8b, 0x16, 0xc8, 0x06, 0x8e, 0xc0, 0x8e, 0xda, 0x66, + 0x31, 0xf6, 0xfc, 0x66, 0x29, 0xcd, 0x67, 0xf3, 0xa4, 0x5e, 0x07, 0x1f, 0xeb, 0xb9, 0x66, 0x8b, + 0x4c, 0x14, 0x66, 0x2b, 0x4c, 0x10, 0x74, 0x0d, 0x06, 0xa1, 0xc2, 0x06, 0x8e, 0xc0, 0x30, 0xc0, + 0xfc, 0x67, 0xf3, 0xaa, 0x07, 0xf8, 0xc3, 0xf9, 0xc3, 0xba, 0xe4, 0x04, 0xeb, 0x19, 0xba, 0xff, + 0x04, 0xeb, 0x14, 0xba, 0x27, 0x05, 0xeb, 0x0f, 0xba, 0x58, 0x05, 0xeb, 0x0a, 0xba, 0xab, 0x05, + 0xeb, 0x05, 0xba, 0xf6, 0x05, 0xeb, 0x00, 0x8c, 0xc8, 0x8e, 0xd8, 0x89, 0xd6, 0x31, 0xc9, 0x80, + 0x3c, 0x00, 0x74, 0x04, 0x46, 0x41, 0xeb, 0xf7, 0xbb, 0x02, 0x00, 0xb4, 0x40, 0xcd, 0x21, 0xb8, + 0xff, 0x4c, 0xcd, 0x21, 0xba, 0x24, 0x06, 0xeb, 0x0f, 0xba, 0x61, 0x06, 0xeb, 0x0a, 0xba, 0xe4, + 0x04, 0xeb, 0x05, 0xba, 0x27, 0x05, 0xeb, 0x00, 0xa1, 0xc8, 0x06, 0x85, 0xc0, 0x74, 0x20, 0x8e, + 0xc0, 0x89, 0xd6, 0x31, 0xff, 0x31, 0xc9, 0x8a, 0x04, 0x84, 0xc0, 0x74, 0x08, 0x26, 0x88, 0x05, + 0x46, 0x47, 0x41, 0xeb, 0xf2, 0xbb, 0x02, 0x00, 0x31, 0xd2, 0xb4, 0x40, 0xe8, 0xa9, 0xfe, 0xb8, + 0xff, 0x4c, 0xcd, 0x21, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, + 0x6e, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x0d, 0x0a, 0x00, 0x6f, + 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x69, 0x74, + 0x73, 0x65, 0x6c, 0x66, 0x0d, 0x0a, 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x27, 0x73, 0x20, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, + 0x20, 0x72, 0x65, 0x61, 0x64, 0x0d, 0x0a, 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x45, 0x4c, + 0x46, 0x33, 0x32, 0x20, 0x69, 0x33, 0x38, 0x36, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x74, + 0x20, 0x6d, 0x6f, 0x73, 0x74, 0x20, 0x31, 0x36, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x0d, 0x0a, 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, + 0x3a, 0x20, 0x6e, 0x6f, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x69, 0x74, 0x20, 0x44, 0x50, 0x4d, 0x49, + 0x20, 0x68, 0x6f, 0x73, 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x43, 0x57, 0x53, 0x44, 0x50, 0x4d, 0x49, 0x2e, 0x45, 0x58, 0x45, + 0x20, 0x62, 0x65, 0x73, 0x69, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x0d, 0x0a, 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x44, 0x50, 0x4d, 0x49, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x20, 0x72, 0x65, 0x66, 0x75, 0x73, + 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x6d, 0x6f, 0x64, + 0x65, 0x0d, 0x0a, 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, + 0x50, 0x4d, 0x49, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x20, 0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x0d, 0x0a, + 0x00, 0x6f, 0x6b, 0x64, 0x6f, 0x73, 0x3a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, 0x50, 0x4d, 0x49, + 0x20, 0x68, 0x6f, 0x73, 0x74, 0x20, 0x72, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x27, 0x73, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x0d, 0x0a, 0x00, 0x43, 0x57, 0x53, 0x44, 0x50, 0x4d, + 0x49, 0x2e, 0x45, 0x58, 0x45, 0x00, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x06, + 0x00, 0x00, 0xec, 0x06, 0x00, 0x00, 0xec, 0x06, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x8d, 0xb4, 0x00, 0x00, 0x8d, 0xb4, 0x00, 0x00, 0x8d, 0xb4, 0x00, 0x00, +};